Live pothole and speed-bump detection from a camera. Train and test on a laptop, then run the same code on a Raspberry Pi with a USB camera.
Two classes: pothole, speedbump.
get_dataset.py ─▶ data/roadwatch/ (YOLO images + labels, remapped to 2 classes)
train.py ─▶ models/roadwatch.pt (yolo11n fine-tuned)
detect.py ─▶ live boxes on camera/video (laptop and Pi)
export_pi.py ─▶ models/roadwatch.onnx (+ optional NCNN, fastest on the Pi)
roadwatch.detect auto-selects the fastest model present, preferring
NCNN ▸ ONNX ▸ .pt. So the same command runs on the laptop (uses .pt) and on the
Pi (uses ONNX or NCNN) with no code changes.
Only source code is in git — the dataset (~2.3 GB), the trained weights, and the
.venv are not committed; they are regenerated by the scripts below. That keeps the
repo small and lets you rebuild the whole thing on any machine (macOS, Linux, Windows, or
a Raspberry Pi) with the same commands. Python 3.9+ is the only prerequisite.
- New training machine: do steps 1 → 5 (set up, get data, train, detect, export).
- Deployment device (Pi/other): you only need step 1 (with
pip install -e ., no[data]) plus a copy ofmodels/roadwatch.onnx— jump to Run on the Raspberry Pi.
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[data]" # training machine: inference + dataset deps
# pip install -e . # deployment device: inference deps onlyThe dataset is Humps/Bumps & Potholes
on Roboflow Universe (CC BY 4.0). Put your free Roboflow API key in a .env file at the
repo root (it is gitignored):
ROBOFLOW_API_KEY=your_key_here
Then:
python scripts/get_dataset.py # downloads + normalises to data/roadwatch/Source classes (Pothole, Speed Bump) are remapped to our canonical
pothole / speedbump via CLASS_ALIASES in the script. Already have the export zip?
Use python scripts/get_dataset.py --zip path/to/yolov8.zip to skip the download.
python scripts/train.py # yolo11n, ~60 epochs, auto GPU/MPS/CPU
python scripts/train.py --epochs 100 # train longer
python scripts/train.py --resume # continue the last runThe best checkpoint is copied to models/roadwatch.pt. Training curves and metrics land
in runs/detect/roadwatch/.
python -m roadwatch.detect # auto-select camera, live window
python -m roadwatch.detect --list-cameras # see what cameras are available
python -m roadwatch.detect --pick-camera # choose one interactively
python -m roadwatch.detect --source 0 # force a specific camera index
python -m roadwatch.detect --source clip.mp4 # run on a video filePress q to quit the window. On macOS, grant camera access to your terminal in
System Settings ▸ Privacy & Security ▸ Camera the first time.
python scripts/export_pi.py --imgsz 416 # writes models/roadwatch.onnx (+ NCNN if it can)ONNX is the reliable, portable format and always exports. NCNN is faster on the Pi but
its pnnx converter is a native binary tied to a specific OS version, so it may fail on
your laptop (e.g. a wheel built for a newer macOS). If it does, that's fine — run
python scripts/export_pi.py --formats ncnn --imgsz 416 on the Pi to build it there,
and detection will auto-prefer it. --imgsz must match your training/inference size.
On the Pi (Raspberry Pi OS 64-bit recommended), with the USB camera plugged in:
sudo apt install -y python3-venv libgl1
git clone <this repo> && cd roadwatch
python3 -m venv .venv && source .venv/bin/activate
pip install -e . # inference only; no dataset/training deps
pip install onnxruntime # to run the ONNX model on the Pi
# copy models/roadwatch.onnx from the laptop into models/ on the Pi, then:
python -m roadwatch.detect --pick-camera --imgsz 416
python -m roadwatch.detect --headless # no display (over SSH); prints detections
# optional: build the faster NCNN model on the Pi itself
python scripts/export_pi.py --formats ncnn --imgsz 416--imgsz at inference must match the value you exported with. NCNN gives several times
the frame rate of the ONNX/.pt model on the Pi's CPU.
- The V4L2 backend needs MJPG to hit 30 fps at HD over USB 2.0;
camera.pysets this automatically on Linux. DEFAULT_NAME_HINTinsrc/roadwatch/camera.py("innomaker") makes auto-select prefer that USB camera. Change it to match your device, or just use--pick-camera.