Skip to content
Real-time computer vision

Turn a camera feed intonumbers you can act on.

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.

  • No GPU required
  • 5 bundled scenes
  • Webcam & clip upload
  • Prometheus metrics
kestrel · junction · previewsimulated overlay
In
0
Out
0
Tracked
0
Unique
0

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.

End-to-end
57fps

edge tier, 2 CPU threads, 800 px

Detector latency
13.6ms

edge tier mean, p95 15.2 ms

Tracked at once
28objects

concourse scene, no GPU

COCO mAP
40.5

accuracy tier (YOLOX-S)

Pipeline

Six stages, one thread per session

Every stage is in the repo — there is no hosted vision API behind this.

  1. 01

    Decode

    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.

  2. 02

    Detect

    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.

  3. 03

    Associate

    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.

  4. 04

    Analyse

    Signed-side crossing tests give directional counts. Polygon zones give occupancy, dwell time and capacity alerts. A decaying grid accumulates the density heatmap.

  5. 05

    Stream

    One WebSocket per viewer: a JSON envelope then the matching JPEG. Slow clients get frames dropped rather than back-pressuring the pipeline.

  6. 06

    Persist & observe

    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.

Capabilities

What you can measure

Each one is live in the console, adjustable while the stream runs.

Directional line counting

Drag the line anywhere on the frame. Crossings are attributed per class and per direction, with a rolling flow rate.

Zone occupancy & dwell

Draw a polygon, set a capacity, get live occupancy, peak, entries, average dwell time and threshold alerts with cooldown.

Density heatmap

A decaying accumulator over a 48x27 grid, streamed as bytes and composited on the client — shows where pressure builds, not just how much.

Three accuracy tiers

Swap between Nano, Tiny and S live, mid-session. The trade-off is stated in mAP and milliseconds, not adjectives.

Your camera or your clip

Bundled scenes for a fair comparison, webcam ingest over the same socket, or upload a clip up to 40 MB.

Speed estimation, honestly

Give the counting line a real-world length and pixel velocity becomes km/h. Without that calibration the field stays empty instead of guessing.

Measured

Throughput, accuracy and tracking quality

Benchmarked with scripts/bench.py — Intel i5-11400H, 2 ONNX Runtime threads, 800 px stream width.

Detector tiers
TierParamsmAPInferFPS
YOLOX-NanoEdge tier — Raspberry-Pi class0.9M25.813.6 ms57
YOLOX-TinyDefault — 26.7 fps on 6 threads5.1M32.854.9 ms16.7
YOLOX-SAccuracy tier — 7.5 fps on 6 threads9.0M40.5216 ms4.4
Tracking quality (14 s, 12 fps, Tiny)
SceneDensityUnique IDsMean track life
Junction Flow19 vehicles/frame4167 frames
Concourse Crowd26 people/frame5673 frames
Boulevard Mixed15 people/frame8625 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.

Limits

What it does not do

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.

Stack

Built to be deployed, not demoed

Inference
ONNX Runtime · YOLOX (Apache-2.0) · NumPy post-processing
Service
FastAPI · WebSockets · threaded session workers
Storage
SQLite (WAL) rollups · single writer thread
Observability
Prometheus counters, gauges and latency histograms
Frontend
Next.js 15 · TypeScript · Tailwind · Canvas 2D · Recharts
Delivery
Docker · GitHub Actions · Render (API) · Vercel (UI)

Point it at a junction, a doorway, or your own webcam.

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.