summaryrefslogtreecommitdiff
path: root/Biz/Kidcam/Detector.py
blob: f2af9ad4abccdfc035b1b0c2314d4f55b4e407d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env run.sh
"""Person detection module for Kidcam using YOLOv8-nano."""

# : out kidcam-detector
# : dep opencv-python
# : dep ultralytics
# : dep numpy
# : dep pytest
import cv2 as cv
import numpy as np
import sys


class PersonDetector:
    """Detect persons in video frames using YOLOv8-nano."""

    def __init__(
        self,
        model_path: str,
        confidence_threshold: float = 0.5,
    ) -> None:
        """Initialize person detector.

        Args:
            model_path: Path to YOLOv8 model file
            confidence_threshold: Minimum confidence for detection (0.0-1.0)
        """
        try:
            import ultralytics as ul

            self.model = ul.YOLO(model_path)
            self.confidence_threshold = confidence_threshold
            self.last_bbox: tuple[int, int, int, int] | None = None
        except ImportError as e:
            msg = "ultralytics library required for YOLOv8"
            raise ImportError(msg) from e

    def detect_person(self, frame: np.ndarray) -> bool:
        """Detect if person is present in frame.

        Args:
            frame: BGR image from OpenCV

        Returns:
            True if person detected above confidence threshold
        """
        results = self.model(frame, verbose=False)
        self.last_bbox = None

        for result in results:
            boxes = result.boxes
            for box in boxes:
                class_id = int(box.cls[0])
                confidence = float(box.conf[0])

                if class_id == 0 and confidence >= self.confidence_threshold:
                    x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
                    self.last_bbox = (int(x1), int(y1), int(x2), int(y2))
                    return True

        return False

    def get_last_detection_bbox(
        self,
    ) -> tuple[int, int, int, int] | None:
        """Get bounding box from last detection.

        Returns:
            Tuple of (x1, y1, x2, y2) or None if no recent detection
        """
        return self.last_bbox


def main() -> None:
    """Test person detection with webcam."""
    if "test" in sys.argv:
        test()
        return

    try:
        cap = cv.VideoCapture("/dev/video0")
        if not cap.isOpened():
            sys.exit(1)

        detector = PersonDetector("yolov8n.pt", confidence_threshold=0.5)

        while True:
            ret, frame = cap.read()
            if not ret:
                break

            if detector.detect_person(frame):
                bbox = detector.get_last_detection_bbox()
                if bbox:
                    x1, y1, x2, y2 = bbox
                    cv.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)

            cv.imshow("Kidcam - Person Detection", frame)

            if cv.waitKey(1) & 0xFF == ord("q"):
                break

    except FileNotFoundError:
        sys.exit(1)
    except Exception:
        sys.exit(1)
    finally:
        if "cap" in locals():
            cap.release()
        cv.destroyAllWindows()


def test() -> None:
    """Basic unit tests for PersonDetector."""
    test_frame = np.zeros((480, 640, 3), dtype=np.uint8)

    try:
        detector = PersonDetector("yolov8n.pt", confidence_threshold=0.5)
        assert detector.confidence_threshold == 0.5
        assert detector.get_last_detection_bbox() is None

        result = detector.detect_person(test_frame)
        assert isinstance(result, bool)

        bbox = detector.get_last_detection_bbox()
        assert bbox is None or (isinstance(bbox, tuple) and len(bbox) == 4)

    except ImportError:
        pass
    except Exception:
        sys.exit(1)


if __name__ == "__main__":
    main()