Wire detection pipeline: throttle YOLO, save crops, insert sightings, route person/vehicle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-04-03 13:46:38 -04:00
parent c77f732ac7
commit 6771923585
2 changed files with 63 additions and 3 deletions

View File

@ -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

View File

@ -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)