Adaptive traffic signal control with RL and a YOLO vehicle detector

A simulated 4-way intersection in SUMO, controlled three ways: a fixed timer, a rule-based adaptive controller, and a DQN agent. A separate YOLOv8 detector, fine-tuned on real traffic-camera footage, is the perception front end.

Python, SUMO, TraCI, Gymnasium, stable-baselines3, YOLOv8, PyTorch
−66–70%
average wait vs a fixed timer, light-to-moderate demand
−12.7%
throughput under saturation: the trade-off
0.907
mAP50 on 9 video clips never seen in training
5
DQN versions, none better than the rule-based controller overall

Question

“AI traffic lights” is a common smart-city pitch. The part most versions skip is the one that matters: does it measurably help, and under what conditions does it stop helping?

So the order of work was fixed before any controller existed: build the intersection, measure a fixed-timer baseline, then build each smarter controller and score it on the identical scenario with the identical scoring script.

North–south greenQueued 0 NS, 0 EWThrough 0Avg wait 0.0sLive: a simplified version of the adaptive rule. Green ends when its queue clears and the other side is waiting.

Setup

  • Fixed-timer baseline: SUMO’s static program, 42s green and 3s yellow per approach pair.
  • Rule-based adaptive controller: reads per-lane halting counts over TraCI every simulation step and extends, cuts or holds the green phase with gap-out logic.
  • DQN agent: a Gymnasium environment over the same intersection. State is both queues, current phase, green time elapsed and how long the red side has waited. The action is keep or switch, bounded by the same 10s minimum and 60s maximum green.
  • Scoring: every run writes per-vehicle tripinfo XML, and one script turns it into wait time, time loss, max wait and throughput.

Results

ScenarioAvg wait, fixed → adaptiveVehicles completed
Light-moderate, asymmetric10.55s → 3.60s (−66%)1179 → 1182
Moderate-heavy, with burst11.97s → 3.57s (−70%)1985 → 1990
Genuine saturation40.27s → 32.49s (−19%)6730 → 5872 (−12.7%)
Fixed timer vs rule-based adaptive controller, same scenario files and scoring script.

Under saturation the adaptive controller still lowered average wait but completed 12.7% fewer trips. Logging every green phase showed why: its short minimum green caused 2.3x more time lost to yellow transitions than the fixed timer.

Phase logging also showed that in the first two scenarios all of the improvement came from one mechanism: ending a green phase once its queue cleared, instead of holding it for 42 seconds. The extend-on-demand logic never fired. The honest claim is about that cutoff, not about extend-on-demand control.

Bar charts comparing fixed-timer and adaptive controllers across three demand scenarios
The advantage shrinks, and throughput flips, as demand approaches saturation.

What broke

Arrival detection. The controller inferred new arrivals from the net change in queue length. Under real demand, cars arrive and leave on the same step, so the queue stays flat while traffic keeps coming and the controller gapped out too early. I replaced it with vehicle-ID tracking (set difference on lane vehicle IDs). Afterward gap-out fired in 0% of phases at light demand and 97% at heavy demand, as it should.

The RL agent, five times:

VersionChangeMax waitCompletedSwitches
v1Queue-only reward158sworst—
v2–v3Hard max-green cap; reward summed over every second a switch costs53–56s~4950~276
v4+ throughput reward123s5423100
v5+ anti-starvation penalty56s4936277
Saturation scenario. The rule-based controller completed 5872.

v3 fixed a real bug: a switch costs four simulated seconds but returned one reward, so switching looked free to the agent. v4 recovered most of the throughput gap but starved one direction (EW waited 97.7s on average, NS 7.2s). v5 balanced the directions and gave the throughput back. With fixed hand-picked weights, fixing one objective broke another every time.

Charts comparing max wait, throughput and switch count across DQN versions one through five
Each RL version moved the problem rather than solving it.

Perception

To replace SUMO’s ground-truth vehicle counts with something a real intersection could have, I fine-tuned YOLOv8n on UA-DETRAC traffic-camera footage: 6,808 training images, single “vehicle” class.

Two data problems came first. The class names in the export were video-clip IDs, not vehicle types, so I verified the boxes were valid and remapped everything to one class. Then a 1-epoch run scored 0.909 mAP50, which was too good. A check showed all 60 video clips were in both train and validation, so near-identical consecutive frames were being graded against each other.

SplitmAP50mAP50-95
Random frame-level split, epoch 10.9090.641
Clip-level split, epoch 10.8380.556
Clip-level split, 30 epochs0.9070.649
The first row measured memorization. Validation is 940 images from 9 clips never seen in training.

Precision is 0.918 but recall is 0.819: the model misses about 18% of vehicles, so it systematically undercounts. Any controller fed by it would have to correct for that, not treat the counts as exact.

Grid of held-out traffic camera frames with predicted vehicle bounding boxes
Predictions on held-out validation clips.

Next

  • Feed YOLO’s detected counts into the controllers in place of simulator ground truth. The two halves are not connected yet.
  • Replace the hand-weighted scalar reward with a weight sweep or a constrained RL formulation.
  • Add turning movements and a second intersection.