Detección facial en tiempo real con YuNet y automatización CLI
YuNet es un detector facial compacto disponible a través de OpenCV. Una CLI pequeña puede usarlo para anotar imágenes y videos locales, con salidas por lotes predecibles y fallos verificados. La detección encuentra regiones con apariencia de rostro; no identifica personas ni ofrece una decisión de autenticación.
Comprende las fortalezas de YuNet
YuNet ofrece una opción ligera orientada a CPU para detectar rostros y puntos de referencia faciales. La precisión y la velocidad dependen del tamaño de la imagen, el tamaño del rostro, la pose, la iluminación, el umbral y el hardware. Consulta la documentación del modelo de OpenCV Zoo para conocer el modelo y su evaluación publicada; mide tu propia carga de trabajo antes de dar garantías de tiempo real o de precisión.
Configura YuNet rápidamente
Crea un entorno de Python e instala un solo wheel de OpenCV. Esta CLI no abre ventanas de
interfaz gráfica, por lo que usa el paquete headless; no instales además opencv-python en el mismo entorno.
python3 -m venv venv
. venv/bin/activate
pip install opencv-python-headless==5.0.0.93
Descarga los bytes del modelo probado, no un puntero de Git LFS. Este comando fija la revisión de OpenCV Zoo:
curl --fail --location --output face_detection_yunet.onnx \
https://media.githubusercontent.com/media/opencv/opencv_zoo/47534e27c9851bb1128ccc0102f1145e27f23f98/models/face_detection_yunet/face_detection_yunet_2023mar.onnx
El modelo tiene 232.589 bytes con SHA-256
8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4. Verifícalo con sha256sum en
Linux o con shasum -a 256 en macOS. Mantén el modelo junto a los scripts. Usa una versión de
Python compatible y con soporte para el wheel fijado, y mantén reproducible el entorno resultante.
Automatiza la detección en una sola imagen
Guarda esta implementación compartida como face_tools.py. Usa un fotograma redimensionado real para
la inferencia y reasigna los cuadros delimitadores a la imagen original, en lugar de cambiar el
ajuste de tamaño del detector sin redimensionar su entrada.
from contextlib import contextmanager
import math
import os
from pathlib import Path
from tempfile import TemporaryDirectory
# Set before importing OpenCV so image decoding has an explicit pixel ceiling.
os.environ.setdefault('OPENCV_IO_MAX_IMAGE_PIXELS', '20000000')
import cv2
MODEL_PATH = Path(__file__).with_name('face_detection_yunet.onnx')
def create_detector():
return cv2.FaceDetectorYN.create(str(MODEL_PATH), '', (320, 320), 0.9, 0.3, 5000)
def annotate(detector, image, max_side=960):
height, width = image.shape[:2]
if width * height > 20_000_000:
raise ValueError('Frame exceeds the pixel limit')
scale = min(1.0, max_side / max(width, height))
small = cv2.resize(image, (max(1, round(width * scale)), max(1, round(height * scale))))
detector.setInputSize((small.shape[1], small.shape[0]))
_, faces = detector.detect(small)
count = 0 if faces is None else len(faces)
if faces is not None:
scale_x = width / small.shape[1]
scale_y = height / small.shape[0]
for face in faces:
x, y, w, h = face[:4]
left, top = max(0, round(x * scale_x)), max(0, round(y * scale_y))
right = min(width - 1, round((x + w) * scale_x))
bottom = min(height - 1, round((y + h) * scale_y))
cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 2)
return image, count
@contextmanager
def new_output(path):
output = Path(path).absolute()
if output.exists() or output.is_symlink():
raise ValueError('Output must not exist')
with TemporaryDirectory(prefix='.face-output-', dir=output.parent) as temporary:
candidate = Path(temporary) / ('result' + output.suffix)
yield candidate
if not candidate.is_file() or candidate.stat().st_size == 0:
raise ValueError('No nonempty output produced')
os.link(candidate, output)
def detect_image(detector, source, output):
source = Path(source)
if not source.is_file() or source.stat().st_size > 50 * 1024 * 1024:
raise ValueError('Use a regular image file no larger than 50 MiB')
image = cv2.imread(str(source))
if image is None:
raise ValueError('Cannot decode image')
annotated, count = annotate(detector, image)
with new_output(output) as candidate:
if not cv2.imwrite(str(candidate), annotated):
raise ValueError('Cannot encode output image')
return count
def detect_video(detector, source, output):
if not Path(source).is_file():
raise ValueError('Use a local video file')
capture = cv2.VideoCapture(str(Path(source).absolute()))
writer = None
frames = 0
detections = 0
try:
if not capture.isOpened():
raise ValueError('Cannot open video')
fps = capture.get(cv2.CAP_PROP_FPS)
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
if not math.isfinite(fps) or fps <= 0 or width <= 0 or height <= 0:
raise ValueError('Invalid video timing or dimensions')
if width * height > 20_000_000 or width % 2 or height % 2:
raise ValueError('Use bounded, even video dimensions')
with new_output(output) as candidate:
writer = cv2.VideoWriter(str(candidate), cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
if not writer.isOpened():
raise ValueError('Cannot open video encoder')
while True:
ok, frame = capture.read()
if not ok:
break
if frame.shape[:2] != (height, width):
raise ValueError('Frame dimensions changed')
annotated, count = annotate(detector, frame)
writer.write(annotated)
frames += 1
detections += count
writer.release()
writer = None
if frames == 0:
raise ValueError('No frames decoded')
return frames, detections
finally:
capture.release()
if writer is not None:
writer.release()
Guarda este invocador como detect_faces.py en el mismo directorio:
import argparse
import sys
from face_tools import create_detector, detect_image, detect_video
import cv2
parser = argparse.ArgumentParser()
parser.add_argument('mode', choices=['image', 'video'])
parser.add_argument('input')
parser.add_argument('output')
args = parser.parse_args()
try:
detector = create_detector()
if args.mode == 'image':
print(f'Detected {detect_image(detector, args.input, args.output)} faces')
else:
frames, detections = detect_video(detector, args.input, args.output)
print(f'Processed {frames} frames with {detections} face detections')
except (OSError, ValueError, cv2.error):
sys.exit('Face detection failed; check input, model, codec, and destination')
Ejecuta python detect_faces.py image photo.jpg annotated.jpg. Una imagen válida sin rostros detectados
igualmente produce una salida e informa cero detecciones. El sistema de archivos de salida debe
admitir enlaces duros; la publicación nunca reemplaza los archivos existentes.
Procesa carpetas por lotes
Reutiliza el mismo detector de forma secuencial en lugar de enviar una lambda no serializable con
pickle a un grupo de procesos o dejar que cada entrada sobrescriba output.jpg. Guarda esto como batch_detect.py:
import argparse
from pathlib import Path
import sys
from face_tools import create_detector, detect_image
import cv2
parser = argparse.ArgumentParser()
parser.add_argument('input_directory')
parser.add_argument('output_directory')
args = parser.parse_args()
try:
source = Path(args.input_directory)
images = sorted(path for path in source.iterdir() if path.is_file() and not path.is_symlink()
and path.suffix.lower() in {'.jpg', '.jpeg', '.png'})
if not images:
sys.exit('No supported images found')
detector = create_detector()
output = Path(args.output_directory)
output.mkdir(mode=0o700)
failures = 0
for image in images:
try:
count = detect_image(detector, image, output / (image.name + '.jpg'))
print(f'{image.name}: {count} faces')
except (OSError, ValueError, cv2.error):
failures += 1
print(f'Failed: {image.name}', file=sys.stderr)
sys.exit(1 if failures else 0)
except (OSError, ValueError, cv2.error):
sys.exit('Cannot prepare batch; check model and directories')
Ejecuta python batch_detect.py images new-results. Los nombres completos de los archivos de origen se conservan
antes de añadir .jpg, de modo que photo.jpg y photo.png obtienen salidas separadas. Los archivos
fallidos cuentan para un estado de lote distinto de cero; los resultados exitosos siguen
disponibles.
Maneja flujos de video
Ejecuta python detect_faces.py video input.mp4 annotated.mp4 para un video local con tasa de fotogramas constante.
La salida contiene únicamente video anotado: el audio no se copia. El total de detecciones
cuenta observaciones a lo largo de los fotogramas, no personas distintas. La sincronización con tasa
de fotogramas variable y la reconexión de una cámara en vivo requieren un flujo de trabajo
diferente.
Una lectura de fotograma fallida en OpenCV puede significar el fin del archivo o un problema del
decodificador, y VideoWriter.write no informa el éxito de cada fotograma. Revisa el recuento de fotogramas
resultante, la duración y la reproducción antes de publicar. El ejemplo comprueba la inicialización
y que la salida no esté vacía, no la integridad completa del archivo multimedia.
Ajusta la velocidad para tiempo real
Reduce max_side en annotate para redimensionar realmente los fotogramas de inferencia y luego
mide el efecto en los rostros pequeños y lejanos. Mantén un detector por proceso de trabajo; no lo
compartas entre llamadas simultáneas. Un wheel para CPU no ofrece soporte de CUDA de forma
automática. La aceleración por GPU requiere una compilación de OpenCV compatible, hardware y una
configuración de backend validada por separado.
Para contenido multimedia no confiable, usa procesos de trabajo aislados y límites de tiempo, memoria y disco para el trabajo completo. Las comprobaciones de bytes y de píxeles de la imagen no limitan cada asignación del decodificador de video ni el tiempo total de procesamiento.
Compara brevemente detectores alternativos
| Detector | Punto de partida útil | Limitación importante |
|---|---|---|
| YuNet | Detección facial compacta a través de OpenCV | La precisión depende de la escala, la pose y el umbral |
| Modelos faciales DNN de OpenCV | Un modelo preentrenado o backend diferente | Verifica el modelo exacto y el preprocesamiento |
| Cascadas de Haar | Flujos de trabajo heredados de detección ligera | A menudo, menos robustas ante la variación de pose e iluminación |
Compara los candidatos con los mismos datos representativos en lugar de tratar las afirmaciones cualitativas de velocidad como un punto de referencia.
Conclusión
Un módulo auxiliar de detector compartido mantiene coherentes los flujos de trabajo de imágenes, carpetas y video, a la vez que conserva las salidas y expone los fallos. Verifica el modelo fijado y las anotaciones reales; luego mide el rendimiento y la precisión que requiere tu aplicación. Para flujos de trabajo de visión gestionados, consulta el servicio de Inteligencia artificial de Transloadit.
