julio 2, 2021 10:00 am

Jesús

Muchas de las soluciones clásicas de reconocimiento facial están compuestas de los siguientes elementos:

  1. 1
    Un detector de rostros.
  2. 2
    Un descriptor de rostros (es decir, un algoritmo que convierta cada imagen de un rostro en un vector).
  3. 3
    Un clasificador de machine learning que pueda identificar a qué individuo corresponde cada rostro (previamente convertido en vector).

Seguramente habrás notado que la receta anterior aplica a las dos instancias concretas de reconocedores faciales que hemos estudiado hasta ahora: Patrones Locales Binarios (LBP) y Eigenfaces.

En el artículo de hoy repetiremos estos pasos, solo que lo haremos de la mano de deep learning. Como sabrás, una de las combinaciones más poderosas que existen en el mundo de computer vision (y en IA, en general) es deep learning + machine learning.

En otras palabras, si usamos las excelentes capacidades descriptivas de una red neuronal (deep learning) para convertir imágenes en vectores (a los que llamaremos embeddings o features), que luego utilizamos para entrenar un clasificador (machine learning), obtenemos un reconocedor altamente capaz.

Al final de este artículo habrás aprendido:

  • Cómo funciona, a alto nivel, FaceNet, la red de extracción de embeddings.
  • Cómo utilizar FaceNet en OpenCV.
  • Cómo entrenar un reconocedor facial sobre CALTECH Faces usando FaceNet en OpenCV y un SVM en scikit-learn.

Cómo Funciona FaceNet en OpenCV

La red que usaremos para convertir cada rostro en un embedding proviene de la publicación FaceNet: A Unified Embedding for Face Recognition and Clustering, y aunque sin duda se trata de un paper muy interesante, revisar cada detalle de FaceNet escapa del alcance de este post.

Sin embargo, lo que debemos recordar es lo siguiente:

  1. 1
    FaceNet toma una imagen de un rostro y produce un embedding (es decir, un vector) de 128 elementos que cuantifican dicho rostro.
  2. 2
    Imágenes de un mismo rostro producen embeddings similares.
  3. 3
    Imágenes de rostros de dos personas diferentes producen embeddings muy diferentes entre sí.
  4. 4
    No tenemos que entrenar FaceNet sobre nuestro conjunto de datos, sino que podemos usar una versión ya entrenada directamente en OpenCV (¡viva transfer learning!).

Los cuatro puntos anteriores resumen lo necesario para poder extraer embeddings con confianza. No obstante, si quieres saber cómo la red calcula los embeddings de 128 elementos, lee la siguiente subsección.

Cómo FaceNet Calcula los Embeddings Faciales

La red se entrena sobre tres imágenes a la vez: Dos de ellas corresponden a la misma persona (Will Ferrel), y la tercera a un individuo diferente (Chad Smith). La idea es que la red aprenda a crear vectores de 128 elementos tales que las dos imágnes correspondientes a la misma persona produzcan vectores cercanos, mientras que el vector del segundo individuo quede lo más lejos posible de los otros dos.

La respuesta a esta interrogante se encuentra en el proceso de entrenamiento de la red, particularmente en cómo está estructurada la data de entrada, así como la función de pérdida.

Cada lote de data que se le pasa a la red de entrenamiento se compone de tres imágenes:

  1. 1
    Un Ancla, que es la identidad actual.
  2. 2
    Una imagen Positiva, que contiene el rostro de la misma persona en el Ancla.
  3. 3
    Una imagen Negativa, que contiene el rostro de otra persona.

El punto está en que la persona en el Ancla y la imagen Positiva es la misma, mientras que la identidad de la persona en la imagen Negativa es diferente.

Así, la función de pérdida lo que hace es ajustar los parámetros de la red para producir embeddings tales que:

  1. 1
    Los embeddings del Ancla y la imagen Positiva estén cerca el uno del otro.
  2. 2
    El embedding de la imagen Negativa esté lejos de los dos anteriores.

Fascinante, ¿no crees? 

CALTECH Faces

caltech faces

Muestra de CALTECH Faces.

Para entrenar nuestro reconocedor facial, usaremos uno de los conjuntos de datos canónicos para este tipo de tareas: CALTECH Faces, el cual tiene estas características:

  • Tiene aproximadamente 450 imágenes.
  • En él se hallan representadas alrededor de 27 personas.
  • Cada sujeto fue fotografiado en condiciones variables de luminosidad, escenario, y con diversas expresiones faciales.

