Sistema de Comunicación Asistida por Parpadeo.

Vamos a desarrollar un prototipo de sistema de comunicación asistida en Python que permita a personas con movilidad reducida seleccionar palabras y frases mediante el parpadeo. Utilizaremos el seguimiento facial con MediaPipe y el procesamiento de imágenes con OpenCV.

Objetivo: Crear una interfaz accesible que facilite la comunicación para personas con limitaciones motoras severas.

Funcionamiento Básico:

  1. El sistema detecta el rostro y los puntos clave de los ojos usando MediaPipe.
  2. Calcula la “Relación de Aspecto del Ojo” (Eye Aspect Ratio – EAR) para detectar parpadeos.
  3. Muestra una matriz de palabras/frases en la pantalla.
  4. Un parpadeo corto permite al usuario navegar por las opciones (cambiar la palabra seleccionada).
  5. Un parpadeo largo (mantener los ojos cerrados por más tiempo) selecciona la palabra resaltada actualmente.

Requisitos Previos

  • Python 3: Asegúrate de tener Python 3 instalado en tu sistema.
  • Cámara Web: Necesitarás una cámara conectada a tu ordenador.
  • Entorno Virtual (Recomendado): Es una buena práctica crear un entorno virtual para aislar las dependencias de este proyecto y no afectar otras instalaciones de Python en tu sistema.
  • Librerías de Python: opencv-python, mediapipe, numpy.

Pasos de Instalación y Configuración (Ejemplo para Linux/Debian/Ubuntu)

apt install python3 
sudo apt install python3-venv
mkdir parpadeo
cd parpadeo
python3 -m venv tu_entorno_virtual
source tu_entorno_virtual/bin/activate

Necesitas instalar también el numpy que es una librería de python para procesar datos de forma numérica, para instalarlo lo hacemos de la siguiente forma:

#dentro del entorno virtual instalar:
pip install numpy
pip install opencv2-python
pip install mediapipe 

Claramente hay que tener conectada una cámara para que esto funcione, creamos el script en python con el siguiente código dentro del subdirectorio parpadeo:

nano parpadeo.py

Copiamos el siguiente código.

import cv2
import mediapipe as mp
import numpy as np

# Inicializar MediaPipe Face Mesh
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(min_detection_confidence=0.5, min_tracking_confidence=0.5)

# Constantes para la detección de parpadeos
EYE_AR_THRESH = 0.3  # Umbral de relación de aspecto para detectar parpadeo
EYE_AR_CONSEC_FRAMES = 3  # Número de frames consecutivos para confirmar un parpadeo
LONG_BLINK_THRESHOLD = 20  # Número de frames para considerar un parpadeo largo

# Índices de los landmarks de los ojos en MediaPipe Face Mesh
LEFT_EYE = [362, 385, 387, 263, 373, 380]
RIGHT_EYE = [33, 160, 158, 133, 153, 144]

def eye_aspect_ratio(eye):
    # Calcular la distancia euclidiana entre los dos conjuntos de puntos verticales del ojo
    A = np.linalg.norm(eye[1] - eye[5])
    B = np.linalg.norm(eye[2] - eye[4])

    # Calcular la distancia euclidiana entre los puntos horizontales del ojo
    C = np.linalg.norm(eye[0] - eye[3])

    # Calcular la relación de aspecto del ojo
    ear = (A + B) / (2.0 * C)
    return ear

def detect_blink(frame, landmarks):
    left_eye = np.array([(landmarks.landmark[i].x, landmarks.landmark[i].y) for i in LEFT_EYE])
    right_eye = np.array([(landmarks.landmark[i].x, landmarks.landmark[i].y) for i in RIGHT_EYE])

    left_ear = eye_aspect_ratio(left_eye)
    right_ear = eye_aspect_ratio(right_eye)

    ear = (left_ear + right_ear) / 2.0

    return ear < EYE_AR_THRESH

def draw_word_matrix(frame, words, selected_index):
    rows, cols = 3, 3  # Matriz de 3x3
    for i in range(rows):
        for j in range(cols):
            index = i * cols + j
            if index < len(words):
                color = (0, 255, 0) if index == selected_index else (255, 255, 255)
                cv2.putText(frame, words[index], (50 + j * 200, 100 + i * 50), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)

def main():
    cap = cv2.VideoCapture(0)
    words = ["Hola", "Adios", "Si", "No", "Gracias", "Por favor", "Ayuda", "Agua", "Comida"]
    selected_index = 0
    blink_counter = 0
    selected_word = None

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

        frame = cv2.flip(frame, 1)
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        results = face_mesh.process(rgb_frame)

        if results.multi_face_landmarks:
            landmarks = results.multi_face_landmarks[0]
            if detect_blink(frame, landmarks):
                blink_counter += 1
            else:
                if blink_counter > EYE_AR_CONSEC_FRAMES:
                    if blink_counter > LONG_BLINK_THRESHOLD:
                        selected_word = words[selected_index]
                    else:
                        selected_index = (selected_index + 1) % len(words)
                blink_counter = 0

        draw_word_matrix(frame, words, selected_index)

        if selected_word:
            cv2.putText(frame, selected_word, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 3)

        cv2.imshow("Blink Communication System", frame)

        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

Para ejecutar el programa usaremos el siguiente comando:

python3 parapadeo.py

Solución de Problemas Comunes

  • Error No se pudo abrir la cámara:
    • El script intenta automáticamente los índices de cámara 0, 1, 2 y 3.
    • Asegúrate de que la cámara esté conectada y no esté siendo utilizada por otra aplicación.
    • Si tienes varias cámaras, puede que necesites un índice diferente. Puedes modificar la línea cap = cv2.VideoCapture(0) e intentar con 1, 2, etc., manualmente si el script no la encuentra automáticamente. En Linux, puedes listar cámaras con ls /dev/video*.
  • La detección de parpadeos no funciona bien (demasiado sensible o no detecta):
    • Ajusta el valor de EYE_AR_THRESH. Un valor más bajo requiere que los ojos se cierren más para detectarlo. Un valor más alto lo hace más sensible. La iluminación y la distancia a la cámara afectan esto.
    • Ajusta EYE_AR_CONSEC_FRAMES si la detección es errática.
    • Ajusta SHORT_BLINK_TIME y LONG_BLINK_TIME para cambiar la duración requerida para navegar o seleccionar.
  • El programa se ejecuta lento:
    • Asegúrate de que no haya otros programas consumiendo muchos recursos.
    • Desactivar el dibujo de landmarks (mp_drawing.draw_landmarks) puede mejorar el rendimiento.
    • Si usaste refine_landmarks=True, prueba a cambiarlo a False (pero necesitarás ajustar los índices de LEFT_EYE_POINTS y RIGHT_EYE_POINTS a los de la malla básica).

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

44 − = 43