Detección facial en tiempo real con YuNet de OpenCV
La detección facial en tiempo real es una herramienta potente para una gran variedad de aplicaciones, desde el monitoreo de seguridad hasta las experiencias de usuario interactivas. En este post exploramos cómo montar un pipeline de detección facial en streaming en Python con el detector YuNet de OpenCV, un modelo ligero diseñado para una inferencia eficiente en CPU.
¿Por qué detección facial en tiempo real?
Detectar rostros en tiempo real habilita diversas aplicaciones, desde sistemas de seguridad hasta instalaciones interactivas. Los algoritmos modernos de detección facial como YuNet ofrecen capacidades de detección robustas y mantienen un alto rendimiento, lo que los hace adecuados para entornos de producción.
Configurar tu entorno
Usa Python 3.10+ y OpenCV 4.8+ para el modelo YuNet de marzo de 2023. Instala Git LFS antes de clonar el repositorio del modelo y ejecuta los ejemplos desde el directorio del modelo que se indica abajo:
# Install required packages
python -m pip install 'opencv-python>=4.8,<5' numpy
# Download YuNet model
git lfs install
git clone https://github.com/opencv/opencv_zoo.git
cd opencv_zoo/models/face_detection_yunet/
git lfs pull --include='models/face_detection_yunet/face_detection_yunet_2023mar.onnx'
Usa una sola distribución de OpenCV para Python en este entorno. La ventana de la webcam requiere la compilación con GUI de arriba, una pantalla funcional y permiso para acceder a la cámara.
Crear un pipeline básico de detección facial en streaming
Aquí tienes un ejemplo completo que captura video desde tu webcam y procesa cada fotograma con YuNet:
import cv2
import numpy as np
# Initialize YuNet face detector
face_detector = cv2.FaceDetectorYN.create(
'face_detection_yunet_2023mar.onnx',
"",
(320, 320),
0.9, # score threshold
0.3, # nms threshold
5000 # top k
)
def draw_faces(frame, faces):
if faces is None:
return
for face in faces:
box = face[0:4].astype(np.int32)
cv2.rectangle(frame, (box[0], box[1]),
(box[0] + box[2], box[1] + box[3]),
(0, 255, 0), 2)
# Initialize webcam
cap = cv2.VideoCapture(0)
try:
if not cap.isOpened():
raise RuntimeError('Could not open webcam')
while True:
ret, frame = cap.read()
if not ret:
break
# Match the detector input to every frame
height, width = frame.shape[:2]
face_detector.setInputSize((width, height))
_, faces = face_detector.detect(frame)
draw_faces(frame, faces)
cv2.imshow('Real-time Face Detection', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
Este script inicializa el detector YuNet, captura fotogramas de video y dibuja recuadros delimitadores alrededor de los rostros detectados en tiempo real.
Uso avanzado: manejo de múltiples rostros y ajuste de parámetros
YuNet ofrece varios parámetros para afinar el rendimiento de la detección. Reemplaza la inicialización del detector de arriba con esta configuración y luego usa el bloque de detección y dibujo dentro del bucle de captura, conservando sus imports y su limpieza actuales:
# Advanced configuration
face_detector = cv2.FaceDetectorYN.create(
'face_detection_yunet_2023mar.onnx',
"",
(320, 320),
0.7, # Lower threshold for higher sensitivity
0.4, # Adjusted NMS threshold
10000 # Increased top k for more faces
)
# Error handling and face processing
try:
face_detector.setInputSize((frame.shape[1], frame.shape[0]))
_, faces = face_detector.detect(frame)
if faces is not None:
for face in faces:
confidence = face[14]
if confidence > 0.7: # Additional confidence filter
box = face[0:4].astype(np.int32)
landmarks = face[4:14].astype(np.int32).reshape(-1, 2)
# Draw face box
cv2.rectangle(frame, (box[0], box[1]),
(box[0] + box[2], box[1] + box[3]),
(0, 255, 0), 2)
# Draw landmarks
for landmark in landmarks:
cv2.circle(frame, tuple(landmark), 2, (0, 255, 255), -1)
except cv2.error as e:
raise RuntimeError('Face detection failed') from e
Cada detección tiene 15 valores: cuatro coordenadas del recuadro, diez coordenadas de puntos de referencia y la puntuación de confianza en el índice 14, tal como se documenta en el tutorial de detección facial de OpenCV.
Optimizar el rendimiento
Para lograr un rendimiento óptimo en aplicaciones en tiempo real:
-
Redimensiona los fotogramas de entrada:
scale = 0.5 resized = cv2.resize(frame, None, fx=scale, fy=scale) face_detector.setInputSize((resized.shape[1], resized.shape[0])) _, faces = face_detector.detect(resized) if faces is not None: # Convert boxes and landmarks back to the original frame's coordinates. faces[:, 0:14:2] *= frame.shape[1] / resized.shape[1] faces[:, 1:14:2] *= frame.shape[0] / resized.shape[0] -
Para imágenes independientes, dale a cada worker su propio detector. No compartas el detector mutable entre llamadas concurrentes. Este ejemplo acotado procesa dos fotogramas ya capturados; las aplicaciones en streaming también necesitan una cola acotada o una política para descartar fotogramas cuando el procesamiento se queda atrás. Crear un detector para cada imagen añade sobrecarga, así que mide si la concurrencia ayuda:
from concurrent.futures import ThreadPoolExecutor def process_frame(frame): detector = cv2.FaceDetectorYN.create( 'face_detection_yunet_2023mar.onnx', '', (frame.shape[1], frame.shape[0]), 0.9, 0.3, 5000 ) _, faces = detector.detect(frame) return faces def detect_pair(frame_a, frame_b): with ThreadPoolExecutor(max_workers=2) as executor: futures = [executor.submit(process_frame, image.copy()) for image in (frame_a, frame_b)] return [future.result() for future in futures] -
Implementa el salto de fotogramas cuando sea necesario. Reemplaza el bucle dentro del bloque
trydel primer ejemplo con este bucle, conservando su función auxiliardraw_facesy su limpiezafinally. Solo los fotogramas procesados muestran recuadros; los fotogramas omitidos se muestran sin detecciones obsoletas:frame_count = 0 process_every = 2 # Process every second frame while True: ret, frame = cap.read() if not ret: break frame_count += 1 if frame_count % process_every == 0: face_detector.setInputSize((frame.shape[1], frame.shape[0])) _, faces = face_detector.detect(frame) draw_faces(frame, faces) cv2.imshow('Real-time Face Detection', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break
Caso de uso práctico: monitoreo de seguridad
Aquí tienes un ejemplo práctico de un sistema de monitoreo de seguridad que registra las detecciones de rostros:
import cv2
from datetime import datetime
import json
class FaceMonitor:
def __init__(self, log_file='face_detections.jsonl'):
self.log_file = log_file
self.face_detector = cv2.FaceDetectorYN.create(
'face_detection_yunet_2023mar.onnx',
"",
(320, 320),
0.9,
0.3,
5000
)
def log_detection(self, num_faces):
entry = {
'timestamp': datetime.now().isoformat(),
'faces_detected': num_faces
}
with open(self.log_file, 'a') as f:
json.dump(entry, f)
f.write('\n')
def monitor(self, frame):
self.face_detector.setInputSize((frame.shape[1], frame.shape[0]))
_, faces = self.face_detector.detect(frame)
if faces is not None:
self.log_detection(len(faces))
return faces
Consideraciones de rendimiento
Mide el throughput y la memoria en tu hardware objetivo con la resolución real de los fotogramas.
La calidad de la detección también depende del tamaño del rostro, la iluminación, las oclusiones y
la configuración de los umbrales. El
repositorio del modelo YuNet
publica resultados de benchmarks; esos resultados no garantizan la misma precisión ni la misma tasa
de fotogramas con una transmisión de webcam. Usa un FaceMonitor distinto por
cada hilo de procesamiento.
Conclusión
Implementar detección facial en tiempo real se ha vuelto más accesible con herramientas modernas como el detector YuNet de OpenCV. La combinación de alto rendimiento y precisión lo convierte en una excelente opción para diversas aplicaciones. Si te interesa explorar soluciones automatizadas de análisis de imágenes a escala, revisa la documentación del servicio de IA de Transloadit.
