| """ |
| OpenCV Haar Cascade face detector. |
| |
| Uses cores.vision for all image operations — no duplicated logic. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import cv2 |
| import numpy as np |
|
|
| from config.settings import Settings, settings as _default_settings |
| from cores.vision import to_gray, BBox |
| from pipeline.feature_extraction import PipelineOutput |
| from providers.base import BaseProvider, ProviderCapability |
|
|
|
|
| class HaarDetector(BaseProvider): |
| name = "haar" |
| capability = ProviderCapability.DETECTION |
|
|
| def __init__(self, settings: Settings | None = None) -> None: |
| super().__init__(settings=settings or _default_settings) |
| cascade_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" |
| self._cascade = cv2.CascadeClassifier(cascade_path) |
| if self._cascade.empty(): |
| raise RuntimeError("Failed to load Haar cascade classifier.") |
|
|
| def is_available(self) -> bool: |
| return not self._cascade.empty() |
|
|
| def _run(self, pipeline_output: PipelineOutput) -> tuple[dict, dict]: |
| img: np.ndarray = pipeline_output.image |
| s = self._settings |
| gray = cv2.equalizeHist(to_gray(img)) |
| rects = self._cascade.detectMultiScale( |
| gray, |
| scaleFactor=s.haar_scale_factor, |
| minNeighbors=s.haar_min_neighbors, |
| minSize=(30, 30), |
| flags=cv2.CASCADE_SCALE_IMAGE, |
| ) |
| boxes = [BBox(int(x), int(y), int(w), int(h)).to_dict() for (x, y, w, h) in rects] |
| raw = { |
| "rectangles": [[int(x), int(y), int(w), int(h)] for (x, y, w, h) in rects], |
| "num_faces": len(rects), |
| } |
| normalized = { |
| "boxes": boxes, |
| "num_faces": len(boxes), |
| "landmarks": None, |
| "confidences": [1.0] * len(boxes), |
| } |
| return raw, normalized |
|
|