Training a fire and smoke detector for multi-camera surveillance

Notes from building the detection layer of VigiLense, my final year project: training YOLO on D-Fire, reading the metrics honestly, and turning detections into alerts people can act on.

VigiLense is my final year project at Bahria University: an AI surveillance and incident response system that watches several camera feeds, spots dangerous events, and gets the right footage in front of a human fast. I'm on a team of three, and I own the detection layer. This post covers the first model we finished, fire and smoke, and how its output becomes an alert.

Choosing the model and data

Surveillance runs on many feeds at once, often on modest hardware, so speed mattered as much as accuracy. I picked the nano variant of Ultralytics YOLO (yolo26n) as the base and fine-tuned it on D-Fire, a public dataset of fire and smoke images published by Venâncio et al. in 2022.

Training ran on Google Colab with a T4 GPU, with checkpoints saved to a shared Drive so the whole team could pull the same weights.

Results, read honestly

Metric (test set)ValueWhat it means here
mAP500.780Overall detection quality at 50% box overlap
Precision0.783About 4 in 5 alerts are real fire or smoke
Recall0.716Catches about 7 in 10 fire or smoke instances

For a nano model this is a solid start, but recall is the number I care about most. In a safety system a missed fire is worse than a false alarm. Two things help in practice:

  • Video isn't a single image. Fire and smoke persist across many frames, so a per-frame miss rarely means a missed incident.
  • A tunable threshold. The confidence cut-off defaults to 0.35 and can be lowered for higher-risk areas, trading more false alarms for fewer misses.

From detections to alerts

A model that draws boxes isn't a security system yet. The pipeline around it is what makes it useful:

camera 1 ┐                                   ┌─> SQLite incident log + snapshots
camera 2 ├─> YOLO (persons + fire/smoke) ────┼─> priority score per camera
camera 3 ┘   one batched pass for all cams   └─> web page: camera grid + auto "main view"

Always process the newest frame

Each camera is read in its own thread that keeps only the latest frame. If detection falls behind, it skips stale frames instead of building up a delay. For surveillance, seeing what's happening now matters more than processing every frame.

One batched pass for every camera

Frames from all cameras go through the model together in one batch, which uses the GPU far better than running each feed separately.

Priority scoring and automatic camera switching

Each camera gets a score from what it currently sees, and the highest-scoring camera becomes the main view on the dashboard:

PRIORITY = {"fire": 100, "smoke": 60}
PER_PERSON = 2  # small bonus per visible person

Fire outranks smoke, and people in frame raise the stakes. An operator watching a wall of feeds is pointed to the one that matters without hunting for it.

Evidence, without alert spam

Every alert saves a snapshot and writes an incident row with time, camera, label, confidence and number of people visible. A cooldown (10 seconds by default) stops the same fire on the same camera from firing hundreds of duplicate alerts, a common reason people start ignoring alarm systems.

What's next

  • A weapon model for guns and knives, the next detection class on my list.
  • Person tracking with ByteTrack and cross-camera re-identification, so the system follows one person between feeds.
  • A React dashboard with alert history and evidence clips.

The code is on GitHub. I'll write up the weapon model once it's trained.