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:
- El sistema detecta el rostro y los puntos clave de los ojos usando MediaPipe.
- Calcula la “Relación de Aspecto del Ojo” (Eye Aspect Ratio – EAR) para detectar parpadeos.
- Muestra una matriz de palabras/frases en la pantalla.
- Un parpadeo corto permite al usuario navegar por las opciones (cambiar la palabra seleccionada).
- 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).