diff --git a/vigilar/camera/worker.py b/vigilar/camera/worker.py index 0b27d97..0781478 100644 --- a/vigilar/camera/worker.py +++ b/vigilar/camera/worker.py @@ -21,6 +21,7 @@ from vigilar.camera.recorder import AdaptiveRecorder from vigilar.camera.ring_buffer import RingBuffer from vigilar.config import CameraConfig, MQTTConfig, PetsConfig, RemoteConfig from vigilar.constants import Topics +from vigilar.detection.crop_manager import CropManager from vigilar.detection.pet_id import PetIDClassifier from vigilar.detection.wildlife import classify_wildlife_threat from vigilar.detection.yolo import YOLODetector @@ -113,6 +114,7 @@ def run_camera_worker( # Object detection (YOLOv8 unified detector) yolo_detector = None pet_classifier = None + crop_manager = None if pets_cfg and pets_cfg.enabled: yolo_detector = YOLODetector( model_path=pets_cfg.model_path, @@ -124,9 +126,14 @@ def run_camera_worker( high_threshold=pets_cfg.pet_id_threshold, low_threshold=pets_cfg.pet_id_low_confidence, ) + crop_manager = CropManager( + staging_dir=pets_cfg.crop_staging_dir, + training_dir=pets_cfg.training_dir, + ) state = CameraState() shutdown = False + last_detection_time: float = 0 def handle_signal(signum, frame): nonlocal shutdown @@ -261,15 +268,24 @@ def run_camera_worker( if state.frame_count % idle_skip_factor == 0: recorder.write_frame(frame) - # Run object detection on motion frames - if state.motion_active and yolo_detector and yolo_detector.is_loaded: + # Run object detection on motion frames — throttled to 1 inference/second + if (state.motion_active and yolo_detector and yolo_detector.is_loaded + and now - last_detection_time >= 1.0): + last_detection_time = now detections = yolo_detector.detect(frame) for det in detections: category = YOLODetector.classify(det) if category == "domestic_animal": - # Crop for pet ID + # Crop for pet ID and staging x, y, w, h = det.bbox crop = frame[max(0, y):y + h, max(0, x):x + w] + + crop_path = None + if crop_manager and crop.size > 0: + crop_path = crop_manager.save_staging_crop( + crop, species=det.class_name, camera_id=camera_id + ) + pet_result = None if pet_classifier and pet_classifier.is_loaded and crop.size > 0: pet_result = pet_classifier.identify(crop, species=det.class_name) @@ -278,6 +294,7 @@ def run_camera_worker( "species": det.class_name, "confidence": round(det.confidence, 3), "camera_location": camera_cfg.location, + "crop_path": crop_path, } if pet_result and pet_result.is_identified: payload["pet_id"] = pet_result.pet_id @@ -302,6 +319,21 @@ def run_camera_worker( threat_level=threat_level, confidence=round(det.confidence, 3), camera_location=camera_cfg.location, + crop_path=None, + ) + + elif category == "person": + bus.publish_event( + Topics.camera_motion_start(camera_id), + detection="person", + confidence=round(det.confidence, 3), + ) + + elif category == "vehicle": + bus.publish_event( + Topics.camera_motion_start(camera_id), + detection="vehicle", + confidence=round(det.confidence, 3), ) # Heartbeat every 10 seconds diff --git a/vigilar/events/processor.py b/vigilar/events/processor.py index 2d75c54..e860ead 100644 --- a/vigilar/events/processor.py +++ b/vigilar/events/processor.py @@ -101,6 +101,34 @@ class EventProcessor: payload=payload, ) + # Insert pet/wildlife sightings + if event_type in ( + EventType.PET_DETECTED, EventType.PET_ESCAPE, EventType.UNKNOWN_ANIMAL + ): + from vigilar.storage.queries import insert_pet_sighting + insert_pet_sighting( + engine, + pet_id=payload.get("pet_id"), + species=payload.get("species", "unknown"), + camera_id=source_id or "", + confidence=payload.get("confidence", 0.0), + crop_path=payload.get("crop_path"), + event_id=event_id, + ) + elif event_type in ( + EventType.WILDLIFE_PREDATOR, EventType.WILDLIFE_NUISANCE, EventType.WILDLIFE_PASSIVE + ): + from vigilar.storage.queries import insert_wildlife_sighting + insert_wildlife_sighting( + engine, + species=payload.get("species", "unknown"), + threat_level=payload.get("threat_level", "PASSIVE"), + camera_id=source_id or "", + confidence=payload.get("confidence", 0.0), + crop_path=payload.get("crop_path"), + event_id=event_id, + ) + # Evaluate rules actions = rule_engine.evaluate(topic, payload, fsm.state)