Intercambio de caras

Seguimos estudiando las posibilidades que nos brindan el uso de las bibliotecas Dlib y OpenCV, en esta ocasión vamos a construir una aplicación que nos permitirá intercambiar los rostros de dos personas, aprenderemos a utilizar las funciones cv::convexHull(...) y la clase Subdiv2D, aplicaremos máscaras y utilizaremos la ya conocida función cv::seamelessClone(...).

opencv-faceswap

Para desarrollar nuestra aplicación haremos lo siguiente:

1. Detectar la región del rostro

Esto ya lo hemos realizado en tutoriales anteriores, usando los clasificadores en cascada obtenemos la región de la imagen que contiene el rostro, esta región la pasamos a Dlib para procesarla y obtener los landmarks, el primer fragmento de código que veremos corresponde a la detección del rostro presente en la imagen.

void locateFaceRect(cv::Mat& face1, cv::CascadeClassifier& face_cascade, cv::Rect& rect) {

    cv::Mat frame_gray;
    std::vector<cv::Rect> faces;

    // convertir a grises y equalizar histograma
    cv::cvtColor(face1, frame_gray, CV_BGR2GRAY);
    cv::equalizeHist(frame_gray, frame_gray);

    // usar el clasificador en cascada para detectar el rostro presente
    face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, cv::Size(30, 30));

    // guardar la region que contiene el rostro
    if (!faces.empty()) rect = faces[0];
}

La tarea de esta función es obtener el rectángulo en donde se ubica el rostro.

detectar-caras

2. Obtener la máscara de rostro

El próximo fragmento de código se encargará de obtener la región las especifica del rostro, para ello usaremos los landmarks, con estos puntos de referencia podremos crear una mascara que nos permitirá definir de manera las precisa la región de la imagen que contiene el rostro.

