Edge-optimized fire detection for the Guardian autonomous firefighting drone.
A single-class fire detector trained, exported and quantization-prepared for real-time CPU inference aboard a Raspberry Pi 4B — running in parallel with a visual-odometry pipeline that shares the same camera, the same four ARM cores, and the same flight.
- Overview
- Key characteristics
- Runtime architecture
- Design constraints
- Dataset
- Training
- Results
- Export and quantization
- Requirements
- Installation
- Usage
- Deploying to the drone
- Troubleshooting
- Repository contents
- Roadmap
- Contributing
- Citation
- Related repositories
- License
This is the perception component of a flying robot, and every decision in it was forced by that fact.
Guardian is a GPS-denied autonomous firefighting drone. Its onboard computer is a Raspberry Pi 4B — no accelerator, no discrete GPU, no CUDA. That single quad-core processor is already fully committed to the task that keeps the aircraft airborne: consuming a depth and colour stream from a RealSense camera, extracting and tracking visual features, resolving them into a metric 3D pose, and streaming that pose to a Pixhawk flight controller as a Vision Position Estimate. If that loop stutters, the drone does not run slowly — it loses position lock.
So the detector was built to an unusual specification: detect fire reliably from the air, on the CPU, from the frames the navigation stack is already consuming, while occupying so little of the machine that the flight controller never notices it is there.
The model this repository produces is the artifact loaded by the Guardian backend at launch. The two repositories describe one aircraft, split so that the model's training lineage and the flight software's lineage stay independently readable.
- Nano-scale architecture. YOLOv5n at 1.76 M parameters and 4.1 GFLOPs — the only variant that leaves headroom for feature tracking on a Pi 4B.
- Native 320x320 training. Trained at the resolution it is deployed at, eliminating the test-time resolution mismatch that quietly costs accuracy in most edge deployments.
- Single class, by design. The drone does not need a fire taxonomy. It needs one high-integrity bit, computed cheaply and repeatedly, so the entire capacity of a deliberately tiny network is concentrated behind one decision.
- Frozen ONNX deployment. No PyTorch and no training framework in the flight path — a static-shape graph with batch normalization folded in, executed by ONNX Runtime on CPU.
- Frame-subscriber integration. The detector never opens the camera. It subscribes to frames published in shared memory by the odometry thread.
- Quantization-ready export. The export path is structured for post-training INT8 quantization targeting the processor's NEON integer units.
The naive approach — opening a second camera handle for the detector — fails immediately. The RealSense pipeline is exclusive, a second consumer would double USB bandwidth on a bus already carrying synchronized depth and colour, and two independent consumers running at different rates would desynchronize, so a detection would no longer correspond to a known pose.
The system instead uses a single-producer, multi-consumer frame bus in shared memory. The odometry thread is the producer; the detector is a rate-limited, non-blocking subscriber.
flowchart TD
CAM["RealSense camera<br/>424 x 240 at 15 FPS<br/>depth + colour"]
subgraph PI["Raspberry Pi 4B - 4 cores, no GPU"]
VO["Odometry thread - 15 FPS<br/>every frame consumed<br/>SIFT + optical flow"]
BUS(["Shared frame buffer<br/>latest-frame-wins"])
FD["Fire thread - 10 Hz<br/>rate limited<br/>YOLOv5n ONNX at 320x320"]
STATE(["Published detection state"])
SERVO["Servo thread<br/>persistence gate"]
end
PX["Pixhawk flight controller<br/>EKF via MAVLink"]
PAY["Suppressant release"]
APP["Mobile ground station"]
CAM --> VO
VO -->|publish| BUS
BUS -->|non-blocking copy| FD
FD --> STATE
STATE --> SERVO
STATE --> APP
VO -->|vision position estimate| PX
SERVO --> PAY
style VO fill:#1f6feb,color:#fff
style FD fill:#d1242f,color:#fff
style BUS fill:#8250df,color:#fff
style SERVO fill:#bf8700,color:#fff
The critical property is rate decoupling. The camera runs at 15 FPS and the odometry thread must consume every single frame, because dropping one breaks feature-tracking continuity and corrupts the pose estimate. The detector is watching a physical phenomenon that evolves over seconds rather than milliseconds, so it runs at 10 Hz on its own thread, reading whatever the newest published frame happens to be, and never blocks the producer.
Three details carry the design. The shared buffer's lock is held only for a memory copy, never across inference, giving latest-frame-wins semantics in which the detector may skip frames but the odometry never does. The detector's rate limiter acts as a load governor, voluntarily returning the core to the navigation stack ten times a second rather than spinning at full occupancy. And detection state is published rather than pushed, so downstream consumers — payload control, telemetry, the mobile app — are fully decoupled from inference timing.
| Constraint | Origin | Resolution |
|---|---|---|
| No GPU or accelerator | Multirotor payload and weight budget | YOLOv5n at 1.76 M parameters, 4.1 GFLOPs |
| Cannot starve the odometry thread | Loss of position lock is not a degraded mode | Isolated thread, hard-limited to 10 Hz |
| Cannot touch the camera device | RealSense pipeline is exclusive; bandwidth is finite | Subscribes to frames published in shared memory |
| Fixed, small input resolution | Compute scales with pixel count | Trained and exported natively at 320x320 |
| Small on-disk footprint | SD-card image, updates over a field Wi-Fi hotspot | 3.7 MB checkpoint, 7.1 MB exported graph |
| Dependency-light runtime | No training framework belongs on a flight machine | ONNX Runtime CPU execution provider |
| Aerial, oblique, variable-scale targets | Fire is seen from above, at range, at odd angles | Aerial-inclusive dataset, single-class objective |
| No false payload drops | Suppressant is single-shot and irreversible | Confidence floor plus temporal persistence gating |
Fire Dataset for YOLOv8, version 10 from Roboflow Universe, licensed CC BY 4.0. Single class, fire.
| Property | Value |
|---|---|
| Validation images | 800 |
| Labelled instances | 1,015 |
| Instances per image | approx. 1.27 |
| Classes | 1 |
The instance density matters more than the raw count. At roughly 1.27 fire instances per image, the distribution is rich in multi-ignition and partially-occluded scenes rather than one clean centred flame per frame — which is the regime an airborne camera actually encounters.
Trained on Google Colab using a single NVIDIA Tesla T4, on the Ultralytics YOLOv5 v7.0 framework.
| Parameter | Value | Rationale |
|---|---|---|
| Architecture | YOLOv5n | Smallest variant; the only one leaving headroom for feature tracking |
| Initialization | COCO-pretrained | Low-level edge and texture filters transfer well to flame |
| Input resolution | 320 x 320 | Deployment resolution — trained at the size it is run at |
| Epochs | 80 | Converged in 0.699 hours wall clock |
| Batch size | 16 | 200 iterations per epoch, 0.648 GB peak VRAM |
| Dataset caching | Enabled | Held in RAM, eliminating I/O-bound epochs |
| Precision | Mixed (AMP) | Faster convergence on tensor cores |
The full perception stack of an autonomous aircraft trained in roughly forty minutes on free-tier compute — itself a consequence of choosing an architecture honest about the hardware it has to land on.
Measured on the held-out validation split at 320 x 320 after layer fusion.
| Metric | Value |
|---|---|
| Precision | 0.696 |
| Recall | 0.407 |
| mAP at IoU 0.5 | 0.439 |
| mAP at IoU 0.5:0.95 | 0.201 |
| Parameters | 1,760,518 |
| Fused layers | 157 |
| Compute | 4.1 GFLOPs |
| Checkpoint size | 3.7 MB |
The operating point is precision-leaning by design. The model is considerably more trustworthy when it reports fire than it is exhaustive at finding every flame pixel, and that asymmetry is the correct one for an aircraft carrying a single-shot payload. It is compounded deliberately at runtime, where a raised confidence floor and a temporal persistence gate trade per-frame recall for near-elimination of spurious actuation. A drone that catches a fire on the twelfth frame instead of the seventh has lost half a second; a drone that empties its payload onto a red vehicle has lost the mission.
Inference latency, measured on the exported graph:
| Runtime | Resolution | Inference |
|---|---|---|
| PyTorch, Tesla T4 | 640 x 640 | 34.0 ms |
| ONNX Runtime | 320 x 320 | 5.3 ms |
The figure that governs the design is 5.3 ms of network inference at deployment resolution. At a 10 Hz duty cycle that is a forward pass occupying a low single-digit percentage of one core's time budget — the entire reason the detector can coexist with feature tracking on the same silicon.
The training checkpoint is compiled into a frozen, self-contained graph before it goes near the aircraft.
| Stage | Format | Size |
|---|---|---|
| Training checkpoint | FP32 PyTorch | 3.7 MB |
| Fused inference graph | FP32 ONNX, opset 17 | 7.1 MB |
| Quantization target | INT8 ONNX | approx. 1.9 MB |
Export folds batch normalization into the preceding convolutions, producing 157 fused layers with no training-time overhead shipped. Shapes are pinned static at a batch size of one and a 320 x 320 input, so every allocation is known ahead of time and there is no reshape thrash or surprise mid-flight allocation. A frozen graph is also a deterministic graph: the same bytes produce the same result on every boot, which is an auditability property that matters when the output actuates hardware.
Pre- and post-processing are written directly against that fixed contract — colour conversion, resize, normalization, channel reordering on the way in; confidence filtering, box rescaling into native frame coordinates, and non-maximum suppression on the way out — so the flight path carries no dependency on the training framework at all.
Quantization strategy. The Pi's processor has no floating-point vector path worth exploiting, but it does have NEON SIMD integer units, and that is where the remaining performance lives. The export path is therefore built for post-training static quantization to INT8, which narrows weights fourfold and moves the convolution stacks onto integer kernels.
Static calibration is preferred over dynamic quantization for a specific reason. Dynamic quantization computes activation ranges per inference — cheap to configure, but that cost is re-paid on every forward pass, of which a long patrol performs hundreds of thousands. Static calibration pays it once, on the ground. On a compute-starved airborne CPU, moving work from flight time to build time is always the correct trade.
Two further choices follow from the model's size. Weights are quantized per channel rather than per tensor, because a network this small has no capacity to spare and the thin, high-variance channels in the neck are precisely where flame's colour signature is encoded. And calibration data should be drawn from the drone's own camera rather than generic imagery, because quantization error is a function of the activation distribution, and this distribution is unusual — low-resolution aerial viewpoints with high-dynamic-range flame against dark ground.
Because the model actuates hardware, quantization is gated on re-validation rather than assumed lossless. The acceptance criterion is a negligible drop in mean average precision with a hard requirement that precision does not fall. Recall may be traded; the false-positive rate is what protects the payload.
| Path | Description |
|---|---|
fire_detector_training.ipynb |
End-to-end notebook covering dataset configuration, training, export, and sample inference, with outputs preserved so every metric quoted here is auditable in place |
README.md |
This document |
Trained artifacts are not committed. They are produced by the notebook and deployed to the aircraft as described in the Guardian repository.
To train. A CUDA-capable GPU, or a Google Colab session with a GPU runtime, which is what the notebook was built against. Python 3.9 or newer and the Ultralytics YOLOv5 framework with its dependencies. Training the full 80 epochs takes well under an hour on an entry-level cloud GPU, so no local accelerator is necessary.
To run inference. Python 3.9 or newer, ONNX Runtime, OpenCV and NumPy. No GPU, no PyTorch and no training framework are required — that is the point of the exported graph.
To deploy. A Raspberry Pi 4B running a 64-bit OS, a depth camera, and the Guardian backend.
Retrieve the training framework and its dependencies:
git clone https://github.com/ultralytics/yolov5.git
cd yolov5
pip install -r requirements.txt
For inference only, the runtime dependencies are considerably lighter:
pip install onnxruntime opencv-python numpy
Place the dataset so that it contains train/ and valid/ splits alongside a data.yaml describing them, then train from COCO-pretrained nano weights at deployment resolution:
python train.py --img 320 --batch 16 --epochs 80 \
--data /path/to/fire_data/data.yaml \
--weights yolov5n.pt --cache --device 0
Weights are written to runs/train/exp/weights/, with best.pt selected on validation performance.
Compile the checkpoint into a frozen, static-shape graph. The export resolution must match the training resolution:
python export.py --weights runs/train/exp/weights/best.pt \
--img 320 --batch 1 --include onnx
Against a single image, a directory, a video file or an attached camera:
python detect.py --weights runs/train/exp/weights/best.onnx \
--img 320 --conf 0.5 --source path/to/image.jpg
To reproduce the reported metrics, or to validate a quantized graph before it flies:
python val.py --weights runs/train/exp/weights/best.onnx \
--data /path/to/fire_data/data.yaml --img 320 --task val
Open fire_detector_training.ipynb in Google Colab, select a GPU runtime, place the dataset in the expected Drive location and run all cells. The notebook covers the entire sequence above — mounting storage, retrieving the framework, rewriting dataset paths, training, saving weights back to Drive, exporting, and running sample detections.
Copy the exported graph to the model path the Guardian backend expects:
scp best.onnx guardian@<pi-address>:/home/guardian/Desktop/capture_depth/fire_model/best.onnx
Install the inference runtime on the Pi, then launch the onboard stack with detection enabled. The backend selects ONNX Runtime automatically when the model path ends in .onnx, and falls back to the training framework for a .pt file during bench development. Inference rate, confidence threshold and input size are all configurable at launch; the full option set is documented in the Guardian repository.
The inference rate is the parameter to treat with care. It governs how much of the processor the detector consumes, and raising it spends headroom the navigation loop depends on.
| Symptom | Cause and resolution |
|---|---|
| Detections vanish after export | Export resolution does not match the resolution used at inference. Both must be 320. |
| Inference runs but nothing is ever detected | The confidence floor is above the model's operating range for the scene. Lower it to inspect raw behaviour, then raise it back for deployment. |
| Boxes land in the wrong place | Coordinates are being read without rescaling from the model's fixed input back to native frame dimensions. |
| Inference far slower than expected on the Pi | A GPU execution provider is being requested where none exists, or thread count is oversubscribed against the navigation pipeline. |
| Navigation degrades once detection is enabled | The inference rate is set too high. It is a load governor, not a quality setting. |
| Accuracy drops sharply after quantization | Calibration data does not reflect the deployment distribution. Calibrate on frames from the drone's own camera. |
- Land INT8 quantization with a calibration set captured from the drone's own camera, and publish measured size and accuracy deltas
- Benchmark on-device latency under concurrent navigation load rather than in isolation
- Move non-maximum suppression into the exported graph to reduce post-processing time
- Improve recall through aerial-specific augmentation covering rotation, scale jitter, low light and smoke occlusion
- Add a smoke class as an early-warning signal, since smoke is visible before flame from altitude
- Fuse detections with the odometry pose to emit geolocated fire coordinates rather than image-space boxes
- Evaluate alternative execution providers against the ONNX Runtime CPU baseline
Contributions are welcome, particularly around recall on aerial imagery and on-device benchmarking, which are the two areas where this model has the most room to improve.
Open an issue before starting substantial work so the approach can be discussed first. For changes that affect the model itself, include the validation metrics before and after alongside the training configuration used, since a change that improves mean average precision while reducing precision is not an improvement for this application. For changes to the deployment path, note the effect on inference latency and model size — both are constrained by the target hardware rather than by preference.
If this work is useful in your research, please cite the project:
@software{guardian_fire_detector,
author = {Husnain},
title = {Fire_Detector: Edge-optimized fire detection for the
Guardian autonomous firefighting drone},
year = {2025},
url = {https://github.com/Husnaiin/Fire_Detector}
}
Please also cite Ultralytics YOLOv5 and the underlying dataset, on which this model depends.
| Repository | Role |
|---|---|
| Husnaiin/Guardian | The complete drone system — mobile ground station, Raspberry Pi backend, GPS-denied navigation, flight controller integration and payload control. This model runs inside it. |
| Husnaiin/Fire_Detector | This repository. Training, evaluation and edge export of the detection model. |
| Guardian case study | System-level write-up, architecture and results |
- Ultralytics YOLOv5 for the architecture, training and export tooling
- Roboflow Universe for the fire dataset
- ONNX Runtime for edge inference and the quantization toolchain
- Google Colab for training compute
Released under the MIT License.
The training and export tooling from Ultralytics YOLOv5 is licensed AGPL-3.0 and the dataset is CC BY 4.0. Weights derived using that tooling inherit its licensing obligations; review both before any commercial deployment.
Built for a drone with no GPS, no GPU, and no second chance.
Guardian · flight system · case study