I built a real-time campus surveillance system that turns noisy camera feeds into actionable security incidents
Most computer vision surveillance demos end the moment a model detects something.
A bounding box appears around a knife. A confidence score flashes on the screen. Maybe a label says “person” or “fire”.
And that’s usually where the demo stops.
The problem is that security teams don’t respond to detections—they respond to incidents.
Turning webcam input into security incidents is mostly a triage problem. The valuable part isn’t that an AI spotted something in a single frame; it’s preserving enough context for a human to quickly decide whether the event actually matters.
A camera that reports “knife detected” 60 times because one object stayed in frame isn’t helpful.
A camera that says:
Unknown individual entered Building A at 2:14 PM. The person remained in view for eight seconds, confidence 96%, snapshot attached, similar alert suppressed for the next five minutes.
…is something a security team can actually work with.
That idea became EyeNet.
Rather than treating object detection as the product, EyeNet treats it as the first stage of an incident-response pipeline that converts live video into structured, queryable events with context, persistence, prioritization, and notifications.
The problem
Campus security teams face a surprisingly familiar set of problems:
- Nobody can realistically monitor dozens of camera feeds all day.
- Alerts usually happen through phone calls or WhatsApp messages with little structure or history.
- Naive computer vision systems generate huge numbers of false positives.
- When an actual incident occurs, there’s often no searchable audit trail with snapshots or timestamps.
The challenge isn’t detecting objects.
It’s deciding when a detection deserves to become an incident.
Designing for triage instead of detection
That mindset influenced almost every architectural decision in EyeNet.
One camera reader, many consumers
Instead of letting multiple parts of the system compete for webcam access, a SharedFrameBuffer owns the camera and distributes frames to everyone else.
Camera
↓
SharedFrameBuffer
├── Detection Pipeline
├── Dashboard Stream
└── Future Consumers
Enter fullscreen mode Exit fullscreen mode
Adding another consumer doesn’t mean opening another camera stream.
Multiple signals, one decision
Every frame is analyzed by multiple independent detectors.
- Face recognition identifies enrolled students.
- Known students are checked for uniform compliance using HSV color analysis over the torso region.
- YOLOv8 detects hazards such as weapons, smoke, or fire.
Each detector contributes evidence.
None of them immediately generates an alert.
Tracking before alerting
One of the biggest improvements came from refusing to trust single-frame detections.
Hazards and unknown faces are tracked across frames using ByteTrack. Only detections that remain stable for a configurable number of frames become incidents.
That single design decision eliminated most false-positive alerts.
Instead of:
Frame 182
Knife detected
Alert!
Enter fullscreen mode Exit fullscreen mode
the pipeline behaves more like:
Frame 182
Possible knife
Frame 183
Still visible
Frame 184
Still visible
Frame 185
Track confirmed
→ Create incident
Enter fullscreen mode Exit fullscreen mode
Persistence becomes evidence.
Events instead of detections
Once a track qualifies, it becomes a structured DetectionEvent.
That event contains far more than “knife detected.”
It includes:
- timestamp
- severity
- anomaly score
- snapshot path
- tracking ID
- metadata
- acknowledgement status
From that point onward, the computer vision pipeline is finished.
Everything else works with incidents.
An event-driven architecture
EyeNet routes incidents through an in-process priority event bus.
Camera
↓
Detection Pipeline
↓
Object Tracker
↓
Detection Event
↓
Priority Event Bus
├── SQLite
├── Notifications
└── Live Dashboard
Enter fullscreen mode Exit fullscreen mode
Events aren’t processed FIFO.
Critical incidents like firearms or fire always jump ahead of uniform violations.
Handlers operate independently, so a slow SMTP request never blocks database writes or dashboard updates.
Reducing alert fatigue
Alert fatigue is a real operational problem.
EyeNet tries to avoid it in several ways.
Anomaly scoring
Each event receives a score between 0 and 100 based on:
- detection type
- model confidence
- track persistence
That score determines how aggressively the system responds.
For example, SMS notifications are only sent once an event crosses a configurable threshold.
Cooldowns
Two independent cooldown systems prevent repeated notifications.
A severity-based cooldown ensures the same incident isn’t repeatedly escalated.
The SMS service also maintains its own subtype cooldown so ongoing hazards don’t spam administrators every few seconds.
The goal is simple:
One incident.
One meaningful notification.
Persistence matters
Incidents aren’t ephemeral.
EyeNet stores alerts and metrics inside SQLite running in WAL mode, allowing the detection pipeline to keep writing while the dashboard performs concurrent reads.
The dashboard provides:
- live MJPEG camera streaming
- Server-Sent Events for new incidents
- snapshot thumbnails
- acknowledgement workflow
- filtering by severity and event type
- hourly analytics
- pipeline performance metrics
Instead of simply watching cameras, operators can review what happened, when it happened, and what actions have already been taken.
Notifications
Different incidents require different responses.
EyeNet currently supports:
- Twilio SMS for hazards and unknown visitors once anomaly scores exceed a threshold.
- SMTP email notifications for uniform violations, automatically routed to institutional student email addresses.
Tech stack
- Python
- Flask
- OpenCV
- Ultralytics YOLOv8
- face-recognition (dlib)
- ByteTrack via supervision
- SQLite (WAL mode)
- Twilio
- Docker Compose
What I’d improve next
If I continued working on EyeNet, I’d focus less on new detectors and more on improving incident management.
Some things on the roadmap include:
- Redis Pub/Sub instead of an in-process event bus
- Role-based access control
- Storing face encodings directly in the database
- API rate limiting
- A proper investigation workflow with assignments, notes, escalation states, and case history
Final thoughts
The biggest lesson from building EyeNet was that surveillance isn’t primarily a computer vision problem.
It’s an information triage problem.
The challenge isn’t getting a model to recognize a knife, a face, or a uniform.
The challenge is deciding when those detections become meaningful enough for a human to interrupt what they’re doing and respond.
That’s the gap I wanted EyeNet to close.
Demo: https://www.youtube.com/watch?v=iarosznhHE8
Repository: https://github.com/SplinterSword/EyeNet
If you’re building real-time CV systems, event-driven pipelines, or anything focused on reducing alert fatigue, I’d love to hear how you’re approaching the problem.
답글 남기기