👁️ CONTADOR DE PARPADEOS 👁️ | Python – MediaPipe Face Mesh – OpenCV
¡Hola, hola Omesitos!. Bienvenidos a un nuevo tutorial en el que vamos a estar construyendo un contador de parpadeos con ayuda de una de las soluciones que habíamos visto de Mediapipe, FaceMesh. 😄
CONTENIDO:
- Contador de Parpadeos con Mediapipe Facemesh
- Instalación de packages
- ¡Vamos con la programación!
- Referencias
Contador de Parpadeos con Mediapipe Facemesh en Python
Antes de pasar con el contenido de este post, hablemos un poquito de lo que vamos a hacer.

Vamos a aplicar MediaPipe Face Mesh, de ella obtendremos 468 puntos distribuídos en el rostro de la persona detectada. De todos estos puntos únicamente tomaremos 6 del ojo izquierdo y 6 del ojo derecho. Pero ¿por qué?, bueno esto debido a que estaremos usando la ecuación de la relación de aspecto del ojo que nos provee este artículo, que por cierto encontré en el blog de Pyimagesearch, en donde abordan este mismo tema, solo que en ese post utilizan la librería dlib y nosotros emplearemos MediaPipe para obtener los puntos de referencia.

Figura 2: Eye Aspect Ratio / Relación de Aspecto del Ojo. (Fuente)
Una vez que obtengamos los 6 puntos tendremos que calcular ciertas distancias para obtener el Eye Aspect Ratio o la Relación de Aspecto del Ojo como lo vemos en la figura 2. A partir de allí tendremos que añadir una serie de procesos para realizar el conteo de parpadeos.

