Skip to content
RS-S05Systems / Hardware2024 to 2025Ongoing

Sentinel

A CCTV install grown into a self-hosted edge AI platform: on-site re-identification, attribute reading, ANPR and pattern-of-life analytics that adapt to the site over time.

Overview

Sentinel is a self-hosted CCTV analytics platform I built for a single-tenant private office with a high-security requirement. The brief was not "record footage in case something happens" but "know who enters, when, with whom, carrying what, and flag anything off-baseline". The analytics layer is the product rather than a bolt-on, and everything runs on-premises so raw footage and the identity database never leave the building. The site is a monitored access environment with entry signage, so the biometric layer is consented and disclosed. It grew out of a conventional install; everything downstream of the sensor is now custom.

Capture and storage

Capture is eight Dahua PoE cameras in dual-stream mode, the most important decision in the build. The high-resolution H.265 main stream is written to disk continuously while the low-resolution sub-stream feeds inference, cutting compute by roughly an order of magnitude; sharp face and plate crops come from the time-synced main stream. Two approach cameras sit at about 1.7 m for a cooperative face angle, a fast-shutter ANPR camera at the gate is exposure-locked for retroreflective plates and still reads after dark, an 8 MP wide camera keeps track continuity, and four interior cameras cover the choke points. Recording lands on six Seagate Exos 16 TB drives in RAID 6: 96 TB raw, about 64 TB usable, and roughly 100 days of continuous H.265 before the ring buffer wraps. The recorder is Frigate rather than a locked appliance NVR: I want raw RTSP access and somewhere to hang the ML pipeline.

The model stack

Inference runs on a Jetson Orin Nano, around 67 TOPS, under NVIDIA DeepStream: hardware decode, detector, tracker and downstream models all resident on the GPU as TensorRT-optimised engines, at 5 to 10 fps. Data flows through YOLOv11 for people, vehicles and bags, BoT-SORT for stable track IDs, SCRFD face crops feeding ArcFace 512-dimensional embeddings, OSNet for body re-identification (the workhorse when faces are not available), a pedestrian-attribute model (RAP, PA-100K) for garment colours, headwear and carried objects, and a plate detector with OCR. Every detection collapses to one event row in Postgres with pgvector; each new embedding runs a nearest-neighbour search against the gallery, above threshold a known identity, below it a new persistent pseudo-identity.

A system that improves on site

A model adapted to my cameras, lighting and recurring population beats a stock one by a wide margin, so Sentinel harvests its own training data through two mechanisms I keep separate. The first is online gallery evolution with no retraining: a new embedding is enrolled only when match confidence is high and the crop passes a quality gate (roughly 80 px between the eyes, head yaw under 30 degrees, detection confidence over 0.9), and galleries are capped near 50 vectors and evicted by diversity so they do not drift toward whatever someone wore most recently. The second is periodic retraining: weekly, or when drift detection triggers it, site-specific crops fine-tune the embedding models on a workstation GPU (the Jetson only infers), using ArcFace margin or triplet loss with hard positives and negatives mined from confirmed identities. Around it sits an MLOps spine: crops in a MinIO data lake, an uncertainty-ranked queue feeding CVAT so labelling goes on the two percent of cases that actually move the model, fine-tunes tracked in MLflow, and a challenger engine run in shadow, promoted only if precision and recall improve on a held-out validation set.

Pattern of life and the link out

On top of the event stream sits the pattern-of-life layer, really four modelling problems: per-identity rhythms via STL decomposition or Prophet, association structure as a graph with Leiden community detection over recency-decayed co-presence, HDBSCAN separating staff, deliveries and sporadic visitors with no labels, and an anomaly ensemble (Isolation Forest, autoencoder reconstruction error, an LSTM autoencoder over identity timelines) scoring events live off Redis Streams into Grafana. The site runs on a 5G modem behind carrier-grade NAT and cannot accept an inbound connection; a WireGuard mesh with Tailscale on top has both ends dial out, and since inference stays local the link carries only events, thumbnails and requested clips. Nothing here identifies the site or the client, and any footage shown has faces, plates and neighbouring property blurred.

The hardest problem

The failure mode that shaped the whole design is feedback-loop drift. A system that trains on its own confident matches will happily auto-enrol its own mistakes, and then the errors compound: it becomes confidently wrong and reinforces itself, degrading while its confidence scores keep climbing, which is worse than useless because it looks like it is working. Breaking that loop is the entire point of the quality gates on enrolment, the human adjudication queue for near-threshold decisions, and the held-out validation set a challenger model must beat before it replaces the incumbent. It also forces honesty about limits. Re-identification is not identity: it leans on clothing and build and degrades when someone changes outfit, and face recognition needs a cooperative angle and enough pixels. The system is strong at "probably the same person, this pattern" and weak at anything I would treat as proof, so thresholds and human review are load-bearing parts of the design, not add-ons.

Start a project

Want something built like this?

Everything on this page was designed, built and hosted by one person. Tell me what you need and I reply with scope, cost and timeline, usually within two working days.

Sentinel · Raafay Siddiqui Projects