Face Landmarks Detector con Dlib y OpenCV
Dlib en una biblioteca multipropósito que puede ser usada desde C++ y Python, contiene muchos algoritmos de aprendizaje de máquinas, compresión, análisis de imágenes, entre otras, esta librería es de código abierto y debemos compilarla para poder utilizarla, para nuestro tutorial utilizaremos OpenCV para la detección de rostros y Dlib para la obtención de las partes relevantes de la cara, ojos, boca, nariz, etc., cada una de estas partes definida por un conjunto de puntos preestablecidos.
La detección de cada una de las regiones de la cara tiene muchas aplicaciones, entre ellas, realidad aumentada, alineación de rostros, detección de emociones, análisis de expresiones faciales, entre otras.
Los face landmarks de nuestra modelo:
Los resultados de la imagen inferior los puedes obtener con el código de ejemplo proporcionado por la librería Dlib, el archivo es el siguiente: dlib/examples/face_landmark_detection_ex.cpp
Como podemos ver los landmarks son un conjunto de puntos que definen las regiones de la cara, por ejemplo, la nariz está conformada por los puntos que van desde el número 28 al 36, en la imagen superior estos puntos están unidos por una línea.
Para poder localizar cada uno de estos puntos necesitaremos un modelo entrenado para esta tarea, dlib nos permite entrenar nuestros modelos propios pero en esta ocasión utilizaremos uno de los muchos que podemos encontrar en la web, descárgalo en el enlace: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
Detección de landmarks en tiempo real
Vamos a construir un pequeña aplicación que capture imágenes de nuestra webcam, si hay un rostro presente intentaremos ubicar las regiones que nos interesan del mismo.
Lo primero que haremos será usar OpenCV para realizar las capturas de la cámara y luego aplicaremos los clasificadores en cascada para detectar los rostros presentes, puedes tener más detalles en detección de rostros.
// captura imagenes de la camara
cv::VideoCapture cap(0);
if (!cap.isOpened()) {
return 1;
}
// clasificador en cascada que detecta rostros frontales
cv::CascadeClassifier face_cascade;
face_cascade.load("files/data/haarcascade_frontalface_alt.xml");
Dlib tiene una función preparada para detectar caras, pero utilizaremos la de OpenCV pues ya la conocemos de tutoriales anteriores, usar la función de dlib no es difícil, puedes ver los ejemplos incluidos con la librería.
Lo siguiente que haremos será crear la ventana y cargar el modelo que detectará los landmarks.
// crear una ventana dlib
image_window win;
win.set_title("Landmark Detector :: Tutor de Programacion");
// detecta los landmarks
shape_predictor pose_model;
deserialize("files/data/shape_predictor_68_face_landmarks.dat") >> pose_model;
El proceso para detectar los rostros ya lo hemos estudiado anteriormente, como sabemos las regiones en donde se ha detectado una cara se guarda en un std::vector<cv::Rect>
por lo que debemos convertirlo a std::vector<dlib::rectangle>
ya que dlib requiere este tipo de objeto para identificar el rectángulo en donde se encuentra un rostro, esto es lo que hacemos al final del código mostrado abajo.
while (!win.is_closed())
{
// Grab a frame
cv::Mat temp, frame_gray;
// convertir a grises y equalizar histograma
cv::cvtColor(temp, frame_gray, CV_BGR2GRAY);
cv::equalizeHist(frame_gray, frame_gray);
std::vector<cv::Rect> faces;
std::vector<dlib::rectangle> facesRect;
// detectar los rostros
face_cascade.detectMultiScale(frame_gray, faces, 1.2, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, cv::Size(50, 50));
// guardar la region en donde se encuentra la cara
for (cv::Rect& rc : faces) {
facesRect.push_back(rectangle(rc.x, rc.y, rc.x + rc.width, rc.y + rc.height));
}
// obtener los landmarks ...
}
Ahora, por cada rostro ubicado con el proceso anterior detectaremos sus regiones de interés, antes de hacer algo primero debemos convertir el objeto cv::Mat
a un objeto compatible con dlib, es fácil solo hacemos esto: cv_image<bgr_pixel> cimg(temp)
, luego podemos usar la función pose_model(cimg, facesRect[i])
para detectar los landmarks, el primer parámetro es la imagen de entrada y el segundo indica el rectángulo en donde se encuentra el rostro.
// convertir el objeto cv::Mat
cv_image<bgr_pixel> cimg(temp);
// guarda los puntos obtenidos
std::vector<image_window::overlay_circle> points;
// detectar los landmarks para cada rostro encontrado
for (unsigned long i = 0; i < facesRect.size(); ++i) {
full_object_detection shape = pose_model(cimg, facesRect[i]);
// guardar las coordenada de cada landmark
for (unsigned int n = 0; n < shape.num_parts(); n++) {
points.push_back(image_window::overlay_circle(shape.part(n), 2, color));
}
}
Al final creamos un image_window::overlay_circle
para dibujar un círculo en cada uno de los puntos, al aplicar la función pose_model(...)
esta nos devuelve un full_object_detection
usando sus métodos num_parts()
obtenemos la cantidad de puntos encontrados, con part(int)
obtenemos las coordenada X, Y del punto indicado.
Si deseamos por ejemplo obtener la región rectangular que corresponde a la nariz, solo debemos usar shape.part(n)
para obtener las coordenadas de la misma, recordando que n desde estar entre 28 y 36 ya que este es el rango que le corresponde a la nariz.
// guarda los puntos de la region indicada
std::vector<cv::Point> nariz;
// detectar los landmarks para cada rostro encontrado
for (unsigned long i = 0; i < facesRect.size(); ++i) {
full_object_detection shape = pose_model(cimg, facesRect[i]);
// guardar las coordenada de cada landmark
for (unsigned int n = 0; n < shape.num_parts(); n++) {
// guardar las coordenadas de la nariz
if (n >= 28 && n < 36) {
nariz.push_back(cv::Point(shape.part(n).x(), shape.part(n).y()));
}
}
}
// obtener el rectangulo de la nariz y dibujarlo
cv::Rect rectNariz = cv::boundingRect(nariz);
cv::rectangle(temp, rectNariz, cv::Scalar(125, 125, 0));
Como puedes ver guardamos las coordenadas que corresponden a la región que nos interesa ubicar, luego para obtener el rectángulo usamos la función cv::bundingRect(...)
la cual nos devuelve el cv::Rect
que encierra el conjunto de puntos indicado, al final utilizamos la función cv::rectangle(...)
para dibujarla sobre la imagen.
El código completo:
#include <opencv2/opencv.hpp>
#include <dlib/opencv.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/gui_widgets.h>
using namespace dlib;
using namespace std;
int main()
{
try
{
// captura imagenes de la camara
cv::VideoCapture cap("files/data/video.avi");
if (!cap.isOpened()) {
return 1;
}
// clasificador en cascada que detecta rostros frontales
cv::CascadeClassifier face_cascade;
face_cascade.load("files/data/haarcascade_frontalface_alt.xml");
// crear una ventana dlib
image_window win;
win.set_size(420, 380);
win.set_title("Face Landmark Detector :: Tutor de Programacion");
// detecta los landmarks
shape_predictor pose_model;
deserialize("files/data/shape_predictor_68_face_landmarks.dat") >> pose_model;
const rgb_pixel color(255, 255, 0);
const cv::Scalar scalar(255, 255, 0);
// Grab and process frames until the main window is closed by the user.
while (!win.is_closed())
{
// Grab a frame
cv::Mat temp, frame_gray;
// detener so no se ha podido leer la camara
if (!cap.read(temp)) {
break;
}
// convertir a grises y equalizar histograma
cv::cvtColor(temp, frame_gray, CV_BGR2GRAY);
cv::equalizeHist(frame_gray, frame_gray);
std::vector<cv::Rect> faces;
std::vector<dlib::rectangle> facesRect;
// detectar los rostros
face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, cv::Size(50, 50));
// guardar la region en donde se encuentra la cara
for (cv::Rect& rc : faces) {
cv::rectangle(temp, rc, scalar, 1, cv::LINE_AA);
facesRect.push_back(rectangle(rc.x, rc.y, rc.x + rc.width, rc.y + rc.height));
}
// Turn OpenCV's Mat into something dlib can deal with. Note that this just
// wraps the Mat object, it doesn't copy anything. So cimg is only valid as
// long as temp is valid. Also don't do anything to temp that would cause it
// to reallocate the memory which stores the image as that will make cimg
// contain dangling pointers. This basically means you shouldn't modify temp
// while using cimg.
cv_image<bgr_pixel> cimg(temp);
// guarda los puntos obtenidos
std::vector<image_window::overlay_circle> points;
std::vector<full_object_detection> detects;
std::vector<cv::Point> nariz;
// detectar los landmarks para cada rostro encontrado
for (unsigned long i = 0; i < facesRect.size(); ++i) {
full_object_detection shape = pose_model(cimg, facesRect[i]);
detects.push_back(shape);
// guardar las coordenada de cada landmark
for (unsigned int n = 0; n < shape.num_parts(); n++) {
point pt = shape.part(n);
points.push_back(image_window::overlay_circle(pt, 2, color));
// captura los puntos correspondientes al area de la nariz
if (n >= 28 && n < 36) {
nariz.push_back(cv::Point(pt.x(), pt.y()));
}
}
}
//// dibujar la region de la nariz
//cv::Rect rectNariz = cv::boundingRect(nariz);
//cv::rectangle(temp, rectNariz, cv::Scalar(0, 0, 255));
// Display it all on the screen
win.clear_overlay();
win.set_image(cimg); // establecer la imagen en la ventana
win.add_overlay(points); // mostrar los puntos de los landmarks
win.add_overlay(render_face_detections(detects)); // dibujar los landmarks como lineas
}
}
catch (serialization_error& e) {
cout << endl << e.what() << endl;
}
catch (exception& e) {
cout << e.what() << endl;
}
}
Instrucciones para compilar
- Primero descarga el proyecto y luego descomprime opencv-dlib-landmark en una carpeta de tu preferencia.
- Luego descarga el modelo, lo descomprimes y lo guardas en la carpeta /opencv-dlib-landmark/data.
- Utiliza CMake para generar el proyecto para tú compilador preferido, debe tener soporte para C++ 11.
- Abre el proyecto, cambia a modo Release, compila y ejecuta, debes asegurarte de tener una cámara conectada.
Asumimos que ya tienes instalado OpenCV, la biblioteca Dlib está incluida en el proyecto y esta preparada para compilarse con el mismo.
Hola, Soy Sergio. Excelente página para empezar. He tenido problemas en la compilación, queda en 98% y sale error en "source.cpp", exactamente en CV_BGR2GRAY. Tengo OpenCV 3.0.0 beta instalado. ¿Alguna idea?
ResponderEliminarGracias
Es difícil saber el problema con poca información, recuerda que Dlib requiere un compilador con soporte para C++ 11, otra cosa es que las pruebas las he realizado con OpenCV 3.3.0.
EliminarHola, como compilaste Dlib
ResponderEliminarLa librería DLib esta incluida dentro del proyecto y la misma se compila usando CMake, puedes ver el archivo CMakeList.txt que contiene el script de compilación.
EliminarGracias por tu respuesta, ya lo solucioné esa parte, perdón por ser preguntón. Ahora que estoy ejecutando el archivo de ejemplo dlib/examples/face_landmark_detection_ex.cpp me aparece que no se encuentra el archivo PDB o no se puede abrir, ¿podrás saber a que se debe?. Gracias
Eliminar