MALANG · GMT+7Connect
← / Projects / traffic

Counting cars is not measuring traffic.

Malang's CCTV cameras were already pointed at the road. My thesis turned them into a congestion sensor — two YOLO11 models, four traffic-flow features, and a classifier that calls the jam at 18–22 frames a second.

CONTEXT
Undergraduate thesis · defended 2025
WHERE
Petra Christian University
FEEDS
Malang City CCTV · Diskominfo
OUTCOME
97.5% accuracy · 18–22 FPS

OVERVIEW

A congestion sensor built entirely out of a CCTV feed. Two fine-tuned YOLO11n models — one detecting vehicles, one segmenting the road surface — turn every frame into six numbers describing how traffic is behaving, and a small classifier reads those numbers as congested or not. 97.5% accuracy on the unseen test set, with the whole pipeline running at 18–22 FPS. My undergraduate thesis at Petra Christian University, defended in 2025 against live feeds from Malang City.

Malang is a hard case on purpose. Indonesian traffic is heterogeneous and motorcycle-dominated — motorcycles are over 83% of vehicles nationally — so a detector trained on car-shaped Western traffic has very little to say about it, and a model that can't see motorbikes can't see the jam.

MY ROLE

Sole engineer. I collected and hand-annotated all three datasets, fine-tuned both YOLO11 models, wrote the feature-extraction layer that turns boxes and masks into traffic numbers, trained and compared the two classifiers, ran the evaluation — and defended the whole thing.

PROBLEM

Congestion in Indonesian cities is measured badly or not at all, and it is expensive either way.

  • The cost is national-scale. Congestion is estimated to cost IDR 63.4 trillion a year in lost economic activity and transport efficiency (DirJen Kemenhub, 2024).
  • The instruments are the obstacle. Existing monitoring depends on costly, invasive hardware — in-ground loops, roadside sensors — or trades away accuracy to run in real time (Cui et al., 2020). Most intersections therefore go unmeasured.
  • Vision systems take the easy signal. Earlier camera-based work classifies on vehicle count alone, ignoring density, occupancy and speed. Twenty vehicles moving freely and twenty vehicles stopped are the same count.

So the thesis had two questions to answer: does classifying on four traffic-flow features — flow, occupancy, density and speed — detect congestion more accurately than counting? And how accurate is YOLO11 in the first place on a vehicle population that is mostly motorbikes?

APPROACH

Measure behaviour, not headcount. The detector's job is not to answer the question — it is to produce the raw material the four features are computed from.

The four features

FeatureDerived fromWhat it catches
FlowPer-class detection counts — flow_car, flow_motorbikeHow much traffic is passing
DensityDetected vehicle area against segmented road area — density_car, density_motorbikeHow tightly packed it is
OccupancyShare of the drivable road covered by vehiclesHow full the asphalt is
SpeedFrame-to-frame motion of detectionsWhether it is moving at all

Six columns in the dataset — flow and density are split by vehicle class — plus the binary `congested` label.

Road segmentation is what makes density and occupancy mean anything. Without it, density is vehicles-per-frame, which changes with camera angle rather than with traffic.

Three datasets, all hand-labelled

DatasetSizeLabellingAugmentation
Vehicle detection850 images · 2 classesBounding boxes by hand in RoboflowBrightness, exposure, blur
Road segmentation430 imagesPolygons by hand in RoboflowRotation, horizontal flip, colour correction
Congestion classification20,113 rows · 6 featuresGenerated by the pipeline, labelled congested

The classification set is near-balanced — 10,059 not congested (50.01%) against 10,054 congested (49.99%) — which is what makes accuracy a fair headline number rather than a flattering one.

Vehicle detection dataset — a raw CCTV frame beside the same frame with Car and Motorbike boxes.
Vehicle detection dataset — a raw CCTV frame beside the same frame with Car and Motorbike boxes.
Road segmentation dataset — the same street with the drivable surface drawn as polygons.
Road segmentation dataset — the same street with the drivable surface drawn as polygons.

ARCHITECTURE

Mermaid · flowchart LR
RENDERING DIAGRAM…

Two branches of a single frame meet again at the feature vector: the detector says what and how many, the segmenter says out of how much road, and the motion between frames says how fast. Only then does anything classify.

How each model was trained

Mermaid · flowchart TB
RENDERING DIAGRAM…

The same shape ran three times — YOLO11n for detection, YOLO11n-seg for segmentation, and the classifiers over the extracted features. Only the dataset and the metrics change; the SVM adds a grid search over its hyperparameters in the training step.

Speed is measured using Lucas-Kanade sparse optical flow — tracking the pixel displacement of detected vehicle centroids frame-to-frame and converting it to a normalised speed feature. Combined with SSIM-based frame sampling to skip visually redundant frames, this keeps the pipeline at 18–22 FPS on real CCTV footage.

RESULTS

97.5%
CONGESTION ACCURACY · TEST SET
90.8%
VEHICLE DETECTION mAP@50
72.7%
ROAD SEGMENTATION MASK mAP@50
18–22
FPS END TO END

