Reconocimiento Facial

OpenCV cuenta con la clase FaceRecognizer para facilitarnos el reconocimiento de un rostro presente en una imagen, haremos uso de los clasificadores en cascada para detectar el rostro y luego lo identificaremos con FaceRecognizer usando alguno de los algoritmos disponibles como: EigenFace, FisherFace y LBPH. Nuestro proyecto de Reconocimiento Facial usara LBPH.

1. Detección del Rostro

Utilizaremos uno de los clasificadores en cascada para detectar el rostro presente el la captura de la webcam, funcionara para rostros frontales, también necesitamos detectar los ojos, apliquemos los conceptos aprendidos en Detección de rostros y Detección de Ojos.


Este método será el encargado de obtener las coordenadas del rostro y ambos ojos, devolverá true si los encuentra, esto nos indicara que tenemos un rostro válido para procesar.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
bool GetFaceAndEyes(Mat& frame, Rect& rostro, Rect& lEye, Rect& rEye)
{
 vector<Rect> faces;
 faceDetector.detectMultiScale(frame, faces, 1.1, 3, CASCADE_FIND_BIGGEST_OBJECT | CASCADE_DO_ROUGH_SEARCH);

 if(faces.size() == 1)
 {
  rostro = faces[0];
  Mat face = frame(rostro);

  int leftX = cvRound(face.cols * EYE_SX);
  int topY = cvRound(face.rows * EYE_SY);
  int widthX = cvRound(face.cols * EYE_SW);
  int heightY = cvRound(face.rows * EYE_SH);
  int rightX = cvRound(face.cols * (1.0-EYE_SX-EYE_SW));

  Mat topLeftOfFace = face(Rect(leftX, topY, widthX,heightY));
  Mat topRightOfFace = face(Rect(rightX, topY, widthX, heightY));

  vector<Rect> lEyeR, rEyeR;

  lEyeDetector.detectMultiScale(topLeftOfFace, lEyeR, 1.1, 3, CASCADE_DO_ROUGH_SEARCH);
  lEyeDetector.detectMultiScale(topRightOfFace, rEyeR, 1.1, 3, CASCADE_DO_ROUGH_SEARCH);

  if(lEyeR.size() == 1 && rEyeR.size() == 1)
  {
   lEye = lEyeR[0];
   rEye = rEyeR[0];

   lEye.x += leftX;
   lEye.y += topY;

   rEye.x += rightX;
   rEye.y += topY;

   return true;
  }
 }

 return false;
}

2. Procesar el Rostro

Una vez tengamos las coordenadas válidas de los ojos y el rostro procedemos a recortarlo y alinearlo, aplicando transformaciones de rotación, escala y luego recorte, también convertimos la imagen a escala de grises.


Esta función devuelve el rostro alineado, recortado y convertido a escala de grises, indicamos como parámetros las coordenadas obtenidas con la función anterior.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void CropFace(Mat& face, Mat& warped, Rect leftEye, Rect rightEye)
{
 Point left = Point(leftEye.x + leftEye.width/2, leftEye.y + leftEye.height/2);
 Point right = Point(rightEye.x + rightEye.width/2, rightEye.y + rightEye.height/2);
 Point2f eyesCenter = Point2f( (left.x + right.x) * 0.5f, (left.y + right.y) * 0.5f );

 // Get the angle between the 2 eyes.
 double dy = (right.y - left.y);
 double dx = (right.x - left.x);
 double len = sqrt(dx*dx + dy*dy);
 double angle = atan2(dy, dx) * 180.0 / CV_PI;

 // Hand measurements shown that the left eye center should ideally be at roughly (0.19, 0.14) of a scaled face image.
 const double DESIRED_RIGHT_EYE_X = (1.0f - DESIRED_LEFT_EYE_X);

 // Get the amount we need to scale the image to be the desired fixed size we want.
 double desiredLen = (DESIRED_RIGHT_EYE_X - DESIRED_LEFT_EYE_X) * FaceWidth;
 double scale = desiredLen / len;

 // Get the transformation matrix for rotating and scaling the face to the desired angle & size.
 Mat rot_mat = getRotationMatrix2D(eyesCenter, angle, scale);

 // Shift the center of the eyes to be the desired center between the eyes.
 rot_mat.at<double>(0, 2) += FaceWidth * 0.5f - eyesCenter.x;
 rot_mat.at<double>(1, 2) += FaceHeight * DESIRED_LEFT_EYE_Y - eyesCenter.y;

 warped = Mat(FaceHeight, FaceWidth, CV_8U, Scalar(128));

 warpAffine(face, warped, rot_mat, warped.size());
}

