""" Face Detection Module Uses OpenCV's DNN face detector for robust face detection """ import cv2 import numpy as np import logging from typing import List, Tuple, Optional logger = logging.getLogger(__name__) class FaceDetector: """ Face detector using OpenCV DNN module with pre-trained models """ def __init__(self, confidence_threshold: float = 0.5): """ Initialize face detector Args: confidence_threshold: Minimum confidence for face detection """ self.confidence_threshold = confidence_threshold self.net = None self._load_model() def _load_model(self): """Load pre-trained face detection model""" try: # Using OpenCV's DNN face detector (Caffe model) # This is a lightweight and efficient model prototxt_path = "models/deploy.prototxt" model_path = "models/res10_300x300_ssd_iter_140000.caffemodel" # Try to load from local files first try: self.net = cv2.dnn.readNetFromCaffe(prototxt_path, model_path) logger.info("✓ Loaded face detection model from local files") except: # Fallback: use Haar Cascade (built-in to OpenCV) logger.warning("DNN model not found, using Haar Cascade fallback") self.face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) self.net = None except Exception as e: logger.error(f"Error loading face detection model: {e}") # Use Haar Cascade as fallback self.face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) self.net = None def detect_faces(self, image: np.ndarray) -> List[Tuple[int, int, int, int]]: """ Detect faces in an image Args: image: Input image (BGR format) Returns: List of face bounding boxes [(x, y, w, h), ...] """ if image is None or image.size == 0: logger.warning("Empty image provided") return [] try: if self.net is not None: return self._detect_dnn(image) else: return self._detect_haar(image) except Exception as e: logger.error(f"Face detection error: {e}") return [] def _detect_dnn(self, image: np.ndarray) -> List[Tuple[int, int, int, int]]: """Detect faces using DNN model""" h, w = image.shape[:2] # Prepare blob for DNN blob = cv2.dnn.blobFromImage( cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0) ) self.net.setInput(blob) detections = self.net.forward() faces = [] for i in range(detections.shape[2]): confidence = detections[0, 0, i, 2] if confidence > self.confidence_threshold: box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (x1, y1, x2, y2) = box.astype("int") # Convert to (x, y, w, h) format x = max(0, x1) y = max(0, y1) w = min(image.shape[1] - x, x2 - x1) h = min(image.shape[0] - y, y2 - y1) if w > 0 and h > 0: faces.append((x, y, w, h)) return faces def _detect_haar(self, image: np.ndarray) -> List[Tuple[int, int, int, int]]: """Detect faces using Haar Cascade""" gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE ) return [tuple(face) for face in faces] def assess_face_quality(self, image: np.ndarray, face: Tuple[int, int, int, int]) -> float: """ Assess the quality of detected face Args: image: Input image face: Face bounding box (x, y, w, h) Returns: Quality score between 0 and 1 """ try: x, y, w, h = face face_roi = image[y:y+h, x:x+w] if face_roi.size == 0: return 0.0 # Convert to grayscale gray_face = cv2.cvtColor(face_roi, cv2.COLOR_BGR2GRAY) # 1. Size score (larger faces are better) size_score = min(1.0, (w * h) / (image.shape[0] * image.shape[1] * 0.5)) # 2. Sharpness score (using Laplacian variance) laplacian_var = cv2.Laplacian(gray_face, cv2.CV_64F).var() sharpness_score = min(1.0, laplacian_var / 500.0) # 3. Brightness score mean_brightness = np.mean(gray_face) brightness_score = 1.0 - abs(mean_brightness - 127.5) / 127.5 # 4. Contrast score contrast = gray_face.std() contrast_score = min(1.0, contrast / 64.0) # Weighted average quality = ( size_score * 0.3 + sharpness_score * 0.3 + brightness_score * 0.2 + contrast_score * 0.2 ) return quality except Exception as e: logger.error(f"Quality assessment error: {e}") return 0.0 def draw_faces(self, image: np.ndarray, faces: List[Tuple[int, int, int, int]]) -> np.ndarray: """ Draw bounding boxes around detected faces Args: image: Input image faces: List of face bounding boxes Returns: Image with drawn bounding boxes """ output = image.copy() for (x, y, w, h) in faces: cv2.rectangle(output, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText( output, "Face", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2 ) return output def extract_face_roi(self, image: np.ndarray, face: Tuple[int, int, int, int], padding: float = 0.2) -> Optional[np.ndarray]: """ Extract face region of interest with padding Args: image: Input image face: Face bounding box (x, y, w, h) padding: Padding ratio around face Returns: Face ROI image or None """ try: x, y, w, h = face # Add padding pad_w = int(w * padding) pad_h = int(h * padding) x1 = max(0, x - pad_w) y1 = max(0, y - pad_h) x2 = min(image.shape[1], x + w + pad_w) y2 = min(image.shape[0], y + h + pad_h) face_roi = image[y1:y2, x1:x2] return face_roi if face_roi.size > 0 else None except Exception as e: logger.error(f"Face ROI extraction error: {e}") return None