agosto 11, 2021 10:00 am

Jesús

Si tienes un smartphone (lo más probable), seguramente te habrás tomado un sinfín de selfies. Calma, calma, no te estoy juzgando ?. 

Una de las características más llamativas de las cámaras frontales de los celulares modernos, es la capacidad de reconocer gestos y, en el caso de los selfies, usualmente una gran y amplia sonrisa suele ser la señal que el celular espera para capturar el momento. 

Si vemos esta curiosa cualidad a través de nuestro prisma científico, descubriremos que se trata de un mero detector de objetos, solo que el objeto en este caso, es una sonrisa o, más bien, un rostro sonriente. 

Genial, ¿no te parece? 

Más genial aún es implementar nuestro propio detector con las confiables herramientas que hemos usado en incontables ocasiones: OpenCV y TensorFlow

Pues, ¿qué crees? Es justo lo que haremos en este post.

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

  • Cómo entrenar un clasificador de sonrisas en TensorFlow.
  • Cómo usar un detector de rostros basado en el algoritmo Haar Cascade en OpenCV.
  • Cómo combinar los dos puntos anteriores para detectar sonrisas en videos o en tiempo real (mediante tu webcam).

Cómo Funciona un Detector de Sonrisas

Como mencionamos en la introducción, un detector de sonrisas no es más que una instancia particular de un detector de objetos, en el que el objeto a ser detectado es un rostro sonriente.

Por tal motivo, los componentes necesarios para construir un detector de objetos, así como el procedimiento, son los mismos.

Con el fin de refrescar nuestra memoria, aquí están los dos componentes principales de un detector, en este caso, de sonrisas:

  1. 1
    Un detector de rostros. En nuestro proyecto usaremos un Haar Cascade.
  2. 2
    Un clasificador de sonrisas. Para esto, entrenaremos una red neuronal convolucional implementada en TensorFlow.

El funcionamiento de nuestro detector se describe en la siguiente imagen:

Los pasos que el detector ejecuta son estos:

  1. 1
    Toma una imagen de entrada, y la pasa por el detector de rostros.
  2. 2
    Cada detección de rostros es pasada por el clasificador de sonrisas.
  3. 3
    Con base a las coordenadas del detector, y a la categoría arrojada por el clasificador (la persona está sonriendo o no), dibujamos los resultados en la imagen original.

Fácil, ¿verdad?

Creación del Entorno de Desarrollo con Anaconda

Empecemos observando la estructura del proyecto:

Estructura del proyecto

Estructura del proyecto

  • La carpeta data contiene las imágenes del conjunto de datos SMILES, que usaremos en breve para entrenar el clasificador.
  • datasmarts/train.py implementa la lógica de entrenamiento del clasificador de sonrisas.
  • En datasmarts/detect.py se encuentra el código del detector de sonrisas per se.
  • En resources tenemos el archivo de configuración del detector de rostros basado en Haar Cascade (haarcascade_frontalface_default.xml), y un video de ejemplo que usaremos para demostrar el funcionamiento del detector (smile.mp4).

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

conda env create -f env.yml

Dicha instrucción habrá creado un ambiente llamado tf-smile-detector, correspondiente a este archivo de configuración:

Activa el ambiente así:

conda activate tf-smile-detector

Excelente, prosigamos a la próxima sección.

Implementando un Detector de Sonrisas con OpenCV y TensorFlow

Lo primero que haremos será implementar el clasificador de sonrisas, por lo que tenemos que hablar brevemente del conjunto de datos que usaremos para dicha labor.

SMILES

Este conjunto de datos se compone, como su nombre sugiere, de imágenes de rostros de individuos sonriendo… o no.

En total, se compone de 13.165 imágenes en escala de grises, de dimensiones 64x64.

Otra de las características cruciales de SMILES es que se encuentra desbalanceado, ya que de las 13.165 imágenes que lo integran, solo 3.690 pertenecen a personas sonrientes, mientras que los individuos en las 9.475 restantes muestran otras expresiones.

¡ATENTO!

El desbalance de datos es un problema muy frecuente en machine learning. Aunque existen muchas formas de atacarlo, en este artículo nos centraremos en asignarle un mayor peso a la clase “sonriendo”, puesto que es la menos representada. Esto ocasionará que la red le de mayor preponderancia a esta categoría.

Habiéndonos familiarizado con los datos, pongámonos manos a la obra. Abre el archivo datasmarts/train.py e insertas estas líneas para importar las dependencias del programa:

Definimos la función auxiliar create_network(), la cual implementa la arquitectura de la CNN clasificadora:

Como notarás, se trata de un modelo muy simple, compuesto por dos convoluciones y dos capas densas, una de ellas siendo la de salida.

Ahora definimos otra función auxiliar, load_and_process_image(), la cual toma la ruta de una imagen en disco como entrada, y produce una representación en forma de arreglo de NumPy de la misma (ten en cuenta que cada imagen se convierte a escala de grises y se redimensiona para que tenga 28 píxeles de ancho):

Acto seguido, declaramos los argumentos del script:

  • -d/--dataset: Ruta al conjunto de datos SMILES.
  • -m/--model: Ruta donde se guardará la red una vez entrenada.

En el siguiente bloque de código vamos recopilando las imágenes y las etiquetas del dataset. En SMILES, las imágenes de personas sonrientes se encuentra en el subdirectorio positives; así, si una imagen contiene la palabra positives en su ruta, la catalogamos como “smile”, en caso contrario, como “not_smile”:

Procedemos a estandarizar las imágenes (es decir, sus píxeles oscilarán entre 0 y 1), y aplicamos one-hot encoding a las etiquetas:

Asignamos a cada clase un peso inversamente proporcional al número de instancias que tiene en el conjunto de datos:

