How SugboFlow works
The models, the pipeline, and the math behind the map — from a video frame to a signal timing recommendation.
1 · Detection & tracking models
| Component | What we use | Details |
|---|---|---|
| Object detector | YOLO11s (Ultralytics) | 9.5M parameters, COCO-pretrained, 640px input. Filtered to vehicle classes only: car, motorcycle, bus, truck, bicycle. |
| Multi-object tracker | ByteTrack | Associates detections across frames into stable track IDs, keeping low-confidence boxes in play so vehicles survive brief occlusions (a jeepney passing behind a truck keeps its ID). |
| Hardware | Auto-selected | Apple GPU (MPS) → NVIDIA CUDA → CPU fallback. On an Apple Silicon Mac the demo clip (89s @ 30fps) processes in about real time. |
Known limitation: COCO has no jeepney or tricycle class, so jeepneys land in bus/truck and tricycles in motorcycle. The data model already carries the local taxonomy — the planned upgrade is a fine-tuned model on annotated PH footage, which slots in without any schema change.
2 · From boxes to counts: the virtual counting line
Each camera stores a counting line drawn on its view (Monitoring page), saved in normalized 0–1 coordinates so any resolution works. Every frame, each tracked vehicle's box center is tested against the line with a signed side-of-line function:
side(p) = (x₂−x₁)(pᵧ−y₁) − (y₂−y₁)(pₓ−x₁)
When the sign flips between consecutive frames, that track crossed the line — counted exactly once, with the flip direction giving the travel direction (northbound vs southbound). Each crossing is timestamped as video start + frame / fps and dropped into a 1-minute bin per (camera, class, direction).
3 · System architecture
The worker is a separate process so a 30-minute video never blocks the API. It also handles link ingestion — YouTube and public Facebook videos (yt-dlp downloads the video, or records a live stream for a set duration, before counting). The same design will host RTSP live-stream consumers later — a stream is just a video that never ends.
4 · How the database is updated
video_jobs row (queued)→worker sets running + progress every 2s→bulk-insert vehicle_counts→job done + totals| Table | One row is… | Key columns |
|---|---|---|
cameras | a monitoring point | name, intersection, lat/lng, counting-line config (JSON), approach lanes |
video_jobs | one processed video | status, progress, video_start, per-class totals, annotated file path |
vehicle_counts | a 1-minute bin | camera, bin_start, vehicle_class, direction, count, PCU, source |
Three properties keep the data trustworthy:
- Idempotent re-runs — counts carry their
job_id; re-running a job deletes its old rows first, so nothing double-counts. - Source tagging — every row is
video,demo(synthetic seed data), orlive, so demo data can be swept away the moment real footage arrives. - Minute-level atoms — hourly/daily views are aggregated in SQL (
date_trunc), never stored, so there is one source of truth.
5 · The traffic math
PCU weighting
Counts are weighted into passenger-car units so a bus and a motorcycle don't count the same: motorcycle ×0.5 · car ×1.0 · jeepney ×1.5 · bus/truck ×2.0 · bicycle ×0.3.
Congestion index (map colors)
ratio = latest-hour PCU ÷ typical PCU for this weekday & hour (averaged over history, ignoring partially-recorded hours). Free flow <0.75 · moderate <1.05 · heavy <1.35 · severe ≥1.35. With no history yet, it falls back to utilization of the camera's approach capacity.
Forecast (next 24h)
Seasonal-naive baseline — the average for the same weekday & hour — scaled by how the last 3 hours ran versus their own baseline, with that trend factor decaying over the forecast horizon.
Signal timing — Webster's method
C = (1.5·L + 5) / (1 − Y)
L = lost time (4s per phase), Y = sum of critical flow ratios (measured peak-hour PCU ÷ saturation flow of 1800 PCU/hr/lane). Green time is split in proportion to each approach's flow ratio, and average delay per vehicle is estimated with Webster's delay formula for both the current and recommended plans — that difference is the "estimated improvement" on the Optimize page. Verified in unit tests against a hand-worked textbook example.
Congestion-aware routing
The drivable road network around the cameras (12.6k nodes, 27.7k edges from OpenStreetMap via osmnx) gets baseline travel times per edge; edges within 600m of a congested camera are slowed by its level (moderate ×1.25, heavy ×1.7, severe ×2.4), then a shortest-path search finds the fastest adjusted route and reports what it avoided.
Full source layout and setup commands are in the project README.