""" Face detector using OpenCV Haar Cascades. This module provides face detection functionality optimized for privacy blur in video editing workflows. """ import os from typing import List, Tuple, Optional import numpy as np class FaceDetector: """ Face detector using OpenCV Haar Cascades. Optimized for privacy blur use case: - Detects frontal faces - Configurable detection sensitivity - Generates feathered masks for smooth blur edges """ def __init__( self, scale_factor: float = 1.1, min_neighbors: int = 5, min_size: Tuple[int, int] = (30, 30), ): """ Initialize the face detector. Args: scale_factor: Image pyramid scale factor min_neighbors: Minimum neighbors for detection min_size: Minimum face size in pixels """ self.scale_factor = scale_factor self.min_neighbors = min_neighbors self.min_size = min_size self._classifier = None @property def classifier(self): """Lazy-load the Haar cascade classifier.""" if self._classifier is None: import cv2 # Use haarcascade for frontal face detection cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' if not os.path.exists(cascade_path): raise RuntimeError(f"Haar cascade not found: {cascade_path}") self._classifier = cv2.CascadeClassifier(cascade_path) return self._classifier def detect(self, frame: np.ndarray) -> List[Tuple[int, int, int, int]]: """ Detect faces in a frame. Args: frame: BGR image as numpy array Returns: List of face bounding boxes as (x, y, width, height) """ import cv2 # Convert to grayscale for detection gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Detect faces faces = self.classifier.detectMultiScale( gray, scaleFactor=self.scale_factor, minNeighbors=self.min_neighbors, minSize=self.min_size, flags=cv2.CASCADE_SCALE_IMAGE, ) # Convert to list of tuples return [tuple(face) for face in faces] def generate_mask( self, frame_shape: Tuple[int, int, int], detections: List[Tuple[int, int, int, int]], mask_scale: float = 1.5, feather_radius: int = 20, ) -> np.ndarray: """ Generate a mask image from face detections. Args: frame_shape: Shape of the original frame (height, width, channels) detections: List of face bounding boxes mask_scale: Scale factor for mask region (1.0 = exact bounding box) feather_radius: Radius for edge feathering Returns: Grayscale mask image (white = blur, black = keep) """ import cv2 height, width = frame_shape[:2] mask = np.zeros((height, width), dtype=np.uint8) for (x, y, w, h) in detections: # Scale the bounding box center_x = x + w // 2 center_y = y + h // 2 scaled_w = int(w * mask_scale) scaled_h = int(h * mask_scale) # Calculate scaled bounding box x1 = max(0, center_x - scaled_w // 2) y1 = max(0, center_y - scaled_h // 2) x2 = min(width, center_x + scaled_w // 2) y2 = min(height, center_y + scaled_h // 2) # Draw ellipse for more natural face shape cv2.ellipse( mask, (center_x, center_y), (scaled_w // 2, scaled_h // 2), 0, # angle 0, 360, # arc 255, # color (white) -1, # filled ) # Apply Gaussian blur for feathering if feather_radius > 0 and len(detections) > 0: # Ensure kernel size is odd kernel_size = feather_radius * 2 + 1 mask = cv2.GaussianBlur(mask, (kernel_size, kernel_size), 0) return mask def detect_faces_batch( frames: List[np.ndarray], detector: Optional[FaceDetector] = None, ) -> List[List[Tuple[int, int, int, int]]]: """ Detect faces in multiple frames. Args: frames: List of BGR images detector: Optional detector instance (creates one if not provided) Returns: List of detection lists, one per frame """ if detector is None: detector = FaceDetector() return [detector.detect(frame) for frame in frames]