Fetching latest headlinesโ€ฆ
EyeNet: Turning a Webcam Feed Into Actionable Security Incidents
NORTH AMERICA
๐Ÿ‡บ๐Ÿ‡ธ United Statesโ€ขJuly 6, 2026

EyeNet: Turning a Webcam Feed Into Actionable Security Incidents

0 views0 likes0 comments
Originally published byDev.to

I built a real-time campus surveillance system that turns raw video into actionable incidents โ€” here's how EyeNet works

Most "AI surveillance" demos I see online stop at object detection: a bounding box, a confidence score, done. That's not actually useful for a security team. A camera that just says "knife detected" a hundred times a second because of one flickering frame is worse than no camera at all.

So I built EyeNet โ€” a camera-first, real-time incident response system that treats detection as just the first step in a pipeline that ends in a structured, queryable, actionable event.

The problem

Talk to any campus security or discipline team and you'll hear the same pain points:

  • Humans can't watch 20 camera feeds at once, all day, every day.
  • Alerts happen over phone calls or WhatsApp โ€” no structure, no traceability, no history.
  • Naive CV systems throw false positives constantly unless you enforce persistence and cooldowns.
  • Nobody has a reviewable audit trail with snapshots when something does go wrong.

EyeNet's goal: turn a live video feed into structured incidents โ€” not noise.

Architecture

Camera โ†’ SharedFrameBuffer โ†’ DetectionPipeline โ†’ ObjectTracker โ†’ DetectionEvent
                                                                       โ†“
                                                              EventBus (priority queue)
                                                            โ†™        โ†“        โ†˜
                                                     DB Handler   Notifications   SSE โ†’ Dashboard

A few decisions I'd make again:

One camera reader, many consumers. SharedFrameBuffer owns /dev/video0 exclusively so the pipeline, dashboard stream, and any future consumer aren't fighting over the same device.

Multi-signal detection per frame, not just one model.

  • Face recognition against enrolled encodings (face-recognition / dlib), matched via L2 distance against a threshold.
  • Uniform compliance โ€” for known faces only, I estimate a torso ROI below the face box, convert to HSV, and check for light-blue fabric ratio. No fancy classifier needed for this one.
  • Hazard detection via YOLOv8, filtered through a keyword allow-list (knife, gun, fire, smoke, axe, sword...) with per-class confidence thresholds and a minimum bounding-box area to kill tiny false positives.

Tracking before alerting. Every detection goes through ByteTrack (via supervision, with a fallback ID scheme if it's not installed). A hazard or unknown face only becomes an alert once its track has persisted for a minimum number of frames โ€” and only once per track. This single decision killed the vast majority of my false-positive alerts.

Severity-aware event bus. Events aren't handled FIFO โ€” they go into a priority queue where CRITICAL (gun, fire, bomb) always jumps ahead of LOW (uniform violations). An in-process EventBus with multiple worker threads dispatches to registered handlers (DB, notifications, SSE) independently, so a slow SMTP call never blocks persistence.

Anomaly scoring, not just a label. Every event gets a 0โ€“100 urgency score from a base weight per detection type, the model's confidence, and how long the track has persisted. This score gets stored in the alert metadata and gates SMS notifications (only fires above a threshold).

Cooldowns at two levels. A system-wide cooldown keyed off severity (1 min for CRITICAL up to 24 hours for LOW) plus a separate in-memory cooldown inside the SMS sender keyed by event subtype. Belt and suspenders against alert spam.

Persistence & the dashboard

Alerts and metrics land in SQLite with WAL mode enabled โ€” cheap way to get decent concurrent-read behavior while the pipeline keeps writing. The Flask dashboard then gives you:

  • Live MJPEG video feed
  • Live alerts over Server-Sent Events (no websockets needed)
  • Alert filtering by type/severity, snapshot thumbnails, and an acknowledge button
  • Hourly analytics aggregated over the last 24 hours
  • A metrics panel polling fps, detection counts, and processing latency every few seconds

Auth is deliberately simple for now โ€” single bcrypt-hashed admin account, Flask session cookies. Multi-role permissions is on the roadmap, not solved yet.

Notifications

  • SMS via Twilio for unknown faces and hazards, gated by anomaly score โ‰ฅ 25
  • Email via SMTP for uniform violations, addressed to the student's derived institutional email

Stack

Python 3.9, Flask, OpenCV, Ultralytics YOLOv8, face-recognition (dlib), supervision for ByteTrack, SQLite (WAL), Twilio, bcrypt, Docker Compose with camera device passthrough.

What I'd tackle next

  • Swap the in-process SSE queue for something multi-process safe (Redis pub/sub) if this needs to scale past a single process
  • Proper RBAC instead of one admin account
  • Bind face encodings to the DB instead of a standalone pickle file
  • Rate limiting on the API routes
  • A real incident review workflow โ€” notes, assignment, escalation states โ€” instead of just acknowledge/dismiss

Demo

Here's a walkthrough of it running: youtube.com/watch?v=iarosznhHE8

Try it / poke at the code

Repo: github.com/SplinterSword/EyeNet

If you're working on anything similar โ€” event-driven CV pipelines, alert fatigue problems, or SQLite-under-concurrent-load setups โ€” I'd love to compare notes. Happy to answer questions about any of the design decisions above in the comments.

Comments (0)

Sign in to join the discussion

Be the first to comment!