Spaces:
Running
Running
| """Tests for the face + expression detector. | |
| The detector depends only on numpy and OpenCV's cascade interface. We inject | |
| fake cascades so the tests are deterministic and need no real camera/images. | |
| """ | |
| import numpy as np | |
| import pytest | |
| from face_detection.detector import Face, FaceDetector | |
| class FakeCascade: | |
| """Mimics cv2.CascadeClassifier: returns canned detections per call.""" | |
| def __init__(self, *detections): | |
| # Each call to detectMultiScale pops the next canned result. | |
| self._results = list(detections) | |
| self.calls = [] | |
| def detectMultiScale(self, image, **kwargs): # noqa: N802 (cv2 naming) | |
| self.calls.append(image.shape) | |
| result = self._results.pop(0) if self._results else [] | |
| return np.array(result, dtype=int) if len(result) else np.empty((0, 4), int) | |
| def gray_frame(h=200, w=200): | |
| return np.zeros((h, w), dtype=np.uint8) | |
| def color_frame(h=200, w=200): | |
| return np.zeros((h, w, 3), dtype=np.uint8) | |
| def test_no_face_returns_empty_list(): | |
| det = FaceDetector(face_cascade=FakeCascade([]), smile_cascade=FakeCascade()) | |
| assert det.detect(gray_frame()) == [] | |
| def test_single_face_neutral_when_no_smile(): | |
| faces = FakeCascade([(10, 20, 50, 50)]) | |
| smiles = FakeCascade([]) # no smile inside the face ROI | |
| det = FaceDetector(face_cascade=faces, smile_cascade=smiles) | |
| result = det.detect(gray_frame()) | |
| assert len(result) == 1 | |
| assert isinstance(result[0], Face) | |
| assert result[0].bbox == (10, 20, 50, 50) | |
| assert result[0].expression == "neutral" | |
| def test_single_face_happy_when_smile_detected(): | |
| faces = FakeCascade([(10, 20, 50, 50)]) | |
| smiles = FakeCascade([(5, 30, 20, 10)]) # a smile within the ROI | |
| det = FaceDetector(face_cascade=faces, smile_cascade=smiles) | |
| result = det.detect(gray_frame()) | |
| assert result[0].expression == "happy" | |
| def test_multiple_faces_detected(): | |
| faces = FakeCascade([(0, 0, 40, 40), (100, 100, 30, 30)]) | |
| # One smile lookup per detected face; first happy, second neutral. | |
| smiles = FakeCascade([(1, 1, 5, 5)], []) | |
| det = FaceDetector(face_cascade=faces, smile_cascade=smiles) | |
| result = det.detect(gray_frame()) | |
| assert len(result) == 2 | |
| assert result[0].expression == "happy" | |
| assert result[1].expression == "neutral" | |
| def test_color_frame_is_converted_to_gray_before_detection(): | |
| faces = FakeCascade([(10, 20, 50, 50)]) | |
| det = FaceDetector(face_cascade=faces, smile_cascade=FakeCascade([])) | |
| det.detect(color_frame()) | |
| # The image handed to the cascade must be 2D (grayscale). | |
| assert len(faces.calls[0]) == 2 | |
| def test_face_center_helper(): | |
| face = Face(bbox=(10, 20, 40, 60), expression="neutral") | |
| assert face.center == (30, 50) # (10 + 40/2, 20 + 60/2) | |
| def test_largest_face_picks_biggest_bbox(): | |
| small = Face(bbox=(0, 0, 10, 10), expression="neutral") | |
| big = Face(bbox=(0, 0, 80, 80), expression="happy") | |
| assert FaceDetector.largest([small, big]) is big | |
| assert FaceDetector.largest([]) is None | |