π Drone Detection & Real-Time Tracking β YOLOv5
"Can a $35 computer see, think, and chase a drone β all at once?"
That was the question. This is the answer.
π― The Challenge
Drones are everywhere. Detecting them in real-time on powerful hardware is one thing β but what if you only have a Raspberry Pi 4? No GPU, no CUDA, no shortcuts.
The challenge was clear:
- Detect drones accurately in real-time
- Run inference at usable frame rates on a CPU
- Move a physical camera to follow the drone β automatically
Most people would say it's not possible. We built it anyway.
π Final Model Performance
| Metric | Score |
|---|---|
| Precision | 94.8% |
| Recall | 96.2% |
| mAP@0.5 | 99.0% π₯ |
| mAP@0.5:0.95 | 70.6% |
| False Positives | ~1% β |
| Inference on Pi | ~15-20 FPS |
π‘ The Story Behind This Model
Act 1 β The First Attempt
We started simple. A single dataset, ~500 images, YOLOv5s with Transfer Learning. The first model came out at 95% mAP β impressive on paper.
But on the Raspberry Pi, reality hit hard.
The camera would lock onto a wooden tray and call it a drone. A chair. A lamp. The model was too eager β it had never truly learned what wasn't a drone.
We had a precision problem. And we knew exactly why: not enough negative images.
Act 2 β The Dataset Problem
The original dataset had only 5 background images out of 503 total. Five. The model had barely seen what the world looks like without a drone in it.
So we went hunting for data.
Instead of spending weeks manually collecting and labeling images, we combined 3 of the largest drone datasets on Roboflow Universe:
| Dataset | Images | Source |
|---|---|---|
| DroneDetectionPITT | 34,000 | Roboflow Universe |
| Drone Detection (YOLO) | 1,094 | Roboflow Universe |
| Drone Detection (drones-lfobz) | 5,079 | Roboflow Universe |
| Positive (auto-labeled) | 321 | Original dataset |
| Negative (background) | 155 | COCO Dataset |
| Total | 40,529 | β |
But combining datasets isn't just copy-paste. Each dataset has its own format, resolution, and labeling style. We built a custom pipeline to:
- Download all 3 datasets via Roboflow API
- Convert every label to unified YOLO format
- Auto-label our positive images using the first model as a labeling tool
- Add COCO background images as hard negatives
It took engineering. It took iteration. But it worked.
Act 3 β Training at Scale
With 40,000+ images ready, we needed serious compute. We upgraded to an NVIDIA A100 40GB GPU on Google Colab and trained for 30 epochs with:
Model : YOLOv5s (Transfer Learning from COCO)
Image size: 416Γ416
Batch size: 256
Epochs : 30
GPU : NVIDIA A100 40GB
Time : ~45 minutes
The results were immediate. mAP jumped from 95% to 99%. False positives dropped from 100% to 1%. The model had finally learned the difference between a drone and everything else.
Act 4 β Running on the Edge
A great model means nothing if it can't run where it needs to. We exported to ONNX format and deployed on Raspberry Pi 4 using ONNXRuntime β no GPU, no PyTorch, just pure optimized inference.
Achieved: 15-20 FPS on a $35 computer. β
Act 5 β The Tracker
Detection alone wasn't enough. We built a complete pan-tilt tracking system:
Camera Feed β YOLOv5 Detection β PID Controller β Servo Motors
A 4-state machine manages the behavior:
SEARCHING β drone appears β TRACKING
TRACKING β drone lost β LOST
LOST β timeout β RETURNING
RETURNING β centered β SEARCHING
The PID controller computes servo movement based on the pixel error between the drone's center and the frame center:
Pan axis: Kp=0.055 Ki=0.0005 Kd=0.010
Tilt axis: Kp=0.045 Ki=0.0005 Kd=0.010
Deadzone : 10 pixels (prevents micro-jitter)
Hardware stack:
- Raspberry Pi 4 β main compute
- USB Webcam β 640Γ480 live feed
- PCA9685 PWM Driver β I2C servo controller
- 2Γ Servo Motors β pan and tilt axes
- External 5V/2A PSU β dedicated servo power
π§ Quick Start
Install
pip install onnxruntime opencv-python numpy
Detect on Image
import cv2
import numpy as np
import onnxruntime as ort
session = ort.InferenceSession("best.onnx")
input_name = session.get_inputs()[0].name
def letterbox(frame, size=416):
h0, w0 = frame.shape[:2]
r = min(size/h0, size/w0)
nw, nh = int(round(w0*r)), int(round(h0*r))
dw, dh = (size-nw)/2, (size-nh)/2
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (nw, nh))
img = cv2.copyMakeBorder(img,
int(round(dh-.1)), int(round(dh+.1)),
int(round(dw-.1)), int(round(dw+.1)),
cv2.BORDER_CONSTANT, value=(114,114,114))
img = np.expand_dims(img.transpose(2,0,1), 0).astype(np.float32)/255.0
return img, r, dw, dh
def detect(image_path, conf_thres=0.45):
frame = cv2.imread(image_path)
h0, w0 = frame.shape[:2]
img, r, dw, dh = letterbox(frame)
preds = session.run(None, {input_name: img})[0][0]
mask = preds[:, 4] > conf_thres
preds = preds[mask]
results = []
for det in preds:
cx = int((det[0] - dw) / r)
cy = int((det[1] - dh) / r)
w = int(det[2] / r)
h = int(det[3] / r)
conf = float(det[4])
x1 = max(0, cx - w//2)
y1 = max(0, cy - h//2)
x2 = min(w0, cx + w//2)
y2 = min(h0, cy + h//2)
results.append({'bbox': [x1,y1,x2,y2], 'conf': conf})
cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
cv2.putText(frame, f"Drone {conf:.0%}", (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 2)
cv2.imwrite("output.jpg", frame)
print(f"Detected {len(results)} drone(s)")
return results
results = detect("your_image.jpg")
Detect on Live Camera
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret: break
img, r, dw, dh = letterbox(frame)
preds = session.run(None, {input_name: img})[0][0]
mask = preds[:, 4] > 0.45
for det in preds[mask]:
cx = int((det[0]-dw)/r); cy = int((det[1]-dh)/r)
w = int(det[2]/r); h = int(det[3]/r)
cv2.rectangle(frame, (cx-w//2, cy-h//2), (cx+w//2, cy+h//2), (0,255,0), 2)
cv2.imshow("Drone Detector", frame)
if cv2.waitKey(1) & 0xFF == ord('q'): break
cap.release()
π Repository Files
| File | Description |
|---|---|
best.onnx |
ONNX model β optimized for CPU & Raspberry Pi |
best.pt |
PyTorch weights β for fine-tuning or retraining |
drone_enhanced.yaml |
Dataset configuration used in training |
hyp_drone_enhanced.yaml |
Hyperparameters used in training |
results.png |
Training loss & metrics curves |
confusion_matrix.png |
Confusion matrix |
π€ About
Author: Ahmed Darwish
Deployment: Raspberry Pi 4
Training: Google Colab β NVIDIA A100 40GB GPU
Stack: Python Β· YOLOv5 Β· ONNXRuntime Β· OpenCV Β· PCA9685 Β· PID Control
Built from scratch. Trained at scale. Deployed on the edge. π