#!/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()