Spaces:
Running
Running
| import cv2 | |
| import numpy as np | |
| class FaceDetector: | |
| """ | |
| Detects faces in images/frames using OpenCV's Haar Cascade classifier. | |
| """ | |
| def __init__(self): | |
| # Path to Haar cascade file included in opencv-python | |
| cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' | |
| self.face_cascade = cv2.CascadeClassifier(cascade_path) | |
| if self.face_cascade.empty(): | |
| raise RuntimeError("Failed to load Haar cascade for face detection.") | |
| def detect_faces(self, frame_rgb: np.ndarray) -> list[dict]: | |
| """ | |
| Detects faces in an RGB image. | |
| Returns a list of dicts: {'box': [x, y, w, h], 'face_crop': np.ndarray} | |
| """ | |
| # OpenCV Haar cascade expects grayscale and BGR layout for speed | |
| # But we pass RGB, so convert to grayscale directly | |
| gray = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY) | |
| # Detect faces | |
| faces = self.face_cascade.detectMultiScale( | |
| gray, | |
| scaleFactor=1.1, | |
| minNeighbors=8, | |
| minSize=(60, 60), | |
| flags=cv2.CASCADE_SCALE_IMAGE | |
| ) | |
| detected = [] | |
| for (x, y, w, h) in faces: | |
| # Add small padding to crop to capture full facial features | |
| pad_h = int(h * 0.1) | |
| pad_w = int(w * 0.1) | |
| y_start = max(0, y - pad_h) | |
| y_end = min(frame_rgb.shape[0], y + h + pad_h) | |
| x_start = max(0, x - pad_w) | |
| x_end = min(frame_rgb.shape[1], x + w + pad_w) | |
| face_crop = frame_rgb[y_start:y_end, x_start:x_end] | |
| detected.append({ | |
| "box": [int(x), int(y), int(w), int(h)], | |
| "padded_box": [int(x_start), int(y_start), int(x_end - x_start), int(y_end - y_start)], | |
| "face_crop": face_crop | |
| }) | |
| return detected | |