🚁 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:

  1. Download all 3 datasets via Roboflow API
  2. Convert every label to unified YOLO format
  3. Auto-label our positive images using the first model as a labeling tool
  4. 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. πŸš€

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Space using engdarwish/drone-detection-yolov5 1