3. Reconocimiento del Rostro

En este punto ya tenemos el rostro que vamos a usar para entrenar nuestro algoritmo, OpenCV cuenta con implementaciones de los algoritmos: Eigenfaces Principal Component Analysis (PCA), Fisherfaces Linear Discriminant Analysis (LDA) y LBPH Local Binary Pattern Histograms, inventado por Ahonen, Hadid and Pietikäinen en el año 2004.

Para este proyecto utilizaremos LBPH, el método createLBPHFaceRecognizer() nos devolverá un objeto de tipo FaceRecognizer que implementa el algoritmo LBPH, del mismo modo podemos usar createFisherFaceRecognizer() o createEigenFaceRecognizer().

3.1 Fase de Entrenamiento


Para agregar un rostro a FaceRecognizer haremos varias capturas del mismo, teniendo en cuenta que debemos capturar rostros diferentes de la misma persona, cuanto más rostros capturemos mejor funcionara nuestro programa, procuremos capturas caras con diferentes gestos, iluminación y rotaciones hacia la derecha o izquierda.


Cada captura la almacenaremos en una Lista de Objetos Mat, cuando tengamos suficientes imágenes iniciamos el entrenamiento llamando al método train de facerecognizer pasamos como parámetros la lista de caras capturadas y una lista de objetos int que representan un identificador para el rostro, llamamos a update para agregar mas rostros.

1
2
3
4
Ptr<FaceRecognizer> model = createLBPHFaceRecognizer();
vector<Mat> rostros;
vector<int> ids;
map<int , string> names;

Funcionamiento de la aplicación en la fase de entrenamiento (solo si se ha detectado un rostro válido):
  • Para iniciar el entrenamiento presionamos la tecla E, esto cuando deseemos agregar una persona a la base de datos.
  • Para recolectar un rostro, presionamos la tecla A, cuando estamos en modo entrenamiento podemos recolectar la cantidad de caras que deseemos.
  • Para agregar los rostros capturados de la persona que añadiremos a la base de datos presionamos la tecla T, debimos haber capturado por lo menos un rostro. La consola nos pedirá un nombre para identificar el rostro, lo tecleamos y presionamos ENTER al hacer esto volveremos al modo reconocimiento.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//si el modo entrenamiento esta activo
if(entrenado)
{
    Mat nface = CropFace(frame(face), lEye, rEye);
    //Agregar el rostro y su numero id a las correspondientes listas
    if(agregarRostro)
    {
        rostros.push_back(nface);
        ids.push_back(identificador);
        agregarRostro = false;
    }
    //entrenar el modelo con los rostros capturados
    if(entrenar && rostros.size() >= 1)
    {
        model-&gt;update(rostros, ids);
        entrenar = agregarRostro = entrenado = false;
        rostros.clear();
        ids.clear();
        identificador += 1;
    }
}   

3.2 Fase de Reconocimiento


La aplicación estará permanentemente en fase de reconocimiento, siempre que no este en modo entrenamiento, se intentara identificar el rostro que se haya detectado y se mostrara el resultado en la parte de abajo de la imagen.