¡ATENTO!

No tienes que descargarte el conjunto de datos directamente. Más bien, baja el archivo comprimido asociado a este post en la siguiente sección, puesto que en él incluí una versión curada de los datos, cuyas imágenes fueron organizadas en directorios con nombres falsos para cada persona que aprenderemos a reconocer.

Creación del Entorno de Desarrollo con Anaconda

Primero, veamos la estructura del proyecto:

Estructura del proyecto

  • En caltech_faces tenemos una serie de directorios nombrados como las personas cuyas fotos contienen.
  • datasmarts/extract_embeddings.py implementa el código para detectar y convertir los rostros en embeddings usando FaceNet.
  • datasmarts/train_model.py toma los embeddings producidos por datasmarts/extract_embeddings.py, y entrena un clasificador (es decir, un reconocedor) usando scikit-learn.
  • datasmarts/recognize.py utiliza el modelo entrenado datasmarts/train_model.py para llevar a cabo reconocimiento facial sobre nuevas imágenes.
  • La carpeta resources contiene los archivos necesarios para instanciar el detector de rostros en OpenCV, y FaceNet, la red de extracción de embeddings. Usaremos ambas en la siguiente sección.

Para crear el ambiente de Anaconda, ejecuta el siguiente comando:

conda env create -f env.yml

Dicha instrucción habrá creado un ambiente llamado opencv-face-recognition, correspondiente al siguiente archivo de configuración:

Para activar tu nuevo ambiente, corre esto:

conda activate opencv-face-recognition

The end. Sigamos adelante 🙂

Reconocimiento Facial con FaceNet y Machine Learning

En esta sección implementaremos tres scripts, el primero de ellos siendo datasmarts/extract_embeddings.py. Abre el archivo e inserta estas líneas para importar las dependencias del programa:

Ahora, definimos los argumentos de entrada del script:

  • -i/--input: Ruta al directorio con las imágenes a convertir en embeddings con FaceNet,
  • -e/--embeddings: Ruta donde guardaremos los embeddings generados
  • -d/--detector: Ruta a los archivos de configuración del detector de rostros en OpenCV.
  • -m/--model: Ruta al archivo correspondiente a FaceNet, la red productora de embeddings, que instanciaremos en OpenCV.
  • -c/--confidence: Probabilidad mínima para no descartar una detección.

Cargamos la red de detección facial. Si gustas aprender más al respecto, lee este artículo:

También cargamos el extractor de embeddings (es decir, FaceNet). Fíjate que esta red fue entrenada con PyTorch:

Listamos las rutas a las imágenes en el conjunto de datos, las cuales procesaremos en breve:

Recorreremos iterativamente cada ruta en image_paths, extrayendo de la misma el nombre de la persona. Así mismo, cargaremos la imagen en memoria, y la pasaremos al detector de rostros para hallar las caras:

Luego, procesamos las detecciones. Fíjate que nos quedamos solo con aquellas que tengan una probabilidad mínima de -c/--confidence. Asimismo, descartamos toda detección de dimensiones menores a 20x20 para evitar falsos positivos.

Si un rostro cumple con las dos condiciones anteriores, lo pasamos por FaceNet (embedder) para obtener el embedding de 128 elementos. Cerramos el ciclo añadiendo tanto nombre de la persona como su embedding característico a las listas correspondientes (known_names y known_embeddings, respectivamente):

Finalmente, guardamos ambas listas en un diccionario, y lo serializamos en disco:

Para correr el programa, ejecuta este comando:

python datasmarts/extract_embeddings.py -i caltech_faces -e output/embeddings.pickle -d resources -m resources/openface.nn4.small2.v1.t7

En la consola veremos estos mensajes que constatan que todas las imágenes fueron procesadas correctamente y, por tanto, los embeddings de las mismas se guardaron exitosamente en disco:

Cargando el detector de rostros...
Cargando el feature extractor...
Cargando las imágenes...
Procesando imagen 1/447
Procesando imagen 2/447
Procesando imagen 3/447
…
Procesando imagen 445/447
Procesando imagen 446/447
Procesando imagen 447/447
Serializando 447 vectores.

Es hora de entrenar el clasificador, así que abre el archivo datasmarts/train_model.py y, como de costumbre, empieza importando los módulos necesarios:

Definamos los argumentos de entrada del programa:

  • -e/--embeddings: Ruta a los embeddings extraídos con datasmarts/extract_embeddings.py.
  • -r/--recognizer: Ruta donde se guardará el clasificador (reconocedor) una vez entrenado.
  • -l/--label-encoder: Ruta donde guardaremos el label encoder.

