Inspiration
Crowd crushes are not freak accidents. Astroworld, Itaewon, Hillsborough: in every case the crowd density crossed a known, measurable line long before anyone died, and nobody with authority could see it. Crowd-safety engineers already have the numbers (Fruin's Level of Service puts the danger line at roughly 4 people/m²), but that knowledge lives in post-incident reports, not in the venue while it's happening. We wanted to turn a cheap camera into an instrument that reads density the way a thermometer reads temperature.
What it does
DENSITY is a browser app with two modes that share one algorithm.
Simulation runs thousands of independent agents drifting toward a stage. Every few seconds it bins them into a 2 m grid, computes people per square metre per cell, colours the grid, and raises alerts when a cell crosses the caution or danger threshold.
Real Video does the same on actual footage. A head detector puts a dot on every head, a one-time four-click calibration maps camera pixels to real-world metres, and the identical density and alert pipeline runs on top. We validated the math on synthetic crowds before trusting it on real ones.
For each grid cell the density is simply
$$\rho_{ij} = \frac{n_{ij}}{A_{\text{cell}}}, \qquad A_{\text{cell}} = 4\ \text{m}^2$$
classified as safe (\( \rho < 2 \)), caution (\( 2 \le \rho \le 4 \)) or danger (\( \rho > 4 \)).
How we built it
Everything is vanilla HTML, CSS and JavaScript with no build step and no server.
- Simulation: an agent-based model. Each dot random-walks with heading drift plus a weighted attraction toward a stage line; people packed near the stage slow to a shuffle.
- Detection: a YOLOv8-nano head detector exported to ONNX and quantized to int8, running entirely client-side through ONNX Runtime Web on WebAssembly. Frames are letterboxed to 640 px, run through the network, and cleaned with non-max suppression at 0.45 IoU.
- Calibration: a homography from pixels to metres, solved with the Direct Linear Transform. Four point correspondences \( (x_i, y_i) \to (X_i, Y_i) \) give eight linear equations in the eight unknowns of
$$H = \begin{pmatrix} h_1 & h_2 & h_3 \ h_4 & h_5 & h_6 \ h_7 & h_8 & 1 \end{pmatrix}, \qquad \begin{pmatrix} X \ Y \ 1 \end{pmatrix} \sim H \begin{pmatrix} x \ y \ 1 \end{pmatrix}$$
which we solve by Gaussian elimination with partial pivoting, implemented from scratch. Head points pass through \( H \) into metres, then into the grid. The inverse mapping draws the grid back onto the video in correct perspective.
- Alerts: a per-cell state machine that fires only on threshold crossings, with a 60-second reminder while a cell stays dangerous, so it never spams.
Challenges we ran into
Camera angle. We benchmarked five detectors and four crowd-counting density models on 50 images from standard crowd datasets with ground-truth counts. Stock whole-body YOLO found under 2 % of people in dense crowds, because in a packed crowd there are no visible bodies, only heads. Head detectors did far better, until we pointed one straight down at a crowd. From directly overhead a person is a disc of hair with two shoulders, and every model we tried had been trained on oblique views where a head has a face. Recall collapsed. We labelled our own overhead frames and began fine-tuning, and in the meantime used the calibration rectangle to monitor the angled part of the frame where detection is reliable.
Shipping a model with no server. The detector plus runtime was 30 MB. We quantized the network to int8 and embedded the WebAssembly runtime so the whole tool is a single HTML file that opens with a double-click.
What we learned
The math is the easy part; the data is the hard part. The homography, the grid binning and the state machine all worked first time once tested. Getting a detector that sees what our camera sees took most of the weekend. We also learned to measure before choosing: an hour of benchmarking saved us from building on a detector that fails at exactly the density levels we care about. And a simulator is not a toy: having a synthetic crowd we could surge on demand let us test the alert logic end to end before we had a single frame of real footage.
What's next
Finish the overhead fine-tune so the full frame is usable from any mounting position. Add flow direction, since the dangerous signal in a crush is often people moving toward an already-dense zone. Run it live at a real event with a mounted camera, calibrated once against tape on the floor.
Built With
- canvas
- computer-vision
- css3
- html5
- javascript
- onnx
- onnxruntime-web
- python
- ultralytics
- webassembly
- yolov8
Log in or sign up for Devpost to join the conversation.