AI/ML11 min read · 5 December 2025

Achieving 94% Accuracy with YOLOv8 for Real-Time Traffic Management

How I trained a custom YOLOv8 model on traffic footage, implemented adaptive signal control logic, and optimised inference speed for real-time deployment.

YOLOv8Computer VisionPyTorchPythonOpenCV

Why Traffic Management Needs Computer Vision

Traditional traffic signals run on fixed timers — completely blind to actual traffic density. A computer vision system that counts vehicles in real time can adapt signal timing dynamically, reducing average wait times by 30-40%.

Dataset Preparation

I used a combination of public datasets and custom-annotated footage:

  • UA-DETRAC — 140k+ annotated frames from Chinese highways
  • Custom footage — 2,000 frames from UK roundabouts (annotated with Roboflow)
  • Classes — Car, Truck, Bus, Motorcycle, Cyclist, Pedestrian
python
# Dataset config (data.yaml)
train: datasets/traffic/train
val: datasets/traffic/val
test: datasets/traffic/test

nc: 6
names: ['car', 'truck', 'bus', 'motorcycle', 'cyclist', 'pedestrian']

Training YOLOv8

python
from ultralytics import YOLO

model = YOLO('yolov8m.pt')  # Medium model — good speed/accuracy tradeoff

results = model.train(
    data='data.yaml',
    epochs=100,
    imgsz=640,
    batch=16,
    lr0=0.01,
    lrf=0.001,
    augment=True,       # Random flips, crops, colour jitter
    hsv_h=0.015,        # Hue augmentation
    hsv_s=0.7,          # Saturation
    mosaic=1.0,         # Mosaic augmentation
    device='cuda',      # GPU required
    patience=20,        # Early stopping
    save_period=10,
)

Real-Time Inference Pipeline

python
import cv2
from ultralytics import YOLO
from collections import defaultdict

model = YOLO('best.pt')
cap = cv2.VideoCapture('traffic_feed.mp4')

# Define counting lines per lane
COUNTING_LINES = {
    'north': [(0, 400), (640, 400)],
    'south': [(0, 200), (640, 200)],
}

vehicle_counts = defaultdict(int)
track_history = defaultdict(list)

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        break
    
    # Track vehicles across frames
    results = model.track(frame, persist=True, classes=[0,1,2,3])
    
    if results[0].boxes.id is not None:
        boxes = results[0].boxes.xywh.cpu()
        track_ids = results[0].boxes.id.int().cpu().tolist()
        
        for box, track_id in zip(boxes, track_ids):
            x, y, w, h = box
            track_history[track_id].append((float(x), float(y)))
            
            # Check if vehicle crossed counting line
            if crossed_line(track_history[track_id], COUNTING_LINES['north']):
                vehicle_counts['north'] += 1
    
    # Adaptive signal logic
    update_signal_timing(vehicle_counts)

Adaptive Signal Control

python
def update_signal_timing(counts: dict) -> dict:
    total = sum(counts.values())
    if total == 0:
        return {lane: 30 for lane in counts}  # Default 30s
    
    # Proportional allocation with min/max constraints
    return {
        lane: max(15, min(90, int((count / total) * 120)))
        for lane, count in counts.items()
    }

Results

MetricScore
mAP@0.594.2%
mAP@0.5:0.9571.8%
Inference speed28ms/frame (RTX 3060)
FPS~35 real-time

The 94% mAP@0.5 beat the project target of 85% and was achieved largely through the mosaic augmentation and custom UK road footage that improved generalisation.

MH
Mahmudul Hassan Mithun
AI SaaS Builder · BSc Data Science & AI, UEL · Building ContentForge AI

Related Posts

Building a Production Multi-Agent AI Pipeline with LangChain & FastAPI
Building a Production Multi-Agent AI Pipeline with LangChain & FastAPI
12 min read →
Deploying FastAPI to Railway: The Production Checklist
Deploying FastAPI to Railway: The Production Checklist
8 min read →