Cargamos los embeddings:

Usando un LabelEncoder(), convertimos las etiquetas (es decir, los nombres de las personas) en números enteros:

Entrenamos un SVC() sobre los embeddings:

Guardamos el modelo en disco:

Guardamos el LabelEncoder() en disco:

Para entrenar el modelo, corremos este comando:

python datasmarts/train_model.py -e output/embeddings.pickle -r output/model.pickle -l output/le.pickle

Y esto es lo que veremos en la consola:

Cargando los embeddings...
Codificando las etiquetas...
Entrenando el modelo...

El último script, datasmarts/recognize.py, implementa la lógica para reconocer rostros en nuevas imágenes. Abre el archivo e inserta estas líneas:

Ahora definamos los argumentos del programa:

  • -i/--image: Ruta a la imagen de entrada.
  • -d/--detector: Ruta a los archivos de configuración del detector de rostros en OpenCV.
  • -m/--model: Ruta al archivo correspondiente a FaceNet, la red productora de embeddings, que instanciaremos en OpenCV.
  • -c/--confidence: Probabilidad mínima para no descartar una detección.
  • -r/--recognizer: Ruta al reconocedor de rostros entrenado sobre los embeddings.
  • -l/--label-encoder: Ruta al label encoder.

Cargamos la red de detección facial. Si gustas aprender más al respecto, lee este artículo:

También cargamos el extractor de embeddings:

Cargamos el reconocedor:

Y el label encoder:

Cargamos la imagen de entrada:

Corremos el detector facial sobre la imagen con el fin de hallar rostros:

Procesamos solo aquellos rostros con una probabilidad lo suficientemente alta. Con base a las coordenadas arrojadas por el detector, extraemos la región de interés asociada a la cara de la persona:

Si se trata de una cara lo suficientemente grande (dimensiones mayores a 20x20 píxeles), entonces la convertimos en un embeddings de 128 elementos, que luego pasamos por el reconocedor para obtener una predicción sobre la identidad del individuo:

Mostramos la imagen original con los reconocimientos faciales dibujados en el extracto anterior:

Podemos correr nuestro programa sobre una imagen de prueba, así:

python datasmarts/recognize.py -i "caltech_faces/Oscar Pate/image_0243.jpg" -d resources -m resources/openface.nn4.small2.v1.t7 -r output/model.pickle -l output/le.pickle

En la pantalla verás lo siguiente:

Gran resultado, ¿no crees? ?

Resumen

En el presente artículo aprendimos a explotar y combinar el poder de las redes neuronales, así como machine learning, para dar vida a un muy buen reconocedor facial.

Como de costumbre en el contexto del reconocimiento de rostros, nuestra solución se constituye de los siguientes dos ingredientes:

  1. 1
    Un algoritmo para convertir imágenes en vectores (en este caso, usamos una versión pre-entrenada de FaceNet).
  2. 2
    Un clasificador de machine learning entrenado sobre los vectores obtenidos en el paso anterior.

En este punto es menester recordar que un clasificador por sí mismo no sirve de nada si la data no es buena. Más concretamente, si los vectores derivados de los rostros que queremos reconocer no son lo suficientemente distintos entre sí, dependiendo de la identidad de la persona, no hay milagro de machine learning que haga funcionar la solución.

Afortunadamente, FaceNet ha sido entrenada de manera tal que los vectores de 128 dimensiones que produce, cumplen con estas dos propiedades:

  1. 1
    Dos vectores correspondientes a una misma identidad se encuentran cerca el uno del otro.
  2. 2
    Dos vectores correspondientes a identidades diferentes, se encuentran alejados el uno del otro.

Al final, logramos crear un reconocedor facial sobre las caras de CALTECH Faces, a pesar de que FaceNet no fue entrenada en lo absoluto en este conjunto de datos. ¿Acaso no es genial transfer learning? ?

Espero que hayas disfrutado este post. Siéntete libre de descargar el código a continuación para que lleves a cabo tus propios experimentos:

¡Adiós! ??

Sobre el Autor

Jesús Martínez es el creador de DataSmarts, un lugar para los apasionados por computer vision y machine learning. Cuando no se encuentra bloggeando, jugando con algún algoritmo o trabajando en un proyecto (muy) cool, disfruta escuchar a The Beatles, leer o viajar por carretera.