Client: International Salon Supplies (internal R&D)
Role: Engineer (research & prototype)
Period: Mar 2026
Status: Prototype
Tech stack: Python 3.12, Ultralytics YOLO, ByteTrack, PyTorch (CUDA), OpenCV, WebSockets, Next.js 16, React 19, Tailwind CSS, WebRTC, Roboflow
A proof of concept that runs in-store CCTV streams through computer vision to flag possible concealment behaviour in real time. It fuses a custom-trained detector with explainable pose-based rules and per-person temporal filtering, and shows results as a live overlay on a multi-camera dashboard.
Why
Retail shrinkage is a real cost for any store. I wanted to find out, quickly and cheaply, whether off-the-shelf computer vision could give staff a useful early signal from the cameras the store already has. This was a time-boxed spike, not a product.
Architecture
CCTV streams ─► frame-grabber thread per camera (buffer = 1, always the latest frame)
│
▼
AI thread per camera
├─ pose model + ByteTrack → stable person IDs
├─ custom shoplifting detector → boxes matched to people (IoU ≥ 0.30, 3 consecutive frames)
├─ 5 pose rules scaled to torso height
├─ 8-frame temporal filter per person; long dwell → "loitering"
└─ JSON metadata (normalised boxes, status, alert) ─► WebSocket
│
Next.js dashboard: camera grid (WebRTC video) + positioned person markers,
red alert badges, click to go fullscreen
Engineering highlights
Two signals are better than one
A learned detector alone produces opaque false positives. Hand-written rules alone miss too much. The prototype combines both:
- A custom YOLO detector trained on a public, CC-BY dataset (two classes: normal and shoplifting) for 100 epochs.
- Five explainable pose rules: hand to pocket, wrist below hip (bag), hand at torso centre (jacket), crouching, and an under-arm tuck. Every threshold is a ratio of torso height, not a pixel value, so the rules work at any distance from the camera.
A detection only matters if it's matched to a tracked person for three consecutive frames. Each person then has an eight-frame history that smooths out single-frame noise.
Low latency by design
Each camera has a grabber thread with a one-frame buffer, so inference always runs on the latest frame and never falls behind. Video goes to the browser over WebRTC, and AI results travel separately as lightweight WebSocket metadata. The dashboard never decodes frames for inference, and the model never waits on the UI.
Honest results
Validation on the training dataset reached precision ≈ 0.76, recall ≈ 0.67, mAP50 ≈ 0.65. That's encouraging for a spike and nowhere near enough to act on without a human in the loop.
What production would need
- Evaluation on real in-store footage, not a public dataset
- A privacy and legal review, clear signage, and data-retention rules
- A human-review workflow: alerts are prompts for staff attention, never accusations
- Proper per-camera calibration and monitoring for false-positive rates
By the numbers
- ~400 lines of Python, ~380 lines of TypeScript/React
- Multiple concurrent camera streams on a single CUDA GPU
- Built across a handful of focused sessions in March 2026
Takeaway
A short, well-scoped spike answered the question: yes, the building blocks work. It also produced a clear list of what it would take to do this responsibly, which is at least as valuable as the demo.