😺🙀 Reconocimiento de emociones 😾😿 ( EigenFaces, FisherFaces, LBPH )| Python – OpenCV

Por Administrador

Te doy la bienvenida a un nuevo tutorial, en este voy a realizar el reconocimiento de emociones (enojo, felicidad, sorpresa y tristeza) usando Python y OpenCV, para ello usaremos los mismos métodos que habíamos empleado para el tutorial de reconocimiento facial, EigenFaces, FisherFaces y LBPH. ¡Empecemos!

CONTENIDO

  • Creando la base de datos con los rostros que reflejen las emociones
  • Entrenamiento de los reconocedores
  • Probando EigenFaces, FisherFaces y LBPH para el reconocimiento de emociones faciales
    • Resultados de EigenFaces en el reconocimiento de emociones
    • Resultados de FisherFaces en el reconocimiento de emociones
    • Resultados de Local Binary Pattern Histogram en el reconocimiento de emociones

Los pasos a realizar en este tutorial son similares a los realizados en el post de reconocimiento facial, es decir, primero vamos a capturar los rostros en los que se reflejen las emociones: enojo, felicidad, sorpresa y tristeza, cada uno de ellos en una carpeta diferente. Luego procederemos a entrenar los reconocedores con 3 métodos: EigenFaces, FisherFaces y LBPH. Finalmente vamos a probar el reconocimiento de cada una de las emociones entrenadas, pero además visualizaremos un emoji que represente la emoción reconocida.

NOTA: Te dejo mi repositorio de github en donde también podrás encontrar los programas que elaboraremos en este post.

Creando la base de datos con los rostros que reflejen las emociones

Figura 1: Representación del proceso de captura de rostros con distintas expresiones.

El programa que usaremos para poder crear la base de datos de los rostros que reflejen las emociones será el mismo que habíamos usado para el reconocimiento de rostros, se va a realizar unas pequeñas variaciones en cuanto a los nombres de las variables y la cantidad de rostros por cada emoción (para experimentar un poco). Entonces a continuación tendremos el script llamado capturandoRostros.py

import cv2
import os
import imutils

#emotionName = 'Enojo'
#emotionName = 'Felicidad'
#emotionName = 'Sorpresa'
emotionName = 'Tristeza'

dataPath = '.../Reconocimiento Emociones/Data' #Cambia a la ruta donde hayas almacenado Data
emotionsPath = dataPath + '/' + emotionName

if not os.path.exists(emotionsPath):
    print('Carpeta creada: ',emotionsPath)
    os.makedirs(emotionsPath)

cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)

faceClassif = cv2.CascadeClassifier(cv2.data.haarcascades+'haarcascade_frontalface_default.xml')
count = 0

while True:

    ret, frame = cap.read()
    if ret == False: break
    frame =  imutils.resize(frame, width=640)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    auxFrame = frame.copy()

    faces = faceClassif.detectMultiScale(gray,1.3,5)

    for (x,y,w,h) in faces:
        cv2.rectangle(frame, (x,y),(x+w,y+h),(0,255,0),2)
        rostro = auxFrame[y:y+h,x:x+w]
        rostro = cv2.resize(rostro,(150,150),interpolation=cv2.INTER_CUBIC)
        cv2.imwrite(emotionsPath + '/rotro_{}.jpg'.format(count),rostro)
        count = count + 1
    cv2.imshow('frame',frame)

    k =  cv2.waitKey(1)
    if k == 27 or count >= 200:
        break

cap.release()
cv2.destroyAllWindows()

Dado que este código ya lo habíamos tratado antes, pasaré a su explicación de forma breve.

Línea 1 a 3: Importamos las librerías.

Línea 5 a 8: Debemos descomentar cada una de estas líneas, comentando las demás, para que de este modo se vayan creando las carpetas con los rostros con expresiones de: enojo, felicidad, sorpresa y tristeza.

Línea 10 a 15: Con estas líneas crearemos una carpeta correspondiente al nombre de cada emoción (líneas 5 a 8) dentro de la carpeta ‘Data’.

Línea 17 a 20: Especificamos que vamos a realizar un video streaming, en faceClassif asignamos el detector de rostros que vamos a usar y finalmente establecemos un contador count en 0 para que se cuenten todos los rostros que se irán almacenando.

Línea 24 a 28: Se lee cada fotograma y este a su vez será redimensionado a 640 pixeles de ancho, se aplica escala de grises y para tener una imagen del fotograma intacto nos ayudamos de frame.copy() y asignamos a auxFrame.

Línea 30 a 38: En faces se almacenan todos los rostros detectados y para analizar cada uno de ellos pasaremos al for de la línea 32, en él dibujaremos un rectángulo que rodee cada rostro, luego recortaremos cada rostro rodeado, lo redimensionamos a 150 x 150 pixeles, para que todas las caras almacenadas tengan el mismo tamaño y almacenamos cada rostro en la carpeta creada en las líneas 5 a 15. Finalmente incrementamos en 1 el contador y visualizamos.

Línea 40 a 42: Establecemos que el proceso se detenga cuando una tecla sea presionada, en este caso ‘ESC’, o cuando se hayan almacenado 200 rostros (por cada emoción).

Línea 44 y 45: Indicamos que ha finalizado el video streaming y cerramos las ventanas visualizadas.

Figura 2: Carpetas creadas.

Entrenamiento de los reconocedores

Voy a realizar el entrenamiento con los tres métodos que OpenCV nos ofrece, para ello he querido optimizar el código anterior para entrenar todos los métodos en una sola ejecución, esto nos servirá para realizar pruebas sobre cada uno de ellos, sin embargo tu podrías solo usar el método que necesites. Veamos, creamos un script llamado entrenando.py

import cv2
import os
import numpy as np
import time

def obtenerModelo(method,facesData,labels):
    
    if method == 'EigenFaces': emotion_recognizer = cv2.face.EigenFaceRecognizer_create()
    if method == 'FisherFaces': emotion_recognizer = cv2.face.FisherFaceRecognizer_create()
    if method == 'LBPH': emotion_recognizer = cv2.face.LBPHFaceRecognizer_create()

    # Entrenando el reconocedor de rostros
    print("Entrenando ( "+method+" )...")
    inicio = time.time()
    emotion_recognizer.train(facesData, np.array(labels))
    tiempoEntrenamiento = time.time()-inicio
    print("Tiempo de entrenamiento ( "+method+" ): ", tiempoEntrenamiento)

    # Almacenando el modelo obtenido
    emotion_recognizer.write("modelo"+method+".xml")

dataPath = '.../Reconocimiento Emociones/Data' #Cambia a la ruta donde hayas almacenado Data
emotionsList = os.listdir(dataPath)
print('Lista de personas: ', emotionsList)

labels = []
facesData = []
label = 0

for nameDir in emotionsList:
    emotionsPath = dataPath + '/' + nameDir

    for fileName in os.listdir(emotionsPath):
        #print('Rostros: ', nameDir + '/' + fileName)
        labels.append(label)
        facesData.append(cv2.imread(emotionsPath+'/'+fileName,0))
        #image = cv2.imread(emotionsPath+'/'+fileName,0)
        #cv2.imshow('image',image)
        #cv2.waitKey(10)
    label = label + 1

obtenerModelo('EigenFaces',facesData,labels)
obtenerModelo('FisherFaces',facesData,labels)
obtenerModelo('LBPH',facesData,labels)

Línea 1 a 4: Importamos OpenCV, os, numpy con un alias np y time, este último nos ayudará a conocer el tiempo empleado en el entrenamiento por cada método.

Línea 6 a 20: En estas líneas nos extenderemos un poquito, ya que he creado una función que me ayude a leer, entrenar y guardar cada reconocedor, veamos:

  • Línea 6: Creamos la función obtenerModelo, para esta función necesitaremos de method que es el nombre del método a emplear, facesData el array en donde se almacenarán todos los rostros con sus diferentes emociones y finalmente labels, que son las etiquetas de cada uno de los rostros correspondientes a cada emoción.
  • Línea 8 a 10: Si el método que deseamos usar esEigenFaces entonces se asignará cv2.face.EigenFaceRecognizer_create() a emotion_recognizer. Si deseamos usar esFisherFaces entonces se asignará cv2.face.FisherFaceRecognizer_create() a emotion_recognizer. Por último si deseamos usar LBPH se asignará  cv2.face.LBPHFaceRecognizer_create() a emotion_recognizer.
  • Línea 13 a 17: En estas líneas entrenaremos al reconocedor, para ello en la línea 13 imprimimos un mensaje de 'Entrenando' junto al método que se vaya a usar en ese momento. En la línea 14 almacenamos el tiempo inicial en inicio, en la línea 15 entrenamos el reconocedor, para ello digitamos emotion_recognizer.train y dentro de los paréntesis especificamos el array en donde están almacenados todos los rostros y las etiquetas de cada uno de estos rostros. En la línea 16 tomamos el tiempo actual menos el tiempo de inicio para conocer la cantidad de tiempo en segundos que tomó el entrenamiento. En la línea 17 imprimimos el método usado y el tiempo que le llevó entrenar.
  • Línea 20: Una vez que se ha entrenado el reconocedor, procedemos a almacenarlo con emotion_recognizer.write, dentro de los paréntesis debemos colocar el nombre del modelo a almacenar seguido de la extensión xml o yaml.

Línea 22 a 24: Indicamos la ruta en donde se encuentra el directorio ‘Data’, en el cual se crearon las carpetas con las emociones. Luego listamos todas las carpetas contenidas en esta ubicación y por último imprimimos dicha lista.

Línea 26 a 28: Declaramos los arrays vacíos labels y faceData en ellos se almacenará todos los rostros de las carpetas con una etiqueta asociada según la emoción. Declaramos label en 0, esta nos ayudará con el valor almacenado en labels.

Línea 30 y 31: Con un for recorremos cada elemento del array emotionsList.  Luego asignamos a emotionsPath la ruta completa, esto nos servirá para más adelante leer cada rostro.

Línea 33 a 40: A continuación recorremos cada una de las imágenes contenidas en emotionsPath, es decir que por cada carpeta de emociones leeremos todos los rostros contenidos en ella. En la línea 35 almacenamos el valor de label en el array labels, en cambio en la línea 36 leemos cada imagen y las transformamos a grises, gracias a que estamos usado 0 como segundo argumento de cv2.imread. Una vez que se haya leído cada rostro de una carpeta se incrementará en 1 label para que la próxima carpeta con otra emoción tenga un valor diferente.

Figura 3: Ejemplo de los rostros por cada expresión, y sus etiquetas asociadas.

Línea 42 a 44: Llamamos a la función obtenerModelo que creamos en un principio, en ella especificamos el método que usaremos para entrenar el reconocedor, seguido del array en donde se han almacenado los rostros y finalmente cada una de las etiquetas correspondientes. En mi caso voy a realizar el entrenamiento con los 3 métodos.

Figura 4: Modelos obtenidos luego del entrenamiento.

Probando EigenFaces, FisherFaces y LBPH para el reconocimiento de emociones faciales

Una vez que hemos entrenado el o los modelos será necesario probar su funcionamiento, para ello crearemos otro script llamado reconocimientoEmociones.py .

import cv2
import os
import numpy as np

def emotionImage(emotion):
    # Emojis
    if emotion == 'Felicidad': image = cv2.imread('Emojis/felicidad.jpeg')
    if emotion == 'Enojo': image = cv2.imread('Emojis/enojo.jpeg')
    if emotion == 'Sorpresa': image = cv2.imread('Emojis/sorpresa.jpeg')
    if emotion == 'Tristeza': image = cv2.imread('Emojis/tristeza.jpeg')
    return image

# ----------- Métodos usados para el entrenamiento y lectura del modelo ----------
#method = 'EigenFaces'
#method = 'FisherFaces'
method = 'LBPH'

if method == 'EigenFaces': emotion_recognizer = cv2.face.EigenFaceRecognizer_create()
if method == 'FisherFaces': emotion_recognizer = cv2.face.FisherFaceRecognizer_create()
if method == 'LBPH': emotion_recognizer = cv2.face.LBPHFaceRecognizer_create()

emotion_recognizer.read('modelo'+method+'.xml')
# --------------------------------------------------------------------------------

dataPath = '.../Reconocimiento Emociones/Data' #Cambia a la ruta donde hayas almacenado Data
imagePaths = os.listdir(dataPath)
print('imagePaths=',imagePaths)

cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)

faceClassif = cv2.CascadeClassifier(cv2.data.haarcascades+'haarcascade_frontalface_default.xml')

while True:

    ret,frame = cap.read()
    if ret == False: break
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    auxFrame = gray.copy()

    nFrame = cv2.hconcat([frame, np.zeros((480,300,3),dtype=np.uint8)])

    faces = faceClassif.detectMultiScale(gray,1.3,5)

    for (x,y,w,h) in faces:
        rostro = auxFrame[y:y+h,x:x+w]
        rostro = cv2.resize(rostro,(150,150),interpolation= cv2.INTER_CUBIC)
        result = emotion_recognizer.predict(rostro)

        cv2.putText(frame,'{}'.format(result),(x,y-5),1,1.3,(255,255,0),1,cv2.LINE_AA)

        # EigenFaces
        if method == 'EigenFaces':
            if result[1] < 5700:
                cv2.putText(frame,'{}'.format(imagePaths[result[0]]),(x,y-25),2,1.1,(0,255,0),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,255,0),2)
                image = emotionImage(imagePaths[result[0]])
                nFrame = cv2.hconcat([frame,image])
            else:
                cv2.putText(frame,'No identificado',(x,y-20),2,0.8,(0,0,255),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,0,255),2)
                nFrame = cv2.hconcat([frame,np.zeros((480,300,3),dtype=np.uint8)])
        
        # FisherFace
        if method == 'FisherFaces':
            if result[1] < 500:
                cv2.putText(frame,'{}'.format(imagePaths[result[0]]),(x,y-25),2,1.1,(0,255,0),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,255,0),2)
                image = emotionImage(imagePaths[result[0]])
                nFrame = cv2.hconcat([frame,image])
            else:
                cv2.putText(frame,'No identificado',(x,y-20),2,0.8,(0,0,255),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,0,255),2)
                nFrame = cv2.hconcat([frame,np.zeros((480,300,3),dtype=np.uint8)])
        
        # LBPHFace
        if method == 'LBPH':
            if result[1] < 60:
                cv2.putText(frame,'{}'.format(imagePaths[result[0]]),(x,y-25),2,1.1,(0,255,0),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,255,0),2)
                image = emotionImage(imagePaths[result[0]])
                nFrame = cv2.hconcat([frame,image])
            else:
                cv2.putText(frame,'No identificado',(x,y-20),2,0.8,(0,0,255),1,cv2.LINE_AA)
                cv2.rectangle(frame, (x,y),(x+w,y+h),(0,0,255),2)
                nFrame = cv2.hconcat([frame,np.zeros((480,300,3),dtype=np.uint8)])

    cv2.imshow('nFrame',nFrame)
    k = cv2.waitKey(1)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

Línea 1 a 3: Importamos OpenCV, os y numpy con un alias np.

Línea 5 a 11: Vamos a crear una función llamada emotionImage, como parámetros tendremos  a emotion que puede ser felicidad, enojo, sorpresa o tristeza. Dependiendo de estos se visualizará un emoji diferente a la derecha de los fotogramas (en mi repositorio puedes encontrar estas imágenes).

Figura 5: Emojis que representarán la emoción desconocida.

  • Línea 5: Definimos la función emotionImage, el parámetro de esta función es emotion que corresponde a felicidad, enojo, sorpresa o tristeza.
  • Línea 7 a 10: Dependiendo de la emoción, se leerá la imagen de un emoji que se encuentra en la carpeta Emojis.
  • Línea 11: Esta función devolverá la imagen correspondiente a la emoción reconocida.

Línea 14 a 22: Podemos escoger cualquiera de los métodos de la línea 14 a la 16, solo tendremos que descomentar uno y comentar los demás (claro que para probarlos debemos haber entrenado y guardado un modelo con estos métodos de reconocimiento). Dependiendo de dicho método se asignará a emotion_recognizer cv2.face.EigenFaceRecognizer_create(),  cv2.face.FisherFaceRecognizer_create() o  cv2.face.LBPHFaceRecognizer_create(). Finalmente en la lína 22 se lee el modelo correspondiente a EigenFaces, FisherFaces o LBPH previamente entrenado.

Línea 25 a 27: Especificamos la ruta del directptio ‘Data’, listamos todas carpetas contenidas en esta ubicación e imprimimos dicha lista.

Línea 29: Indicamos que vamos a realizar un video streaming.

Línea 31: Leemos el detector de rostros.

Línea 35 a 38: Se lee cada fotograma, se lo transforma a escala de grises y nos ayduamos de gray.copy() para crear una copia limpia de cada fotograma.

Línea 40: Concatenamos dos imágenes, la primera frame y la segunda será un área de color negro de 300 pixeles de ancho y 480 de alto que creamos con np.zeros (usamos estas dimensiones, ya que son las mismas que posee cada imagen de los emojis).

Línea 42: En faces se almacenan todos los rostros detectados.

Línea 44 a 49: Vamos a extraer la información de faces en donde encontramos las coordenadas x, y, ancho y alto de cada rostro detectado. Recortamos los rostros de la imagen auxFrame, redimensionamos dicho rostro a 150 x 150 pixeles y finalmente vamos a usar emotion_recognizer.predict entre paréntesis el rostro recortado y redimensionado, que se almacenará en result. Este valor se comparará con valores diferentes dependiendo de cada método empleado.

En la línea 49 visualizamos el valor obtenido de result sobre el rostro detectado.

Línea 52 a 61: Vamos con el método EigenFaces. En la línea 53 comparamos el segundo valor almacenado en result con 5700 (tomé el mismo valor que había usado en el post de reconocimiento facial, puedes cambiarlo de acuerdo a las pruebas que realices). Si este valor es menor a 5700, quiere decir que se ha reconocido una emoción, por lo cual se visualiza el nombre de la emoción reconocida, además de un rectángulo verde que rodee el rostro de la persona. Mientras tanto en la línea 56 llamamos a la función emotionImage, y a ella le entregamos  imagePaths[result[0]] que contiene el nombre de la emoción reconocida. En la línea 57, similar a la línea 40, concatenamos frame e image que es la imagen que contiene el emoticon.

Cuando result es mayor a 5700, no ha reconocido emociones por lo tanto en la línea 59 especificamos que se muestre el texto 'No identificado' y se dibujará un rectágunlo rojo que rodee al rostro. En la línea 61 al igual que en la línea 40, concatenamos frame  y una el área en color negro.

Línea 64 a 85: El procedimiento que seguimos tanto para FisherFace como para LBPH es el mismo que en las líneas 52 a 61, la diferencia radica en el valor a comprar en las líneas 65 y 77.

Línea 87 a 93: Visualizamos nFrame , especificamos que cuando se presione ‘ESC’ el proceso se detenga y finalmente cerramos todas las ventanas que se hayan usado para la visualización.

Resultados de EigenFaces en el reconocimiento de emociones

Tengo que resaltar, que los resultados que a continuación se muestran dependen de los rostros con los cuales se ha entrenado este reconocedor, además de que estoy probando en el mismo ambiente con el que capturé los rostros en un principio.

Figura 6: Pruebas realizadas del reconocimiento de emociones con EigenFaces.

Debo decir que en pruebas anteriores (en otro ambiente) que realicé con este método, no obtuve muy buenos resultados, sin embargo en esta ocasión al probarlo como puedes ver en la figura 6 nos fue muy bien.

Resultados de FisherFaces en el reconocimiento de emociones

Al igual que el método anterior tengo que resaltar, que los resultados que a continuación se muestran dependen de los rostros con los cuales se ha entrenado al reconocedor, además de que lo estoy probando en el mismo ambiente con el que capturé los rostros en un principio.

Figura 7: Pruebas realizadas del reconocimiento de emociones con FisherFaces.

Como puedes apreciar en la figura 7, no obtuvimos muy buenos resultados y como lo decía en el video, este resultado no fue lo que esperaba, ya que anteriormente con las pruebas que había realizado FisherFaces respondió bastante bien, incluso en el tiempo de respuesta. Por ello te recomiendo no omitir a este método, al contrario te recomiendo realizar más pruebas e incluso cambiar el valor que habíamos usado para comparar en la línea 65, ya que puede que obtengas mejores respuestas.

Resultados de Local Binary Pattern Histogram en el reconocimiento de emociones

Al igual que en los métodos anteriores tengo que resaltar, que los resultados que a continuación se muestran, dependen de los rostros con los cuales se ha entrenado al reconocedor, además de que lo estoy probando en el mismo ambiente con el que capturé los rostros en un principio.

Figura 8: Pruebas realizadas del reconocimiento de emociones con Local Binary Pattern Histogram.

Algo a resaltar con este método, es que en pruebas previas que había realizado en otros ambientes, este respondía bastante bien, y esta vez no fue la excepción, lo puedes apreciar en la figura 8 en la que ha podido reconocer las 4 emociones.

Y bien, esto ha sido todo por el tutorial de hoy, espero que te haya parecido interesante y que te haya gustado mucho, nos vemos en un siguiente post y/o video. 🙂