Figura 3: Visualización en tiempo real de la relación de aspecto de los ojos.
Además construiremos una gráfica en tiempo real que nos mostrará los cambios que obtenemos en la relación de aspecto de los ojos, cuando estos están abiertos o cerrados, como lo podemos visualizar en la figura 3.
Instalación de packages
Para este proyecto únicamente tendremos que usar pip install mediapipe ya que además de este módulo, se instalarán OpenCV, Numpy, Matplotlib, entre otros.
¡Vamos con la programación!
Para una explicación más detallada del programa que veremos a continuación, por favor dirígete a la serie de videos que he subido en mi canal. En total son 3 videos en los que realizó el programa paso a paso. ¡Anímate a verlos todos!. 😉
📹 👁️ CONTADOR DE PARPADEOS 👁️ (Parte 1) | Python – MediaPipe Face Mesh – OpenCV
📹 👁️ CONTADOR DE PARPADEOS 👁️ (Parte 2) | Python – MediaPipe Face Mesh – OpenCV
📹 👁️ CONTADOR DE PARPADEOS 👁️ (Parte 3) | Python – MediaPipe Face Mesh – OpenCV
Empezamos importando todos los paquetes que vamos a usar.
import cv2 import mediapipe as mp import numpy as np import matplotlib.pyplot as plt from collections import deque
Luego vamos con la declaración de distintas funciones.
def drawing_output(frame, coordinates_left_eye, coordinates_right_eye, blink_counter):
aux_image = np.zeros(frame.shape, np.uint8)
contours1 = np.array([coordinates_left_eye])
contours2 = np.array([coordinates_right_eye])
cv2.fillPoly(aux_image, pts=[contours1], color=(255, 0, 0))
cv2.fillPoly(aux_image, pts=[contours2], color=(255, 0, 0))
output = cv2.addWeighted(frame, 1, aux_image, 0.7, 1)
cv2.rectangle(output, (0, 0), (200, 50), (255, 0, 0), -1)
cv2.rectangle(output, (202, 0), (265, 50), (255, 0, 0),2)
cv2.putText(output, "Num. Parpadeos:", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.putText(output, "{}".format(blink_counter), (220, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (128, 0, 250), 2)
return output
La función drawing_output nos permitirá aplicar cierto color al área de los ojos, y además la visualización de los resultados del conteo.
def eye_aspect_ratio(coordinates):
d_A = np.linalg.norm(np.array(coordinates[1]) - np.array(coordinates[5]))
d_B = np.linalg.norm(np.array(coordinates[2]) - np.array(coordinates[4]))
d_C = np.linalg.norm(np.array(coordinates[0]) - np.array(coordinates[3]))
return (d_A + d_B) / (2 * d_C)
La siguiente función es eye_aspect_ratio que es la que nos ayudará a calcular las 3 distancias (figura 4) que intervienen en el cálculo de la relación de aspecto de ojo, y nos devolverá el resultado de la ecuación descrita en la figura 2.

Figura 4: Distancias verticales y horizontal, necesarias para el cálculo de la relación de aspecto del ojo
def plotting_ear(pts_ear, line1):
global figure
pts = np.linspace(0, 1, 64)
if line1 == []:
plt.style.use("ggplot")
plt.ion()
figure, ax = plt.subplots()
line1, = ax.plot(pts, pts_ear)
plt.ylim(0.1, 0.4)
plt.xlim(0, 1)
plt.ylabel("EAR", fontsize=18)
else:
line1.set_ydata(pts_ear)
figure.canvas.draw()
figure.canvas.flush_events()
return line1
Finalmente tenemos a la función plotting_ear, la cual nos ayudará a generar la gráfica de la relación de aspecto de los ojos en tiempo real.
Una vez que contamos con estas funciones, vamos con el programa principal.
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
mp_face_mesh = mp.solutions.face_mesh
index_left_eye = [33, 160, 158, 133, 153, 144]
index_right_eye = [362, 385, 387, 263, 373, 380]
EAR_THRESH = 0.26
NUM_FRAMES = 2
aux_counter = 0
blink_counter = 0
line1 = []
pts_ear = deque(maxlen=64)
i = 0
with mp_face_mesh.FaceMesh(
static_image_mode=False,
max_num_faces=1) as face_mesh:
while True:
ret, frame = cap.read()
if ret == False:
break
frame = cv2.flip(frame, 1)
height, width, _ = frame.shape
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = face_mesh.process(frame_rgb)
coordinates_left_eye = []
coordinates_right_eye = []
if results.multi_face_landmarks is not None:
for face_landmarks in results.multi_face_landmarks:
for index in index_left_eye:
x = int(face_landmarks.landmark[index].x * width)
y = int(face_landmarks.landmark[index].y * height)
coordinates_left_eye.append([x, y])
cv2.circle(frame, (x, y), 2, (0, 255, 255), 1)
cv2.circle(frame, (x, y), 1, (128, 0, 250), 1)
for index in index_right_eye:
x = int(face_landmarks.landmark[index].x * width)
y = int(face_landmarks.landmark[index].y * height)
coordinates_right_eye.append([x, y])
cv2.circle(frame, (x, y), 2, (128, 0, 250), 1)
cv2.circle(frame, (x, y), 1, (0, 255, 255), 1)
En primer lugar indicamos que vamos a estar realizando un videostreaming. Luego declaramos algunas constantes y variables que nos servirán para el conteo de parpadeos, como el número mínimo de fotogramas para los ojos cerrados, el umbral a comparar con la relación de aspecto del ojo, el contador propiamente y también los puntos que estaremos tomando para los ojos izquierdo y derecho de la malla facial, a estos los tenemos en las líneas 51 y 52.
En la línea 61, establecemos las opciones de configuración que usaremos para el mallado facial con Mediapipe, y procedemos a leer los fotogramas que tendremos que transformar de BGR a RGB para aplicar esta solución como tal.
Entonces tendremos que asegurarnos de que obtengamos datos de la malla facial, es decir que se haya podido detectar algún rostro, y que de este se hayan extraído sus puntos. Si es así tomamos las 6 coordenadas x e y de cada ojo.
ear_left_eye = eye_aspect_ratio(coordinates_left_eye)
ear_right_eye = eye_aspect_ratio(coordinates_right_eye)
ear = (ear_left_eye + ear_right_eye)/2
# Ojos cerrados
if ear < EAR_THRESH:
aux_counter += 1
else:
if aux_counter >= NUM_FRAMES:
aux_counter = 0
blink_counter += 1
frame = drawing_output(frame, coordinates_left_eye, coordinates_right_eye, blink_counter)
pts_ear.append(ear)
if i > 70:
line1 = plotting_ear(pts_ear, line1)
i +=1
#print("pts_ear:", pts_ear)
cv2.imshow("Frame", frame)
k = cv2.waitKey(1) & 0xFF
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
Finalmente tendremos que calcular la relación de aspecto de los ojos, compararlos con el umbral que habíamos establecido previamente, identificar el número de frames en que los ojos se encuentran cerrados y detectar el parpadeo para visualizarlo en el contador. Esta sección usa todas las funciones que habíamos declarado en primer lugar.

Figura 5: Conteo de parpadeos y gráfica de la relación de aspacto de los ojos en tiempo real sobre un videostreaming.
Y bien, esto ha sido todo por el tutorial de hoy. ¡Espero que te haya gustado! 🙃 Nos vemos en el siguiente…
Referencias:
📎 http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf
📎 https://pyimagesearch.com/2017/04/24/eye-blink-detection-opencv-python-dlib/




Hola muchisimas gracias por tu generosidad,impulsa a que la personas cada día pueda crecer a nivel profesional un abrazo desde Colombia Sept,26,2023,Martes 5:12am
Excelente tu explicación…. Además, eres bellisíma…