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 state-of-the-art solution that offers excellent performance on CPU hardware.

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

Before implementing face detection, set up your development environment:

# Install required packages
pip install opencv-python numpy

# Download YuNet model
git clone https://github.com/opencv/opencv_zoo.git
cd opencv_zoo/models/face_detection_yunet/

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
)

# Initialize webcam
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # Set input size
    height, width, _ = frame.shape
    face_detector.setInputSize((width, height))

    # Detect faces
    _, faces = face_detector.detect(frame)

    # Draw results
    if faces is not None:
        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)

    cv2.imshow('Real-time Face Detection', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

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:

# 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:
    _, faces = face_detector.detect(frame)
    if faces is not None:
        for face in faces:
            confidence = face[4]
            if confidence > 0.7:  # Additional confidence filter
                box = face[0:4].astype(np.int32)
                landmarks = face[5:15].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, landmark, 2, (0, 255, 255), -1)
except Exception as e:
    print(f"Detection error: {e}")

This enhanced version includes landmark detection and proper error handling.

Optimizing performance

To achieve optimal performance in real-time applications:

  1. Resize input frames:
scale = 0.5
resized = cv2.resize(frame, None, fx=scale, fy=scale)
face_detector.setInputSize((resized.shape[1], resized.shape[0]))
  1. Process frames asynchronously:
from concurrent.futures import ThreadPoolExecutor

def process_frame(frame):
    _, faces = face_detector.detect(frame)
    return faces

with ThreadPoolExecutor(max_workers=2) as executor:
    future = executor.submit(process_frame, frame)
    faces = future.result()
  1. Implement frame skipping when needed:
frame_count = 0
process_every = 2  # Process every second frame

while True:
    ret, frame = cap.read()
    frame_count += 1

    if frame_count % process_every == 0:
        # Process frame
        _, faces = face_detector.detect(frame)

Practical use case: security monitoring

Here's a practical example of a security monitoring system that logs face detections:

from datetime import datetime
import json

class FaceMonitor:
    def __init__(self, log_file='face_detections.json'):
        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):
        _, faces = self.face_detector.detect(frame)
        if faces is not None:
            self.log_detection(len(faces))
        return faces

Performance considerations

YuNet offers impressive performance metrics:

  • CPU performance: 30-50 FPS on modern processors
  • Memory usage: Approximately 30MB
  • Detection accuracy: 95%+ on standard benchmarks

These metrics make it suitable for most real-time applications without requiring specialized hardware.

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.