Para la identificación del rostro FaceRecognizer cuenta con el método predict(face),  le pasamos el rostro que deseamos identificar, este nos devolverá el id correspondiente al rostro.

Podemos usar el método predict(face, id, confidence), id donde se almacenara el identificador, confidence es un valor matemático que indica el nivel de similitud entre los rostros de modo que entre más cercano a cero más confiable es nuestra identificación. 

Estableciendo un valor para threshold indicamos a FeceRecognizer que cualquier identificación con un confidence mayor que threshold sea tomado como desconocido.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int id = -1;
double confidence = 0.0;
Mat nface;
CropFace(copyFrame(face), nface, lEye, rEye);
//calquier confidence mayor que threshold id = -1
//redicir o aumentar este valor segun nos convenga
model->set("threshold", 70);
model->predict(nface, id, confidence);
if(id >= 0)
{
    string msg = names[id] + " : " + to_string((int)confidence);
    DrawMarker(frame, face, msg , 20);
} 
else DrawMarker(frame, face, "???", 20);



Comentarios

  1. Hola, he seguido tu tutorial, incluso me he descargado el código y al compilarlo me arroja los siguientes errores. Podrías ayudarme?

    Reconocimiento.cpp:210: error: invalid initialization of non-const reference of type ‘cv::Mat&’ from a temporary of type ‘cv::Mat’
    Reconocimiento.cpp:80: error: in passing argument 1 of ‘void CropFace(cv::Mat&, cv::Mat&, cv::Rect, cv::Rect)’
    Reconocimiento.cpp:254: error: invalid initialization of non-const reference of type ‘cv::Mat&’ from a temporary of type ‘cv::Mat’
    Reconocimiento.cpp:80: error: in passing argument 1 of ‘void CropFace(cv::Mat&, cv::Mat&, cv::Rect, cv::Rect)’
    Reconocimiento.cpp:263: error: ‘to_string’ was not declared in this scope

    ResponderEliminar
    Respuestas
    1. Este comentario ha sido eliminado por el autor.

      Eliminar
  2. He solucionado el problema, me gustaria realizar unos cambios para no estar entrenando cada que se abre el programa, sino tener una base de datos predefinidos y nomas utilizar el programa para identificar... En donde se guardan los rostros de entrenamiento? . Saludos

    ResponderEliminar
    Respuestas
    1. hola colega yo tengo el mismo problema al compilar ....como lo solucionaste ...me podrias ayudar xfa

      Eliminar
    2. Como hiciste?????????????????????????????????????????????????????'
      es que me pasa lo mismo

      Eliminar
    3. como lo hiciste a todos nos pasa lo mismo

      Eliminar
  3. Hola! Tengo los mismos errores. Me podrias ayudar CANGUHACK?

    ResponderEliminar
    Respuestas
    1. No estoy muy seguro del origen de estos errores, mi experiencia me dice que tal vez se deba a que el compilador que estoy usando es MSVC y ustedes quizás usen otro.

      Eliminar
  4. Una ayuda por favor, me sale error en lo siguiente:
    1>..\..\..\..\..\Desktop\Reconocimiento facial.cpp(256): error C2668: 'std::to_string' : llamada ambigua a una función sobrecargada


    en este pedazo de codigo

    string msg = names[id] + " : " + to_string((int)confidence);

    ResponderEliminar
    Respuestas
    1. to_string((int)confidence) convierte el valor confidence a string, si este método no funciona puedes usar std::stringstream o cualquier otra forma que te permita la conversión de int a string;

      Eliminar
    2. Como asi? podrias ponerlo exactamente como debe ir porfa ...tengo el mismo error

      Eliminar
    3. Lo primero es agregar el encabezado sstream :

      #include <sstream>

      Segundo reemplazar el código con error por este:

      std::stringstream txt;
      txt << confidence;
      string msg = names[id] + " : " txt.str();

      Eliminar
    4. Nuevamente he cometido un error, la ultima linea del código debería ser:
      string msg = names[id] + " : " + txt.str();

      Eliminar
  5. Como seria con algoritmos geneticos?

    ResponderEliminar
  6. Hola estoy trabajando con openCV me gustria que me ayudarias a como guardar una imagen en un archivo XML o YAML para tener una base de datos con los rostros entrenados y no entrenar cada que inicie la aplcacion, gracias.

    ResponderEliminar
    Respuestas
    1. Aquí puedes ver como guardar Mat en yaml:
      http://acodigo.blogspot.com/2014/07/persistencia-de-datos-yml-xml.html

      Eliminar
  7. Saludos, me he descargado tu código de Reconocimiento facial y al momento de compilarlo me salen los siguientes errores::::

    ---Reconocimiento facial.cpp:207:48: error: invalid initialization of non-const reference of type 'cv::Mat&' from an rvalue of type 'cv::Mat'
    CropFace(copyFrame(face), nface, lEye, rEye);
    ---Reconocimiento facial.cpp:77:6: error: in passing argument 1 of 'void CropFace(cv::Mat&, cv::Mat&, cv::Rect, cv::Rect)'
    void CropFace(Mat& face, Mat& warped, Rect leftEye, Rect rightEye)
    ---Reconocimiento facial.cpp:251:48: error: invalid initialization of non-const reference of type 'cv::Mat&' from an rvalue of type 'cv::Mat'
    CropFace(copyFrame(face), nface, lEye, rEye);
    ---Reconocimiento facial.cpp:77:6: error: in passing argument 1 of 'void CropFace(cv::Mat&, cv::Mat&, cv::Rect, cv::Rect)'
    void CropFace(Mat& face, Mat& warped, Rect leftEye, Rect rightEye)
    ---Reconocimiento facial.cpp:77:6: error: 'to_string' was not declared in this scope
    string msg = names[id] + " : " + to_string((int)confidence);

    Te agradeceria que me ayudaras a solucionarlo....!!!!

    ResponderEliminar
  8. Para los que tengan el error:
    error: invalid initialization of non-const reference of type 'cv::Mat&' from an rvalue of type 'cv::Mat'
    CropFace(copyFrame(face), nface, lEye, rEye);

    o

    In function ‘int main()’:
    error: inicialización inválida de una referencia que no es constante de tipo ‘cv::Mat&’ desde un temporal de tipo ‘cv::Mat’
    error: al pasar el argumento 1 de ‘void CropFace(cv::Mat&, cv::Mat&, cv::Rect, cv::Rect)’

    agregar const antes del primer parámetro en la declaración de la función CropFace

    o en otras palabras, sustituir:
    void CropFace(Mat& face, Mat& warped, Rect leftEye, Rect rightEye)

    por

    void CropFace(const Mat& face, Mat& warped, Rect leftEye, Rect rightEye)


    Y para el error de string:
    error: 'to_string' was not declared in this scope
    string msg = names[id] + " : " + to_string((int)confidence);

    o

    error: ‘to_string’ no se declaró en este ámbito

    sustituir (como ya lo explico Carmelo Marin A):
    string msg = names[id] + " : " + to_string((int)confidence);

    por

    std::stringstream txt;
    txt << confidence;
    string msg = names[id] + " : " + txt.str();

    y agregar al inicio del archivo:
    #include


    Saludos

    ResponderEliminar
  9. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  10. Hola,
    sabes que tengo un error con la declaración:
    Ptr model = createLBPHFaceRecognizer();
    No me encuentra FaceRecognizer y createLBPHFaceRecognizer. Estoy con la ultima version de OpenCV y estoy utilizando Visual studio 2013, por lo demas del codigo me funciona bien menos esta linea de codigo.
    Espero que me puedas ayudar,
    Saludos,

    ResponderEliminar
    Respuestas
    1. Hola, amigo si pudiste resolver el error? Tengo el mismo problema,.. Agradezco respuesta.

      Saludos

      Eliminar
  11. que tal broter estuve checando tus tutos y son los mejores en la web pero me gustaria poder tener mas comunicacion directa via correo para que me apolles devido que me metere de lleno devido a un proyecto universitario , mi correo es charly290909@hotmail.com gracias

    ResponderEliminar
  12. Buenas, he estado probando tu código sobre Ubuntu y me he encontrado con un problemilla. Lo primero, tuve que cambiar lo que ya se ha hablado en comentarios anteriores. Hasta ahí sin problemas, lo nuevo viene cuando al ejecutarlo, no hace nada cuando se le pulsan las teclas. No reconoce ningún comando. Y lo más extraño es que cuando lo ejecutas por segunda vez, deja completamente frito al ordenador. Y hay que reiniciar. Tú lo habías probado en Ubuntu? Yo lo he compilado así: 'g++ RecFac.cpp `pkg-config --libs --cflags opencv` -std=c++11'.

    Muchas gracias!!

    ResponderEliminar
    Respuestas
    1. Prueba con este shell, es el que uso para compilar rapido en opencv y me pudo compilar este ejemplo.

      #!/bin/bash
      echo "compiling $1"
      if [[ $1 == *.c ]]
      then
      gcc -ggdb `pkg-config --cflags opencv` -o `basename $1 .c` $1 `pkg-config --libs opencv`;
      elif [[ $1 == *.cpp ]]
      then
      g++ -ggdb `pkg-config --cflags opencv` -o `basename $1 .cpp` $1 `pkg-config --libs opencv`;
      else
      echo "Please compile only .c or .cpp files"
      fi
      echo "Output file => ${1%.*}"

      Eliminar
  13. Hola, me estan ayudando mucho estos tutoriales en un proyecto que estoy desarrollando. Quiero mover un laser a la posicion en la que está detectanto. Estoy intentando poner un if para mover los ejes en el caso de que este detectanto. por ejemplo if !detect o si detect == 0. No logro obtener si esta detectando rostros ni el numero. ¿Podrias decirme como poner un if si esta detectando en ese momento?

    ResponderEliminar
    Respuestas
    1. Al final lo vi en el tutorial de deteccion de ojos, puse así:
      if (!rect.size() > 0), ¿sería la forma correcta o habría otra forma mejor?

      Eliminar
  14. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  15. Saludos, Puedes realizar el ejemplo en java? Pues me tome la tarea de traducir éste ejemplo a lenguaje java, pero hay funciones que no son similares a las nombradas en las librerias de java. Gracias.

    ResponderEliminar
  16. hola, primero te felicito por tus tutoriales no ha habido errores en el proceso , necesitaba ayuda con mi proyecto universitario ya que necesito hacer un entrenador que reconozca las manos y despues detectar las respectivas señas que se hagan con las manos, he estdo chequeando algoritmos, asi como en el reconocedor facial pero no he encontrado mucha informacion.

    ResponderEliminar
  17. he estado hablando con otra persona acerca del tema, me dice que guarde la trayectoria de los centroides para guardar la trayctoria como referencia

    ResponderEliminar
  18. ha sido de gran ayuda tus tutoriales aunque no mencionas las herramientas para crear los clasificadores y crear los archivos xml para el id del codigo de genero

    ResponderEliminar
  19. hola, sería de gran ayuda que pusieran el código del reconocimiento facial en GitHub junto con los demás ejemplos, ya que no puedo acceder al sitio actual( https://db.tt/vsYOyBBx)

    ResponderEliminar
    Respuestas
    1. El enlace está reparado, ya puedes descargar el proyecto de reconocimiento facial, gracias por avisarme.

      Eliminar

Publicar un comentario

Entradas populares de este blog

Conectar SQL Server con Java

Detección de rostros

Instalar OpenCV para Python en Windows