Directional line counting
Drag the line anywhere on the frame. Crossings are attributed per class and per direction, with a rolling flow rate.
Kestrel detects, tracks and counts people and vehicles on ordinary CPUs. Directional line counts, zone occupancy, dwell time, density heatmaps and capacity alerts — streamed live over a WebSocket, with the geometry editable while the stream runs.
The overlay above is the real renderer driven by a browser-side simulator, so this page never wakes the inference server. Open the console for actual detections.
edge tier, 2 CPU threads, 800 px
edge tier mean, p95 15.2 ms
concourse scene, no GPU
accuracy tier (YOLOX-S)
Every stage is in the repo — there is no hosted vision API behind this.
OpenCV pulls frames from a bundled clip, an upload, or JPEGs pushed up from a browser webcam. File playback skips frames to stay wall-clock accurate instead of drifting into slow motion.
YOLOX on ONNX Runtime, CPU only. Letterbox pre-processing, grid decode and class-wise NMS are hand-written in NumPy, so the serving image needs no PyTorch.
A ByteTrack-style tracker with three passes: IoU on predicted boxes, a second pass on low-confidence detections to survive occlusion, then a size-gated proximity pass for fast movers.
Signed-side crossing tests give directional counts. Polygon zones give occupancy, dwell time and capacity alerts. A decaying grid accumulates the density heatmap.
One WebSocket per viewer: a JSON envelope then the matching JPEG. Slow clients get frames dropped rather than back-pressuring the pipeline.
Five-second rollups and the event log go to SQLite through a single writer thread. Prometheus counters cover frames, crossings, latency histograms and live sessions.
Each one is live in the console, adjustable while the stream runs.
Drag the line anywhere on the frame. Crossings are attributed per class and per direction, with a rolling flow rate.
Draw a polygon, set a capacity, get live occupancy, peak, entries, average dwell time and threshold alerts with cooldown.
A decaying accumulator over a 48x27 grid, streamed as bytes and composited on the client — shows where pressure builds, not just how much.
Swap between Nano, Tiny and S live, mid-session. The trade-off is stated in mAP and milliseconds, not adjectives.
Bundled scenes for a fair comparison, webcam ingest over the same socket, or upload a clip up to 40 MB.
Give the counting line a real-world length and pixel velocity becomes km/h. Without that calibration the field stays empty instead of guessing.
Benchmarked with scripts/bench.py — Intel i5-11400H, 2 ONNX Runtime threads, 800 px stream width.
| Tier | Params | mAP | Infer | FPS |
|---|---|---|---|---|
| YOLOX-NanoEdge tier — Raspberry-Pi class | 0.9M | 25.8 | 13.6 ms | 57 |
| YOLOX-TinyDefault — 26.7 fps on 6 threads | 5.1M | 32.8 | 54.9 ms | 16.7 |
| YOLOX-SAccuracy tier — 7.5 fps on 6 threads | 9.0M | 40.5 | 216 ms | 4.4 |
| Scene | Density | Unique IDs | Mean track life |
|---|---|---|---|
| Junction Flow | 19 vehicles/frame | 41 | 67 frames |
| Concourse Crowd | 26 people/frame | 56 | 73 frames |
| Boulevard Mixed | 15 people/frame | 86 | 25 frames |
Fewer unique IDs for the same object count means fewer identity switches. Vehicles hold their identity for ~5.5 s at a time; dense pedestrian traffic is the honest weak spot.
A counter you cannot audit is a counter you cannot use.
No appearance re-identification. In a dense crowd an occluded person can come back with a new ID — measured at ~86 unique IDs for 15 people per frame on the boulevard scene, against ~41 for 19 clean vehicles.
Speed is a planar estimate from one calibrated length. It is useful for relative flow, not for enforcement.
CPU-only by design. The accuracy tier runs at roughly 8 fps on two threads; the free-tier deployment defaults to the edge tier.
Counts are camera-relative. Two cameras watching the same junction are two independent counters — there is no cross-camera fusion.
The console opens on a live session in a couple of seconds. Drag the counting line, set a zone capacity, switch detector tiers and watch the latency change.