Crowd-density early warning from multi-camera video
A camera system that tracks people, forecasts when crowd density will cross a danger threshold, and raises the alarm before it does. Which counting model it trusts and how it tracks people were both settled by measurement.
IllustrationHover to gather · click to scatterTap to scatter
What it does
Crowd crushes rarely come without warning: density builds before it turns dangerous. For each camera, the system:
detects people with YOLOv8;
tracks each person from frame to frame with a tracker I built from scratch;
converts the count into people per square metre, using that camera’s floor area;
rates the risk against Fruin’s Level of Service, a pedestrian-safety standard;
forecasts how long until density turns dangerous;
raises an on-screen banner, a tone and a timestamped log entry as the risk climbs;
brings in CSRNet density estimation when the crowd gets too dense for detection alone.
Several cameras run at once, each in its own thread, on one tiled dashboard.
Density (people/m²)
Fruin level
Meaning
Below 0.43
A/B
Free movement
0.43 to 1.08
C/D
Restricted movement
Above 1.08
E/F
Dangerous density
It is an early-warning system for density, not a stampede predictor. It says when density will cross an established safety threshold. It doesn’t predict panic or crowd turbulence, which depend on things it doesn’t measure.
Experiment 1: which counting model to trust
Detection and density estimation are the two standard ways to count a crowd, and it isn’t obvious which to trust, so I measured it. I took five evenly spaced frames from each of three clips shot from different camera positions, counted every visible person by hand, and ran YOLOv8n (at 1280 px input) and CSRNet (ShanghaiTech Part B weights) against those counts.
Clip
Camera position
Typical count
Plaza
Elevated, open floor
~35
Corridor
Ground level, chokepoint
~16
Aerial
High overhead, crossing
~80
Figure 1
Where each method breaks
The share of hand-counted people each model found. Detection is near perfect in the plaza and corridor and drops to 82.6% on the dense aerial clip, where density estimation does better.
The camera Illustration
Share of hand count found Measured
People counted as a share of the hand count, and mean absolute error
Method
Plaza
Corridor
Aerial
Detection · YOLOv8n
98.5%
99.6%
82.6%
Density · CSRNet
41.2%
120.9%
87.1%
Mean abs. error · YOLOv8n
1.3
1.2
14.3
Mean abs. error · CSRNet
22.1
3.0
9.9
Five hand-counted frames per clip, fifteen in all. Some aerial counts were ambiguous and recorded as ranges. The camera drawings illustrate each position; they are not the footage.
The two standard ways of counting a crowd fail in opposite directions — and the one that wins depends entirely on where you put the camera.
Crowd Density Benchmark · 2026
What the numbers show
At low to moderate density, detection wins clearly. In the plaza, YOLO found 98.5% of the people (mean error 1.3) while CSRNet found 41%. My best explanation is domain shift: its weights come from street-level photos, and the plaza is a high view of a bright, low-texture stone floor. That fits the evidence but I haven’t tested it, and one detail argues against it: those weights come from the sparse half of their dataset, yet CSRNet did best on my densest clip.
At high density the ranking flips. On the aerial clip, YOLO’s rate fell to 82.6% as people hid each other from view, while CSRNet reached 87.1% with a lower error. That crossover is what the fusion policy below is built on. In the ground-level corridor CSRNet overcounted, at 120.9%.
Input resolution mattered more than the model. Raising YOLO’s input from 640 to 1280 px took its detections on the aerial clip from 13 to 52, four times as many, with nothing else changed.
Five frames per clip is enough to see a strong effect but not to pin down exactly where the crossover sits. Hand counts carry their own error, and these results describe these three clips, not crowds in general.
When to trust which model
Below 50 people in view, the system trusts YOLO alone. From 50 up it also runs CSRNet and takes the higher of the two counts, since both undercount in dense crowds. It switches CSRNet off again only once the count drops below 40, so a crowd hovering around 50 doesn’t flip it on and off, the same dead band a thermostat uses.
CSRNet takes seconds per frame on a CPU, so while it’s active it runs on every 20th frame and its answer is reused in between. Crowd density doesn’t change much in under a second, so little accuracy is lost.
Experiment 2: does appearance help tracking?
The tracker gives each person an ID and keeps it as they move. A swapped ID means the system has confused two people. I built it from scratch rather than importing one, in the style of DeepSORT, adding one piece at a time and checking each before the next:
A Kalman filter predicts where each person will be next frame. It tracks a box’s centre, aspect ratio and height, each with a velocity. Aspect ratio stays steady as someone walks toward the camera, so shape and distance are kept apart. My first version worked out velocity by differencing positions, and it drifted, barely beating the raw detections. Making velocity part of the filter’s state cut the error on a test track from 6.39 to 4.15 px.
Hungarian assignment pairs predictions with detections by the lowest total cost, not the cheapest pair first. Greedy matching can leave another person with a terrible match, and a bad match is an ID swap.
An appearance check compares colour histograms of each person against their last 30 looks. On the corridor clip, the same person three frames apart scored 0.08 apart on average, against 0.78 for two different people.
Fig. 2 · Two people turn back while hidden · Illustration of the synthetic test below
On synthetic paths, with ten runs per scenario:
Scenario
Position only
+ Appearance
Two people cross in view
0 swaps
0 swaps
They cross while hidden
0 swaps
0 swaps
They turn back while hidden
Swapped in 10/10 runs
0/10 runs
The surprise was the first two rows. A well-specified Kalman filter handles crossings, even hidden ones, on its own, because it predicts correctly where each person comes back out. Appearance only earns its place when movement turns unpredictable.
On real footage, 200 frames per clip, with identical detections in both runs:
Clip
IDs issued, position only
+ Appearance
Speed
Corridor, ~16 people
37
28 (−24%)
8.7 → 7.9 fps
Plaza, ~38 people
85
50 (−41%)
6.6 → 4.9 fps
Appearance helps more in the denser plaza, where there are more crossings to get wrong, and it costs more speed there for the same reason. Fewer IDs suggests less fragmentation, but it isn’t a swap count: it also moves when people enter and leave, or when two people are wrongly merged into one ID. It is not a standard tracking benchmark, and it is two clips, run once each.
Forecasting
The system keeps a fixed window of recent density readings and compares the average of the older half with the newer half to get a rate of change. Time to danger is the gap to the Fruin threshold divided by that rate.
Two safeguards keep the forecast honest. Forecasts beyond 120 seconds are suppressed: as the rate nears zero the estimate swings wildly, so the dashboard says density is “not meaningfully rising” instead of promising “critical in 400 s”. And the time shown is averaged over recent forecasts so it doesn’t flicker.
Limits
On a CPU it runs at 4.9 to 8.7 frames a second for one camera, and slower with several cameras and CSRNet active. Real-time use needs a GPU.
The appearance check uses colour, so it can’t tell apart people dressed alike, and several people in the corridor wear white shirts.
When a lost track is re-acquired by appearance, there is no limit on how far away the match can be, so a lookalike across the frame can inherit someone’s ID. DeepSORT limits this with a gate that widens with the filter’s uncertainty; mine uses a fixed threshold.
A single camera can’t recover real-world scale, so each camera’s floor area is set by hand.
Even at 1280 px, YOLOv8n found about 83% of people in the densest footage.
Next
Homography calibration, so floor area no longer has to be measured by hand.
An uncertainty-based gate on matches, and a re-identification network in place of colour histograms.
YOLOv8x fine-tuned on CrowdHuman for dense scenes.
A larger hand-counted set to locate the crossover precisely, and a standard tracking benchmark (MOTA and IDF1).
Flow-direction and chokepoint analysis, and live camera streams.