Vehicle detection — YOLO11n

ClassPRmAP@50mAP@50-95
All0.8530.8310.9080.682
Car0.8620.9070.9520.799
Motorbike0.8440.7540.8640.564

14.4 ms per image — fast enough to run on the live stream rather than on stored clips.

Cars are close to solved at 95.2% mAP@50. Motorbikes are the honest number: 75.4% recall, and mAP@50-95 of 56.4%. They are small, they cluster, and in a queue they physically occlude one another — which is exactly the condition the system is meant to detect.

Detection evaluation — normalised confusion matrix and the precision-recall curve per class.
Detection evaluation — normalised confusion matrix and the precision-recall curve per class.
Detection on free-flowing traffic, and on a dense high-occlusion queue at the same junction.
Detection on free-flowing traffic, and on a dense high-occlusion queue at the same junction.

Road segmentation — YOLO11n-seg

72.7%
MASK mAP@50
83.1%
PRECISION
67.1%
RECALL
48.2%
MASK mAP@50-95

Decent in daylight and clearly the weaker half of the pipeline at night: glare and uneven street lighting break the road mask into fragments, and everything computed against road area degrades with it.

Segmentation in daylight against the same model at night — one clean mask, three fragmented ones.
Segmentation in daylight against the same model at night — one clean mask, three fragmented ones.

Feature extraction

The raw YOLO outputs — boxes and masks — are converted per frame into the six numerical features, printed alongside the overlay while the stream runs. This is the layer that made the dataset: 20,113 rows of real traffic state.

Live feature extraction — FPS, car and motorbike counts, density, occupancy and speed per frame.
Live feature extraction — FPS, car and motorbike counts, density, occupancy and speed per frame.

The classifier — two of them

ModelAccuracyPrecisionRecallF1
Neural network97.54%98%98%98%
SVM · grid-searched97.38%98.2%96.5%97.35%

NN figures are the macro average over both classes; its test ROC AUC is 0.9967. The deck quotes the NN at 97.54% on the results slide and 97.57% in the conclusion — confirm which is final.

Both were trained on the same 20,113 rows and both land within two-tenths of a percent of each other, which says more about the features than about either model. The neural network is the one the conclusion runs on.

PYTORCH · TrafficClassifier
TrafficClassifier(
  (layer_1):     Linear(in_features=6, out_features=64, bias=True)
  (relu1):       ReLU()
  (dropout1):    Dropout(p=0.3, inplace=False)
  (layer_2):     Linear(in_features=64, out_features=32, bias=True)
  (relu2):       ReLU()
  (dropout2):    Dropout(p=0.3, inplace=False)
  (output_layer):Linear(in_features=32, out_features=1, bias=True)
  (sigmoid):     Sigmoid()
)

Six inputs, two hidden layers of 64 and 32 units, ReLU with 0.3 dropout on each, one sigmoid output. Small on purpose — the features carry the signal, so the model doesn't have to.

The classifier live on the stream — the same junction called Not Congested and Congested.
The classifier live on the stream — the same junction called Not Congested and Congested.

WHAT'S WEAK

Two numbers in the results are worse than the headline, and both were in the defence rather than hidden behind it.

WEAKEST NUMBER
Motorbike recall — 75.4%
· One in four motorbikes is missed in dense, heavily occluded queues· Fix: occlusion-specific augmentation· Fix: DeepSORT tracking to hold identity through a crowd· Fix: more high-occlusion training examples
SECOND WEAKEST
Segmentation mAP@50-95 — 48.2%
· Mask precision falls off at night — glare and uneven lighting· Fix: more night-time and multi-angle images· Fix: evaluate alternative segmentation architectures

And a scope limit worth stating plainly: every number here is measured on Malang City CCTV. The thesis makes no claim about other cities, other camera heights, or other traffic mixes.

STACK

  • Python
  • YOLO11
  • PyTorch
  • OpenCV
  • Lucas-Kanade flow
  • scikit-learn · SVM
  • SSIM sampling
  • Roboflow

TIMELINE

  1. OCT — DEC 2024 · PROPOSAL

    Literature review, problem scoping and proposal document — establishing the four-feature hypothesis and choosing YOLO11 as the detector.

  2. JAN 2025 · PROPOSAL DEFENCE

    Proposal defended and approved at Petra Christian University.

  3. JAN — MAY 2025 · BUILD

    Dataset sampling and labelling in Roboflow (850 detection frames, 430 segmentation frames), fine-tuning both YOLO11n models, extracting 20,113 feature rows, and training the NN and SVM classifiers.

  4. JUN 2025 · THESIS DEFENCE

    Full pipeline — detection, segmentation, Lucas-Kanade speed, classification — running live at 18–22 FPS. 97.5% accuracy on the unseen test set.

LESSON

The feature engineering beat the bigger model. Counting vehicles is the obvious signal and the wrong one — twenty moving and twenty stopped are the same count. Once flow, density, occupancy and speed were right, two hidden layers were enough to separate them, and a grid-searched SVM landed within two-tenths of a percent of the network. The work was never in the classifier.