Muchas de las soluciones clásicas de reconocimiento facial están compuestas de los siguientes elementos:
- 1Un detector de rostros.
- 2Un descriptor de rostros (es decir, un algoritmo que convierta cada imagen de un rostro en un vector).
- 3Un 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 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:
- 1FaceNet toma una imagen de un rostro y produce un embedding (es decir, un vector) de 128 elementos que cuantifican dicho rostro.
- 2Imágenes de un mismo rostro producen embeddings similares.
- 3Imágenes de rostros de dos personas diferentes producen embeddings muy diferentes entre sí.
- 4No 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 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:
- 1Un Ancla, que es la identidad actual.
- 2Una imagen Positiva, que contiene el rostro de la misma persona en el Ancla.
- 3Una 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:
- 1Los embeddings del Ancla y la imagen Positiva estén cerca el uno del otro.
- 2El embedding de la imagen Negativa esté lejos de los dos anteriores.
Fascinante, ¿no crees?
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:
¡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:
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:
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:
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:
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:
- 1Un algoritmo para convertir imágenes en vectores (en este caso, usamos una versión pre-entrenada de FaceNet).
- 2Un 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:
- 1Dos vectores correspondientes a una misma identidad se encuentran cerca el uno del otro.
- 2Dos 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! ??