Solution Engineer to Lead, WINGS ICT Solutions · 2022 to 2026

Bus-Lane Enforcement: WebRTC Streams to ML Evidence Pipeline

Violation detection from cameras on moving buses: WebRTC video, ONNX inference, H3 geospatial matching, and evidence handling built for a formal review workflow.

50 concurrent streams in pilot · 600-bus design target · multi-model inference pipeline

WebRTCONNX RuntimeH3Python/Rust

Some details are generalized where a client needs them to be.

Problem

Catch bus-lane violations from cameras on moving buses, and produce evidence suitable for a formal review workflow. A false positive here is not just a bad metric; it affects a real person.

Approach

WebRTC for the video, then a pipeline where cheap geospatial checks run first and gate the expensive ML. H3 for both lane membership and authority routing, and a violation only counts if it holds across a window of frames.

Result

50 buses streaming at once in the pilot, a capacity plan for a 600-bus deployment target, and violation records accepted by the enforcement filing workflow.

The numbers, and the one I’m careful about. 50 concurrent streams is the pilot, fifty buses live through the platform at once. 600 is the deployment target backed by capacity planning and load projections, not a measured production number. Each stream runs multiple ONNX models: vehicle detection, plate recognition, lane classification, and a dashed-line filter.

My role was to shape the backend architecture, keep the domain rules testable, and move the performance-critical path when the Python version reached its limits.

Problem

Urban bus lanes get blocked by unauthorized cars more or less constantly, and the transport operator wanted to enforce them automatically: spot the violation from a camera on a moving bus, read the plate, and hand over evidence suitable for formal review.

Three things made it genuinely hard. The camera moves, so “is this car in a bus lane” isn’t a region you draw once, it’s a live geospatial question on every frame. The inference has to keep up with a lot of streams at once on a constrained hardware budget. And a false positive is not just a bad number on a dashboard, so evidence quality drove every decision we made.

Approach

GPS fix + frame
(MQTT · WebRTC)
H3 lane +
hours rules
Dashed-line
filter → discard
YOLOv11 +
ALPR
Evidence
window
Authority
routing (H3)

Cheap checks first, expensive models last. Many frames are thrown out before inference, and the rest move through the models only after the geospatial rules say they are worth spending compute on.

  • WebRTC for the video. Sub-second glass-to-inference over flaky cellular, with GPS coming in alongside over MQTT.
  • Gate the expensive work. The cheap checks run first and the costly ones last: an H3 lane-membership lookup, then local operating-hours rules, then lane classification, then a small CNN that spots dashed lane markings and drops those frames (a dashed line means entering is legal, so there’s nothing to detect), then finally YOLOv11 and plate recognition. Most frames never make it that far, and that’s the point.
  • H3 hexagons, used twice. The bus-lane geometry is precompiled into a set of H3 cells, so “is this GPS fix in a lane” becomes an O(1) set lookup instead of point-in-polygon math on every frame. The same index answers which authority owns the location, which the legal workflow needs, and which is far easier to get right when geography is indexed one consistent way.
  • Evidence over a window, not a frame. A violation only fires once the detections agree across a window, same plate, same lane, sustained presence, plus a local plate-format check. A single lucky frame is treated as noise, on purpose.

Implementation

The production system is Python on asyncio and uvloop, built with a hexagonal architecture, the domain logic (violation rules, plate validation, lane matching) kept completely separate from the infrastructure around it (aiortc for WebRTC, ONNX Runtime on the GPU, MQTT, the platform APIs). That separation paid off twice. First it made the domain testable. Later, when we moved the performance-critical path to Rust, the domain model ported cleanly because it was never tangled up with the framework, and the async Rust version pushed the number of streams a single node could carry up on the same hardware.

Confirmed violations flow to the operator’s platform and into the enforcement filing workflow through a batched sync with strict delivery guarantees. In an enforcement system the boring integration work matters as much as the ML: a detection you can’t file correctly is worth nothing.

Outcome

  • 50 buses streaming at once in the pilot, with a capacity plan for a 600-bus deployment target.
  • Evidence-grade records accepted by the enforcement filing workflow.
  • The Python-first, Rust-for-the-hot-path approach I’d proven on the UDP server held up here too, and it’s now how I default to building ingestion and inference services.

What I’d do differently: build the route-replay rig sooner. Replaying recorded routes with synthetic GPS jitter surfaced edge cases much faster than field testing alone. We built it late, and I felt it.