void generateFaceTriangles(cv::Mat& face1, cv::Rect& rc, dlib::shape_predictor& sp, cv::Mat& mask, std::vector<cv::Point2f>& pts)
{
    std::vector<cv::Point> landmarks, hull;

    // convertir la imagen OpenCV a Dlib
    cv_image<bgr_pixel> cimg(face1);

    // crear el rectangulo que contiene la cara y detectar los landmarks
    dlib::rectangle rect(rc.x, rc.y, rc.x + rc.width, rc.y + rc.height);
    dlib::full_object_detection shape = sp(cimg, rect);

    // obtener los puntos detectados
    for (unsigned long i = 0; i < shape.num_parts(); i++) 
    {
        // guardar los landmarks
        landmarks.push_back(cv::Point(shape.part(i).x(), shape.part(i).y()));

        // mostrar los landmarks
        cv::circle(face1, landmarks[i], 2, cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
    }

    // guardar el triangulo del rostro
    if (!landmarks.empty()) {
        pts.push_back(landmarks[0]);
        pts.push_back(landmarks[16]);
        pts.push_back(landmarks[8]);
    }

    // crear la mascara, rellenar con ceros (negro) 
    mask.create(face1.size(), CV_8UC1);
    mask = cv::Scalar::all(0);

    // determinar la region de la cara
    cv::convexHull(landmarks, hull);
    cv::fillConvexPoly(mask, hull, cv::Scalar::all(255), cv::LINE_AA);

    // dibujar los contornos de la cara
    cv::drawContours(face1, std::vector<std::vector<cv::Point>> { hull }, 0, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
}

La mayoría de este código ya lo conocemos, con él logramos este resultado:

extraer-region-rostro

En color azul los puntos detectados usados Dlib, para lograr obtener el polígono que se muestra en color verde, el cual recubre la región que comprende el rostro hemos usado la función cv::convexHull(landmarks, hull), esta nos devuelve el polígono convexo que recubre los puntos indicados.

Otra cosa que necesitamos es crear una máscara que defina la región de la cara, para ello vamos a crear una imagen que inicialmente rellenamos con color negro, luego usamos cv::fillConvexPoly(...) para rellenar el polígono obtenido previamente, el color de relleno será blanco, con esto obtenemos lo siguiente:

// crear la mascara, rellenar con ceros (negro) 
mask.create(face1.size(), CV_8UC1);
mask = cv::Scalar::all(0);

// determinar la region de la cara
cv::convexHull(landmarks, hull);
cv::fillConvexPoly(mask, hull, cv::Scalar::all(255), cv::LINE_AA);

Al finalizar obtenemos la siguiente máscara:

mascara

Como vez una máscara es una imagen binaria que define un área que deseamos trabajar, el área en color blanco representa nuestra zona de interés, el color negro será ignorado.

3. Fusionar las caras

El paso final es intercambiar las caras y fusionarlas, lo primero que debemos hacer es transformar el rostro A para que se pueda ubicar en la posición del rostro B, esto implica que debemos cambiar su tamaño y rotación de ser necesario, logramos hacer esto usando la función OpenCV cv::getAffineTransform(src, dst) y cv::warpAffine(...), el primero obtiene la matriz de transformación requerida para posicionar el triangulo definido en el triangulo de destino.

Los puntos usados para definir los triángulos de src y dst corresponden a los puntos (0, 17, 9) de los landmarks obtenidos.

alinear-caras

// obtener la matriz de transformacion 
cv::Mat tf = cv::getAffineTransform(src, dst);

// transformat la imagen y la mascara
cv::Mat face1tf;
cv::warpAffine(face1, face1tf, tf, face2.size());
cv::warpAffine(mask1, mask1, tf, face2.size());

Para finalizar debemos intercambiar ambos rostros, podemos hacer algo tan simple como copiar el rostro transformado usando la mascara que también hemos transformado.

cv::Mat out = face2.clone();
face1tf.copyTo(out, mask1);

intercambiar-rostros

Podemos ver que los rostros se alinean y se intercambian correctamente, pero el resultado no es muy realista, por este motivo aplicaremos la función cv::seamelessClone() que nos permite fusionar dos imágenes de una mejor manera.

// obtener el centro de la mascara
cv::Moments m = cv::moments(mask1, false);
cv::Point center(m.m10 / m.m00, m.m01 / m.m00);

// fusionar las imagenes
cv::Mat out;
cv::seamlessClone(face1tf, face2, mask1, center, out, cv::NORMAL_CLONE);

opencv-dlib-faceswap

La siguiente función es la encargada de realizar este trabajo:

cv::Mat mixFaces(cv::Mat face1, cv::Mat face2, cv::Mat mask1, std::vector<cv::Point2f> src, std::vector<cv::Point2f> dst) {

    // obtener la matriz de transformacion 
    cv::Mat tf = cv::getAffineTransform(src, dst);

    // transformat la imagen y la mascara
    cv::Mat face1tf;
    cv::warpAffine(face1, face1tf, tf, face2.size());
    cv::warpAffine(mask1, mask1, tf, face2.size());

    // obtener el centro de la mascara
    cv::Moments m = cv::moments(mask1, false);
    cv::Point center(m.m10 / m.m00, m.m01 / m.m00);

    // fusionar las imagenes
    cv::Mat out;
    cv::seamlessClone(face1tf, face2, mask1, center, out, cv::NORMAL_CLONE);

    return out;
}

Este proyecto esta desarrollado de la manera mas simple posible para facilitar su entendimiento, está preparado para procesar imágenes con rostros de frente, el proyecto incluye el script CMake necesario para generar, también incluye la librería Dlib recuerda que debe tener OpenCV instalado.

Antes de generar el proyecto con CMake debes descargar el modelo pre-entrenado requerido por Dlib, debes copiarlo en la carpeta data.

Descargar: intercambio-rostros-opencv-dlib.zip

Comentarios

  1. Excelente este intercambio de caras! Estoy haciendo un curso de programación y estos tutoriales me encantan!

    ResponderEliminar
  2. Seria genial que ese ejemplo estuviese representado en imágenes didácticas en Android Studio. Estoy aprendiendo hacer app y no entiendo dónde se colocan los códigos que me mencionaron anteriormente. Un saludo cordial. Si su blog lo hacen más didácticos seria más interesante.

    ResponderEliminar
  3. Puedes hacer intercambio de caras en linea con IA:
    Face Swap

    ResponderEliminar

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

Controles y Contenedores JavaFX 8 - I

Conectar SQL Server con Java

Histogramas OpenCV Python