Add TFLite object detection to reduce false positives
Motion detection now optionally runs MobileNet V2 SSD (COCO, quantized) on frames that trigger motion, identifying objects like people, cats, and cars. Events without detected objects are suppressed by default. Snapshots include bounding box annotations. New MCP tool vision_get_detections() enables label-based queries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ Runs as a service on Mac mini, listens for POSTs from Pis.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import sqlite3
|
||||
import base64
|
||||
import logging
|
||||
@@ -20,7 +21,7 @@ import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from contextlib import contextmanager
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Header
|
||||
@@ -87,6 +88,16 @@ def init_db():
|
||||
logger.info(f"Database initialized: {DB_PATH}")
|
||||
|
||||
|
||||
def migrate_db():
|
||||
"""Add new columns if they don't exist (idempotent)"""
|
||||
with get_db() as conn:
|
||||
columns = [row[1] for row in conn.execute("PRAGMA table_info(events)").fetchall()]
|
||||
if "detections" not in columns:
|
||||
conn.execute("ALTER TABLE events ADD COLUMN detections TEXT")
|
||||
conn.commit()
|
||||
logger.info("Migration: added 'detections' column to events table")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
"""Database connection context manager"""
|
||||
@@ -171,6 +182,12 @@ def stop_cleanup_thread():
|
||||
|
||||
# === Models ===
|
||||
|
||||
class DetectionItem(BaseModel):
|
||||
label: str
|
||||
confidence: float
|
||||
bbox: List[float]
|
||||
|
||||
|
||||
class EventData(BaseModel):
|
||||
timestamp: str
|
||||
camera_id: str
|
||||
@@ -178,6 +195,7 @@ class EventData(BaseModel):
|
||||
confidence: float = 0.0
|
||||
region: str = "full"
|
||||
area_percent: float = 0.0
|
||||
detections: Optional[List[DetectionItem]] = None
|
||||
|
||||
|
||||
class IncomingEvent(BaseModel):
|
||||
@@ -190,6 +208,7 @@ class IncomingEvent(BaseModel):
|
||||
@app.on_event("startup")
|
||||
def startup():
|
||||
init_db()
|
||||
migrate_db()
|
||||
start_cleanup_thread()
|
||||
logger.info(f"🦊 Event collector started on port {PORT}")
|
||||
logger.info(f" Data directory: {DATA_DIR}")
|
||||
@@ -257,14 +276,19 @@ def receive_event(
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save snapshot: {e}")
|
||||
|
||||
# Serialize detections to JSON if present
|
||||
detections_json = None
|
||||
if event.detections:
|
||||
detections_json = json.dumps([d.model_dump() for d in event.detections])
|
||||
|
||||
# Store in database
|
||||
try:
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
INSERT INTO events
|
||||
(event_id, timestamp, camera_id, event_type, confidence,
|
||||
area_percent, snapshot_path, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO events
|
||||
(event_id, timestamp, camera_id, event_type, confidence,
|
||||
area_percent, snapshot_path, detections, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
event_id,
|
||||
event.timestamp,
|
||||
@@ -273,6 +297,7 @@ def receive_event(
|
||||
event.confidence,
|
||||
event.area_percent,
|
||||
snapshot_path,
|
||||
detections_json,
|
||||
now.isoformat() + "Z"
|
||||
))
|
||||
conn.commit()
|
||||
@@ -357,10 +382,15 @@ def get_stats():
|
||||
FROM events GROUP BY event_type
|
||||
""").fetchall()
|
||||
|
||||
with_detections = conn.execute(
|
||||
"SELECT COUNT(*) FROM events WHERE detections IS NOT NULL"
|
||||
).fetchone()[0]
|
||||
|
||||
return {
|
||||
"total_events": total,
|
||||
"annotated": annotated,
|
||||
"unannotated": total - annotated,
|
||||
"with_detections": with_detections,
|
||||
"by_camera": {row[0]: row[1] for row in by_camera},
|
||||
"by_type": {row[0]: row[1] for row in by_type},
|
||||
"data_dir": str(DATA_DIR),
|
||||
|
||||
Reference in New Issue
Block a user