"""Face detection.""" import cv2 import numpy as np from typing import List, Dict from pathlib import Path def load_detector( model_path: str, input_size: tuple, confidence_threshold: float = 0.8, nms_threshold: float = 0.3, top_k: int = 5000, ): """Load face detector.""" if not Path(model_path).exists(): return None try: return cv2.FaceDetectorYN.create( str(model_path), "", input_size, confidence_threshold, nms_threshold, top_k, ) except Exception: return None def detect( image: np.ndarray, detector, min_face_size: int = 60, margin: int = 5 ) -> List[Dict]: """Detect faces. Filter by min size and edge margin. Return list of {bbox, confidence}.""" if detector is None or image is None: return [] img_h, img_w = image.shape[:2] detector.setInputSize((img_w, img_h)) _, faces = detector.detect(image) if faces is None or len(faces) == 0: return [] detections = [] for face in faces: x, y, w, h = face[:4].astype(int) # FaceDetectorYN returns [num_faces, 15] [web:47] # In your previous code you used face[14]; keep it. conf = float(face[14]) # Clip bbox instead of dropping it (prevents "no faces" due to tiny out-of-bounds) x2 = x + w y2 = y + h x = max(0, x) y = max(0, y) x2 = min(img_w - 1, x2) y2 = min(img_h - 1, y2) w = max(1, x2 - x) h = max(1, y2 - y) # Edge margin filter dist_left = x dist_right = img_w - (x + w) dist_top = y dist_bottom = img_h - (y + h) if min(dist_left, dist_right, dist_top, dist_bottom) < margin: continue if w >= min_face_size and h >= min_face_size: detections.append( { "bbox": { "x": float(x), "y": float(y), "width": float(w), "height": float(h), }, "confidence": conf, } ) return detections