Real-time face detection with OpenCV's YuNet
Real-time face detection is a powerful tool for a variety of applications, from security monitoring to interactive user experiences. In this post, we explore how to set up a streaming face detection pipeline in Python using OpenCV's YuNet detector, a lightweight model designed for efficient CPU inference.
Why real-time face detection?
Detecting faces in real time enables various applications, from security systems to interactive installations. Modern face detection algorithms like YuNet provide robust detection capabilities while maintaining high performance, making them suitable for production environments.
Setting up your environment
Use Python 3.10+ and OpenCV 4.8+ for the March 2023 YuNet model. Install Git LFS before cloning the model repository, and run the examples from the model directory below:
# 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'
Use only one OpenCV Python distribution in this environment. The webcam window requires the GUI build above, a working display, and permission to access the camera.
Building a basic streaming face detection pipeline
Here's a complete example that captures video from your webcam and processes each frame using 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()
This script initializes the YuNet detector, captures video frames, and draws bounding boxes around detected faces in real-time.
Advanced usage: handling multiple faces and parameter adjustments
YuNet offers several parameters to fine-tune detection performance. Replace the detector initialization above with this configuration, then use the detection and drawing block inside the capture loop, keeping its existing imports and cleanup:
# 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
Each detection has 15 values: four box coordinates, ten landmark coordinates, and the confidence score at index 14, as documented in the OpenCV face detection tutorial.
Optimizing performance
To achieve optimal performance in real-time applications:
-
Resize input frames:
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] -
For independent images, give each worker its own detector. Do not share the mutable detector between concurrent calls. This bounded example processes two already captured frames; streaming applications also need a bounded queue or a policy to drop frames when processing falls behind. Creating a detector for each image adds overhead, so measure whether concurrency helps:
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] -
Implement frame skipping when needed. Replace the loop inside the first example's
tryblock with this loop, keeping itsdraw_faceshelper andfinallycleanup. Only processed frames show boxes; skipped frames are displayed without stale detections: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
Practical use case: security monitoring
Here's a practical example of a security monitoring system that logs face detections:
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
Performance considerations
Measure throughput and memory on your target hardware at the actual frame resolution. Detection
quality also depends on face size, lighting, occlusion, and threshold settings. The
YuNet model repository
publishes benchmark results; those results do not guarantee the same accuracy or frame rate for a
webcam feed. Use a separate FaceMonitor per processing thread.
Conclusion
Implementing real-time face detection has become more accessible with modern tools like OpenCV's YuNet detector. The combination of high performance and accuracy makes it an excellent choice for various applications. For those interested in exploring automated image analysis solutions at scale, check out Transloadit's AI service documentation.
