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.
- −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.
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
| Scenario | Avg wait, fixed → adaptive | Vehicles completed |
|---|---|---|
| Light-moderate, asymmetric | 10.55s → 3.60s (−66%) | 1179 → 1182 |
| Moderate-heavy, with burst | 11.97s → 3.57s (−70%) | 1985 → 1990 |
| Genuine saturation | 40.27s → 32.49s (−19%) | 6730 → 5872 (−12.7%) |
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.

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:
| Version | Change | Max wait | Completed | Switches |
|---|---|---|---|---|
| v1 | Queue-only reward | 158s | worst | — |
| v2–v3 | Hard max-green cap; reward summed over every second a switch costs | 53–56s | ~4950 | ~276 |
| v4 | + throughput reward | 123s | 5423 | 100 |
| v5 | + anti-starvation penalty | 56s | 4936 | 277 |
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.

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.
| Split | mAP50 | mAP50-95 |
|---|---|---|
| Random frame-level split, epoch 1 | 0.909 | 0.641 |
| Clip-level split, epoch 1 | 0.838 | 0.556 |
| Clip-level split, 30 epochs | 0.907 | 0.649 |
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.

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.