Dividimos el conjunto de datos en 80% para entrenamiento, y 20% para pruebas:

Creamos y compilamos la red:

Ahora la entrenamos por 15 epochs:

Para evaluar su desempeño, corremos la red sobre los datos de prueba, y comparamos las predicciones con los datos reales mediante un reporte de clasificación:

Guardamos el modelo en disco:

Cerramos el programa creando un gráfico comparativo de las curvas de precisión (accuracy) y pérdida (loss), tanto en los subconjuntos de entrenamiento como de validación:

El próximo programa a implementar es el detector de rostros como tal. Abre el archivo datasmarts/detect.py y, nuevamente, inserta estas líneas para importar las dependencias del script:

Este bloque de código define los siguientes argumentos de entrada:

  • -c/--cascade: Ruta al archivo de configuración del detector de rostros basado en Haar Cascade.
  • -m/--model: Ruta al clasificador de sonrisas.
  • -v/--video: Ruta (opcional) al video donde queremos detectar las sonrisas. En caso de que no pasemos este argumento, usaremos la webcam.

Procedemos a cargar tanto el detector de rostros como el clasificador de sonrisas:

Después, creamos un objeto de tipo cv2.VideoCapture() para leer los frames de entrada del video, o de la webcam, dependiendo del caso:

Abrimos un ciclo perpetuo que se cerrará únicamente cuando el usuario presione la tecla Q, o se acaben los frames del video. Lo primero que hacemos en este loop es leer el próximo cuadro del video, y en caso de no haber leído ninguno, rompemos el ciclo (puesto que el video se acabó):

Si pudimos leer un frame, lo redimensionaremos para acelerar el procesamiento del mismo,  y también lo pasamos a escala de grises. Adicionalmente, creamos una copia sobre la cual pintaremos las detecciones más adelante:

Corremos el detector Haar Cascade sobre el frame:

A continuación, procesamos cada detección, usando las coordenadas para extraer la región donde se encuentra el rostro (roi). Pasaremos el rostro por el clasificador para determinar si la persona está sonriendo o no:

Si la persona está sonriendo, dibujaremos una carita feliz en color verde; en caso contrario, una cara seria en color amarillo:

Dibujamos el recuadro arrojado por el detector, así como la predicción del clasificador de sonrisas sobre la copia del frame:

Mostramos las detecciones, y validamos si el usuario presionó la tecla Q, en cuyo caso nos saldremos del ciclo:

Finalmente, liberamos los recursos utilizados hasta el momento:

Para correr el script de entrenamiento, ubícate en la raíz del proyecto y ejecuta este comando:

python datasmarts/train.py -d data -m model.hdf5

Una vez la red se haya entrenado, verás un reporte de clasificación como este:

Como podemos notar, a grandes rasgos la red obtuvo un 90% de accuracy, lo cual puede ser engañoso ya que, como recordarás, SMILES es un dataset desbalanceado. Una mejor métrica es el f1-score, donde vemos que para la categoría “smile”, la red obtuvo un valor de 0.83. No está mal, pero ciertamente el clasificador muestra una tendencia a clasificar rostros como “not_smile”, como demuestra su f1-score de 0.93.

De cualquier manera, estos valores son suficientemente buenos para el propósito didáctico de este post :).

También podemos analizar el desempeño del clasificador mediante este gráfico:

Observamos que, efectivamente, la red aprendió a distinguir entre un rostro que sonríe y uno que no, ya que las curvas de pérdida y exactitud en los subconjuntos de entrenamiento y validación siguen la misma trayectoria.

El paso 2 es correr el detector sobre un video de ejemplo, lo cual podemos hacer a través de este comando:

python datasmarts/detect.py -c resources/haarcascade_frontalface_default.xml -m model.hdf5 -v resources/smile.mp4

En el video podemos ver el resultado:

En líneas generales, nuestro detector hace un buen trabajo, especialmente si consideramos que el ángulo de la cámara es cenital, en vez de enteramente frontal. No obstante, vislumbramos unos cuantos falsos positivos, especialmente en la zona del cuello de la modelo, lo que puede ser síntoma de una de dos cosas (o de ambas):

  1. 1
    Los parámetros del detector de rostros no son los óptimos para este video.
  2. 2
    El clasificador de sonrisas debe mejorar.

Resumen

En el artículo de hoy implementamos un simple detector de sonrisas con la ayuda de OpenCV y TensorFlow.

Debido a que un detector de sonrisas es una instancia especial de un detector de objetos, enfocamos nuestra atención en el desarrollo de sus dos componentes principales:

  1. 1
    Un detector de rostros.
  2. 2
    Un clasificador de sonrisas.

En el caso del primero, optamos por una solución preempacada: Haar Cascade. Para el clasificador, desarrollamos una sencilla red convolucional, entrenada sobre el conjunto de datos SMILES.

Posteriormente, pusimos a prueba nuestra solución sobre un video de ejemplo, obteniendo un resultado satisfactorio, pero con posibilidades de mejora, especialmente en lo que a falsos positivos se refiere en cuanto a detecciones faciales.

¿Por qué no te descargas el código y lo pruebas, esta vez, usando tu webcam? ¿Cómo varían los resultados? ¿Crees que la iluminación y el ángulo de la cámara afectan el desempeño del detector? ¿Funciona igual de bien si usas un sombrero o anteojos? ¿Cómo podrías mejorarlo?

Me encantaría conocer tu opinión y tu experiencia al respecto, así que no dude en escribirme a jesus@datasmarts.net

¡Hasta pronto!

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.

Paso 2/2: Celebra tu NUEVO EMPLEO en Machine Learning ?

A %d blogueros les gusta esto: