tutoriales.com

Reconocimiento de Emociones Faciales con OpenCV y Redes Convolucionales (CNNs)

Este tutorial te guiará a través del proceso de creación de un sistema de reconocimiento de emociones faciales utilizando Python, OpenCV para el procesamiento de imágenes y detección de rostros, y redes neuronales convolucionales (CNNs) para clasificar emociones. Cubriremos desde la preparación del dataset hasta la implementación en tiempo real.

Intermedio20 min de lectura24 views23 de marzo de 2026Reportar error

🚀 Introducción al Reconocimiento de Emociones Faciales

El reconocimiento de emociones faciales es un campo fascinante dentro de la visión artificial que busca identificar automáticamente las emociones humanas a partir de expresiones faciales. Esta tecnología tiene aplicaciones que van desde la interacción humano-computadora y la robótica social hasta el análisis de sentimientos en marketing y la monitorización del bienestar.

En este tutorial, construiremos un sistema práctico que puede detectar rostros en una transmisión de video y luego clasificar la emoción predominante (alegría, tristeza, enojo, etc.) en ese rostro. Utilizaremos OpenCV para las tareas de preprocesamiento y detección facial, y TensorFlow/Keras para construir y entrenar una Red Neuronal Convolucional (CNN) que realizará la clasificación de emociones.

¿Por qué es importante el Reconocimiento de Emociones?

La comunicación humana es multifacética, y las expresiones faciales son un componente crucial. Comprender y responder a las emociones puede mejorar la interacción en diversas áreas:

  • Interacción H-C: Sistemas más intuitivos y adaptativos.
  • Robótica: Robots con mayor inteligencia emocional para interactuar con humanos.
  • Salud: Detección temprana de signos de depresión o estrés.
  • Marketing: Entender las reacciones de los consumidores a productos o anuncios.
📌 **Nota:** Aunque el reconocimiento de emociones es potente, es fundamental usarlo de manera ética y respetando la privacidad. Las emociones son complejas y pueden ser ambiguas.

🛠️ Herramientas y Requisitos Previos

Antes de sumergirnos en el código, asegúrate de tener el entorno de desarrollo configurado. Necesitarás Python y algunas librerías clave.

Requisitos de Software

  • Python 3.x: Se recomienda Python 3.8 o superior.
  • pip: El gestor de paquetes de Python.

Librerías de Python

Instala las siguientes librerías utilizando pip:

pip install opencv-python tensorflow keras scikit-learn matplotlib pandas numpy

Vamos a desglosar brevemente para qué usaremos cada una:

  • opencv-python: Para detección de rostros, procesamiento de imágenes y captura de video.
  • tensorflow y keras: Para construir, entrenar y evaluar nuestra red neuronal convolucional.
  • scikit-learn: Para herramientas de preprocesamiento de datos y métricas de evaluación.
  • matplotlib: Para visualizar datos y curvas de entrenamiento.
  • pandas y numpy: Para manipulación eficiente de datos, especialmente el dataset.

📂 Preparación del Dataset: FER2013

Para entrenar nuestra CNN, necesitamos un dataset de imágenes de rostros etiquetadas con sus emociones correspondientes. El dataset más común para este tipo de tarea es FER2013 (Facial Expression Recognition Challenge 2013).

Características del Dataset FER2013

  • Contiene 48x48 píxeles imágenes en escala de grises de rostros.
  • Etiquetas de emoción para 7 categorías: 0=Enfado, 1=Disgusto, 2=Miedo, 3=Felicidad, 4=Tristeza, 5=Sorpresa, 6=Neutral.
  • Se divide en conjuntos de entrenamiento, validación y prueba.
🔥 **Importante:** El dataset FER2013 no es perfecto y ha sido criticado por su ruido y sesgos. Sin embargo, es un buen punto de partida para este tipo de proyectos educativos.

Descarga y Preprocesamiento

El dataset FER2013 suele encontrarse en formato CSV. Puedes descargarlo de Kaggle o de otras fuentes. Una vez descargado, el primer paso es cargar y preparar estos datos.

import pandas as pd
import numpy as np

# Cargar el dataset FER2013.csv
data = pd.read_csv('fer2013.csv')

print(data.head())
print(data['emotion'].value_counts())

# Preparar los datos de entrada y salida
# Las imágenes están en una sola cadena de píxeles separados por espacios
X = []
y = []

for index, row in data.iterrows():
    pixels = np.array(row['pixels'].split(' '), dtype='uint8')
    image = pixels.reshape(48, 48)
    X.append(image)
    y.append(row['emotion'])

X = np.array(X)
y = np.array(y)

# Normalizar las imágenes a un rango de [0, 1]
X = X / 255.0

# Expandir dimensiones para que coincida con el formato de entrada de Keras (batch, height, width, channels)
X = np.expand_dims(X, -1)

# Convertir las etiquetas a formato one-hot encoding
from tensorflow.keras.utils import to_categorical
y = to_categorical(y, num_classes=7)

print(f"Forma de X: {X.shape}") # Esperado: (num_samples, 48, 48, 1)
print(f"Forma de y: {y.shape}") # Esperado: (num_samples, 7)

División del Dataset

Dividiremos nuestros datos en conjuntos de entrenamiento y prueba para evaluar el rendimiento de nuestro modelo.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(f"Forma de X_train: {X_train.shape}")
print(f"Forma de X_test: {X_test.shape}")

🧠 Construyendo la Red Neuronal Convolucional (CNN)

Las CNNs son especialmente efectivas para el procesamiento de imágenes debido a su capacidad para aprender jerarquías de características visuales. Nuestro modelo tendrá varias capas convolucionales, de pooling, de regularización (dropout) y densas.

Arquitectura del Modelo

Diseñaremos una arquitectura sencilla pero eficaz:

  1. Capas Convolucionales (Conv2D): Extraen características locales de las imágenes.
  2. Capas de Max Pooling (MaxPooling2D): Reducen la dimensionalidad y la sensibilidad a pequeñas traslaciones.
  3. Capas de Dropout (Dropout): Ayudan a prevenir el sobreajuste al desactivar aleatoriamente algunas neuronas durante el entrenamiento.
  4. Capas Densas (Dense): Las capas completamente conectadas al final para la clasificación.
Input Tamaño: 48x48x1 Conv2D -> ReLU -> MaxPooling2D Tamaño: 24x24 Conv2D -> ReLU -> MaxPooling2D -> Dropout Tamaño: 12x12 Flatten -> Dense (256) -> ReLU -> Dropout Tamaño: Vector 256 Dense (7) -> Softmax Salida: 7 Clases

Código de la CNN en Keras

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras.optimizers import Adam

num_classes = 7
input_shape = (48, 48, 1)

model = Sequential()

# Bloque 1
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape, kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

# Bloque 2
model.add(Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

# Bloque 3
model.add(Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

# Aplanar para la capa densa
model.add(Flatten())

# Capas densas
model.add(Dense(256, activation='relu', kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

# Compilar el modelo
model.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

model.summary()
💡 **Consejo:** Experimenta con diferentes arquitecturas, número de filtros, tamaños de kernel y tasas de dropout para encontrar la configuración óptima para tu problema.

🏋️ Entrenamiento del Modelo

Ahora que hemos definido la arquitectura de la CNN, es hora de entrenarla con nuestros datos. Esto puede llevar algún tiempo dependiendo de tu hardware (GPU recomendada).

Parámetros de Entrenamiento

  • Épocas: Número de veces que el modelo verá todo el conjunto de datos de entrenamiento.
  • Tamaño del Lote (batch_size): Número de muestras por actualización de gradiente.
  • Callbacks: Funciones para monitorear y guardar el modelo durante el entrenamiento (ej. ModelCheckpoint, EarlyStopping).
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau

epochs = 50 # Puedes ajustar esto
batch_size = 64 # Puedes ajustar esto

# Callbacks
checkpoint = ModelCheckpoint('emotion_model.h5', monitor='val_accuracy', verbose=1, save_best_only=True, mode='max')
early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1, mode='min', restore_best_weights=True)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, verbose=1, min_lr=0.00001)

callbacks_list = [checkpoint, early_stopping, reduce_lr]

# Entrenamiento
history = model.fit(
    X_train, y_train,
    epochs=epochs,
    batch_size=batch_size,
    validation_data=(X_test, y_test),
    callbacks=callbacks_list,
    verbose=1
)

Visualización del Progreso del Entrenamiento

Es útil ver cómo la precisión y la pérdida evolucionan durante el entrenamiento.

import matplotlib.pyplot as plt

# Graficar la precisión de entrenamiento y validación
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Precisión de Entrenamiento')
plt.plot(history.history['val_accuracy'], label='Precisión de Validación')
plt.title('Precisión del Modelo')
plt.xlabel('Época')
plt.ylabel('Precisión')
plt.legend()

# Graficar la pérdida de entrenamiento y validación
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Pérdida de Entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de Validación')
plt.title('Pérdida del Modelo')
plt.xlabel('Época')
plt.ylabel('Pérdida')
plt.legend()

plt.show()

✅ Evaluación del Modelo

Una vez entrenado, evaluaremos el rendimiento final del modelo en el conjunto de prueba.

# Cargar el mejor modelo guardado
from tensorflow.keras.models import load_model
best_model = load_model('emotion_model.h5')

# Evaluar en el conjunto de prueba
loss, accuracy = best_model.evaluate(X_test, y_test, verbose=0)
print(f'Precisión del modelo en el conjunto de prueba: {accuracy*100:.2f}%')

# Generar un reporte de clasificación
from sklearn.metrics import classification_report, confusion_matrix

y_pred = np.argmax(best_model.predict(X_test), axis=1)
y_true = np.argmax(y_test, axis=1)

emotion_labels = ['Enfado', 'Disgusto', 'Miedo', 'Felicidad', 'Tristeza', 'Sorpresa', 'Neutral']

print('\nReporte de Clasificación:')
print(classification_report(y_true, y_pred, target_names=emotion_labels))

# Mostrar la matriz de confusión
import seaborn as sns
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=emotion_labels, yticklabels=emotion_labels)
plt.xlabel('Predicción')
plt.ylabel('Verdadero')
plt.title('Matriz de Confusión')
plt.show()
Modelo Entrenado y Evaluado

🎥 Implementación en Tiempo Real con OpenCV

La parte más emocionante es ver nuestro modelo en acción. Utilizaremos OpenCV para acceder a la cámara, detectar rostros y luego clasificar sus emociones.

Detección Facial con Haar Cascades

OpenCV viene con clasificadores pre-entrenados basados en Haar Cascades, que son muy eficientes para la detección de objetos, incluidos los rostros. Necesitarás el archivo haarcascade_frontalface_default.xml, que se encuentra en el repositorio de OpenCV.

⚠️ Advertencia: Asegúrate de que la ruta al archivo XML del clasificador sea correcta. Si no lo encuentras, búscalo en el directorio `data/haarcascades` de tu instalación de OpenCV o descárgalo del repositorio de GitHub de OpenCV.

Código para Detección y Reconocimiento en Vivo

import cv2
import numpy as np
from tensorflow.keras.models import load_model

# Cargar el modelo de emoción entrenado
model = load_model('emotion_model.h5')

# Cargar el clasificador Haar Cascade para detección facial
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# Etiquetas de emoción
emotion_labels = ['Enfado', 'Disgusto', 'Miedo', 'Felicidad', 'Tristeza', 'Sorpresa', 'Neutral']

# Iniciar la captura de video de la cámara web
cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("Error: No se pudo abrir la cámara.")
    exit()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # Convertir el frame a escala de grises
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Detectar rostros en el frame
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

    for (x, y, w, h) in faces:
        # Dibujar un rectángulo alrededor del rostro detectado
        cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)

        # Extraer la región de interés (ROI) del rostro
        roi_gray = gray[y:y+h, x:x+w]

        # Redimensionar la ROI a 48x48 píxeles (tamaño de entrada de la CNN)
        roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA)

        # Normalizar y expandir dimensiones para el modelo
        roi = roi_gray.astype('float32') / 255.0
        roi = np.expand_dims(roi, axis=0) # Añadir dimensión de batch
        roi = np.expand_dims(roi, axis=-1) # Añadir dimensión de canal (1 para escala de grises)

        # Predecir la emoción
        prediction = model.predict(roi)[0]
        emotion_index = np.argmax(prediction)
        emotion = emotion_labels[emotion_index]
        confidence = prediction[emotion_index] * 100

        # Mostrar la emoción y confianza en el frame
        text = f"{emotion}: {confidence:.2f}%"
        cv2.putText(frame, text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2, cv2.LINE_AA)

    # Mostrar el frame resultante
    cv2.imshow('Reconocimiento de Emociones Faciales', frame)

    # Salir del bucle si se presiona 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Liberar la cámara y destruir todas las ventanas
cap.release()
cv2.destroyAllWindows()
Paso 1: Cargar el modelo CNN entrenado.
Paso 2: Cargar el clasificador Haar Cascade para rostros.
Paso 3: Iniciar la captura de video de la cámara web.
Paso 4: En un bucle, leer frames de la cámara.
Paso 5: Convertir el frame a escala de grises y detectar rostros.
Paso 6: Para cada rostro, extraer la ROI, redimensionarla y preprocesarla.
Paso 7: Realizar la predicción de emoción con el modelo CNN.
Paso 8: Mostrar el resultado en el frame original.
Paso 9: Mostrar el frame y esperar la pulsación de 'q' para salir.
Paso 10: Liberar recursos al finalizar.

💡 Posibles Mejoras y Próximos Pasos

Este tutorial proporciona una base sólida para el reconocimiento de emociones faciales. Aquí hay algunas ideas para llevar tu proyecto al siguiente nivel:

  • Datasets más robustos: Experimenta con datasets más grandes y de mayor calidad (ej., AffectNet, RAF-DB) para entrenar modelos más precisos.
  • Aumento de Datos (Data Augmentation): Genera más datos de entrenamiento a partir de los existentes (rotaciones, flips, zoom, etc.) para mejorar la generalización del modelo.
  • Modelos pre-entrenados: Utiliza modelos pre-entrenados en grandes datasets (ej., VGG-Face, ResNet) y aplica transfer learning para ajustar las capas finales a tu problema.
  • Técnicas de Regularización Avanzadas: Investiga técnicas como la regularización L1/L2, Max-norm regularization o Stochastic Depth.
  • Reconocimiento de Puntos Clave (Facial Landmarks): Integrar la detección de puntos clave faciales (ej., con Dlib) puede ayudar a alinear los rostros y mejorar la robustez de la extracción de características.
  • Despliegue en la Nube: Despliega tu modelo como un servicio web utilizando plataformas como AWS SageMaker, Google Cloud AI Platform o Azure Machine Learning.
  • Optimización para Dispositivos Móviles: Convierte tu modelo a formatos ligeros como TensorFlow Lite o OpenVINO para implementarlo en dispositivos edge o móviles.
¿Qué es el Overfitting y cómo lo evitamos? El *overfitting* (sobreajuste) ocurre cuando un modelo aprende demasiado bien los datos de entrenamiento, capturando el ruido y los detalles específicos que no son representativos de nuevos datos. Esto lleva a un bajo rendimiento en datos no vistos.

Para evitarlo, usamos:

  • Dropout: Desactiva neuronas aleatoriamente durante el entrenamiento.
  • Batch Normalization: Estabiliza el entrenamiento y reduce la necesidad de un dropout alto.
  • Early Stopping: Detiene el entrenamiento cuando el rendimiento en el conjunto de validación deja de mejorar.
  • Aumento de Datos: Introduce variaciones artificiales en los datos de entrenamiento para aumentar su cantidad y diversidad.

Conclusion ✨

En este tutorial, hemos recorrido el proceso completo para construir un sistema de reconocimiento de emociones faciales. Desde la preparación de un dataset de imágenes hasta la construcción y entrenamiento de una CNN con Keras/TensorFlow, y finalmente, su implementación en tiempo real con OpenCV.

Has aprendido a:

  • Cargar y preprocesar el dataset FER2013.
  • Diseñar y compilar una arquitectura CNN.
  • Entrenar el modelo utilizando callbacks para optimizar el proceso.
  • Evaluar el rendimiento del modelo.
  • Integrar OpenCV para la detección facial y la clasificación de emociones en vivo.

El reconocimiento de emociones es un campo activo de investigación con un enorme potencial. ¡Anímate a explorar más y construir tus propias aplicaciones!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!