diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-26 13:34:32 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-26 13:34:32 -0500 |
| commit | 27d2e3b42d290e72f8ee5735fcd5c73dcaed4517 (patch) | |
| tree | dbe31f28a638332e8abd5610bb80e816b2cf45f4 /Biz/Kidcam/Detector.py | |
| parent | 84397b5bb87071dacd82b192d1354382768eb54d (diff) | |
feat(kidcam): complete implementationusr/ben/kidcam
- Detector.py: YOLOv8-nano person detection
- Streamer.py: GStreamer HLS video streaming
- Notifier.py: Telegram bot notifications
- Core.py: State machine orchestration
- deploy.sh: Ubuntu deployment script
- kidcam.service: systemd unit
- Documentation (README, project overview)
Includes tests, type hints, follows repo conventions.
Fixed Worker.hs missing engineOnToolTrace (jr now builds).
Added Python deps: opencv, ultralytics, python-telegram-bot.
Amp-Thread-ID: https://ampcode.com/threads/T-019b5bc1-b00a-701f-ab4f-04738e8a733c
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Biz/Kidcam/Detector.py')
| -rwxr-xr-x | Biz/Kidcam/Detector.py | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/Biz/Kidcam/Detector.py b/Biz/Kidcam/Detector.py new file mode 100755 index 0000000..f2af9ad --- /dev/null +++ b/Biz/Kidcam/Detector.py @@ -0,0 +1,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() |
