Turns one fixed road camera's RTSP stream into per-vehicle speed in km/h, road occupancy as a percentage of a hand-calibrated ROI, and a seven-state congestion label, rendered live on an OpenCV dashboard.
Manual traffic monitoring does not scale, and congestion decisions need numbers while the congestion is happening. This project produces three of them from a single camera: vehicle counts by class, movement speed in km/h, and how much of the road surface is occupied. Detection is a YOLOv8s model fine-tuned on four Vietnamese traffic classes; tracking is ByteTrack; speed comes from a per-zone homography rather than pixel displacement.
Note
This project analyses one fixed camera at a time. Speed and occupancy zones are hand-aligned point by point in traffic_config.json and are only valid for the camera angle they were drawn on. Scope is flow analysis: there is no plate recognition, so individual vehicles are not identified.
Click to watch
RTSP stream
└─▶ Threaded capture (producer-consumer, queue maxsize=10, drop-oldest, TCP transport)
└─▶ YOLOv8s detection + ByteTrack tracking
├─▶ Speed estimator (per-zone homography → EMA filter)
├─▶ Occupancy calculator (Σ vehicle footprint / ROI area)
└─▶ Traffic analytics (rolling means → state rules → trend chart)
└─▶ Dashboard renderer (OpenCV overlay + Matplotlib Agg)
| Thread | Owns | Behaviour |
|---|---|---|
| Capture | RTSP connection, frame queue | Discards the oldest frame when the queue is full; RTSP transport forced to TCP |
| Main | Model, tracker state, analytics buffers, display | Consumes one frame per iteration, 1 s read timeout |
| Role | Choice | Fallback when the file is absent |
|---|---|---|
| Detector | traffic4.pt — YOLOv8s fine-tuned on bus, car, motor, truck |
Stock yolov8n.pt, which does not carry these four classes |
| Tracker | my_tracker.yaml — ByteTrack association parameters |
Ultralytics stock bytetrack.yaml |
| Inference call | model.track(persist=True, conf=0.25) |
— |
Counting is per unique track ID on first entry into an occupancy ROI.
- Each speed zone in
traffic_config.jsonsupplies four pixel points plus the real width and length of the rectangle they map to;cv2.getPerspectiveTransformbuilds one matrix per zone. - The bounding box's bottom-centre is taken as the ground contact point.
- That point is mapped to metres and differenced against its previous mapped position over wall-clock time.
- Track history is deleted when a vehicle leaves every zone, and reset when it crosses into a different zone.
| Guard | Value |
|---|---|
| Minimum time delta | Δt > 0.02 s |
| Minimum track age | > 5 frames |
| Outlier rejection | > 100 km/h holds the previous value |
| Smoothing | v = 0.9·v_prev + 0.1·v_new |
- Occupancy is the sum of nominal per-class real footprints for vehicles inside the ROI polygons, divided by the total real area of those polygons, capped at 100 %.
- Footprints are fixed per class in
VEHICLE_REAL_AREAS(main.py), so a compact car and a large SUV contribute the same area. - The displayed state is evaluated on rolling means — the last 50 speed samples above 5 km/h and the last 30 occupancy samples. First matching rule wins.
| Condition | State |
|---|---|
| No vehicles in ROI, occupancy < 1 % | Empty Road |
| Speed < 5 km/h, occupancy > 15 % | Stopped / Red Light |
| Occupancy > 45 %, speed < 20 km/h | Traffic Jam |
| Occupancy > 45 % | High Density |
| Speed < 25 km/h | Slow Traffic |
| Occupancy > 15 % | Moderate |
| otherwise | Free Flow |
| mAP50 | mAP50-95 | Precision | Recall |
|---|---|---|---|
| 0.953 | 0.742 | 0.903 | 0.921 |
Read from the validation metrics stored inside
traffic4.pt(50 epochs,imgsz=640,batch=16, Ultralytics 8.3.204, checkpoint dated 2025-11-30). The training set is not distributed with this repository, so these figures carry no image or instance denominator here. Behaviour in heavy rain, fog and severe occlusion is unmeasured.
| Runtime | Value |
|---|---|
| End-to-end throughput | ~7–9 FPS |
| Display resolution | 1730 × 720 |
| Host hardware | <RUNTIME_HARDWARE> |
Throughput is read from the on-screen FPS counter, not from an instrumented benchmark. Sampling the committed recording
output/run35/analytics_TestVideo1.mp4at eleven points gives 7–8. The host was not recorded, so the figure is not comparable across machines.
| Requirement | Notes |
|---|---|
| Python | Not pinned anywhere in the repository: <PYTHON_VERSION> |
| Packages | ultralytics, opencv-python, numpy, matplotlib |
| GPU | Optional; Ultralytics selects CUDA when it is available |
| Weights | traffic4.pt at the repository root |
| RTSP source | tools/mediamtx.exe is committed and is a Windows build; FFmpeg is not committed |
Warning
main.py loads the calibration stored under the literal key "TestVideo3.mp4", whatever the stream actually carries. Publish that video, or change the key in the load_config_from_json call, or add a matching entry to traffic_config.json. test.py is the same pipeline pinned to "TestVideo1.mp4".
git clone https://github.com/NithanNguyen/traffic_analysis.git && cd traffic_analysis
pip install ultralytics opencv-python numpy matplotlib
cd tools && ./mediamtx.exe # terminal 1
ffmpeg -re -stream_loop -1 -i <VIDEO>.mp4 -c:v copy -rtsp_transport tcp -f rtsp rtsp://localhost:8554/live_stream # terminal 2
python main.py # terminal 3Press q in the display window to exit.
traffic_analysis/
├── main.py # Entry point: threaded RTSP capture → track → analytics → dashboard
├── test.py # Same pipeline, pinned to the "TestVideo1.mp4" calibration key
├── traffic4.pt # Fine-tuned YOLOv8s weights, 4 classes
├── traffic_config.json # Speed and occupancy zone geometry, keyed by video filename
├── my_tracker.yaml # ByteTrack association parameters
├── LICENSE # MIT
├── assets/images/ # Dashboard capture used above
└── tools/ # MediaMTX Windows binary and its default configuration
Nguyễn Phạm Thiên Ân · Nguyễn Hoàng An · Phan Tiến Đạt — Course project: AI Programming Techniques.
| # | Task | Description | Artifacts |
|---|---|---|---|
| 1 | Vehicle Detection Model Training | Fine-tuned a YOLOv8s detector on four Vietnamese traffic classes (bus, car, motor, truck) and validated it against mAP50, mAP50-95, precision and recall. |
traffic4.pt |
| 2 | Homography-based Vehicle Speed Estimation | Surveyed published speed-estimation methods, then implemented per-zone perspective transformation mapping the bounding-box ground contact point to metric coordinates, with exponential moving average smoothing, minimum-track-age gating and outlier rejection. | main.py, traffic_config.json |
| 3 | Traffic State Classification Logic & Visualization Dashboard | Designed the rule-based inference that derives a seven-state congestion label from rolling means of speed and road occupancy, and the real-time OpenCV dashboard rendering counts, speed, occupancy and trend charts. | main.py |
Built on Ultralytics YOLOv8, ByteTrack, OpenCV and MediaMTX.
- This repository's source is released under the MIT License; see
LICENSE. - The
traffic4.ptcheckpoint was produced with Ultralytics, whose metadata it carries and which is distributed under AGPL-3.0 — check Ultralytics licensing before reusing the weights.