Contents — 27 sections
Project Overview
Edge camera intelligence that records and alerts only when something real happens — a person, not a swaying tree — so you get few, meaningful notifications instead of a hundred false ones.
The reason people stop trusting their security cameras is false alarms. A basic motion-triggered camera fires on everything: a tree swaying, headlights sweeping a wall, a cloud shadow, rain, an insect on the lens, the day-to-night exposure shift. After the tenth pointless 3 a.m. alert, the owner mutes notifications — and then misses the one that mattered. This project fixes that at the source with edge intelligence: the camera itself decides whether motion is worth your attention, recording and alerting only on genuine events (typically a person), so the handful of alerts you do get are ones you actually act on.
It works in two stages, cheap-then-smart. A fast, low-power motion gate (a PIR sensor and/or frame-differencing) wakes the system only when the scene changes, so the expensive part runs rarely. Then an on-device classifier — a small neural network doing person/object detection — confirms whether the motion is something you care about before anything is recorded or sent. This two-stage design is what makes it practical on modest hardware: the classifier is not grinding through every frame of an empty driveway, only the frames where something moved. The whole decision happens on the device, which also means the video and the analysis stay local — a privacy property that matters for a camera watching your home.
Because it only records and notifies on confirmed events, the benefits compound: storage is a timeline of real events rather than gigabytes of empty footage, notifications are trustworthy enough to leave switched on, and review is fast because there is little to review. It is honest about its limits — a small edge model is not a cloud-scale detector, it can miss or misclassify in hard conditions (heavy rain, thermal glare, unusual angles), and it must be tuned to the scene — and it is built with privacy front of mind, keeping footage on local storage rather than a third-party cloud by default. But as a way to turn a noisy, ignored camera into a calm, reliable one, edge-filtered alerting is exactly the right architecture.
What this project does
- Wakes on a cheap motion gate (PIR and/or frame-differencing) before doing heavy work
- Confirms real events with an on-device person/object classifier
- Records and notifies only on confirmed events — not on every movement
- Keeps video and analysis local for privacy by default
- Builds an event timeline instead of hours of empty footage
- Tunes detection to the scene (zones, sensitivity, classes of interest)
- Sends few, trustworthy notifications with a snapshot
Real-World Applications
| Setting | How it is used |
|---|---|
| Home security | A camera that alerts on a person at the door or in the yard but ignores pets, trees and passing cars, so notifications stay meaningful. |
| Small business / premises | After-hours intrusion alerts with event-only recording, reducing storage and review time for a shopkeeper or office. |
| Package / porch monitoring | Detecting a person approaching the door (and, with tuning, a delivery) without a flood of false triggers. |
| Remote property / outbuildings | Low-bandwidth sites where sending only confirmed-event snapshots, not continuous video, is essential. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Two-stage cheap-then-smart pipeline for practical edge performance
- On-device classification to reject trees, shadows, headlights, weather
- Local-first storage and processing for privacy
- Event-only recording — a meaningful timeline, minimal storage
- Region-of-interest zones and per-class alerting
- Trustworthy notifications you can leave enabled
- Honest about edge-model limits and scene tuning
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Advanced |
| Estimated completion time | 14–22 hours |
| Indicative build cost | ₹2,500 – ₹7,000 (board-dependent) |
| Primary discipline | Security |
| Reference platform | ESP32-S3 DevKitC-1 |
Skills you should have (or will pick up)
- Building a two-stage motion-gate → classifier pipeline
- Running a quantised object-detection model on the edge (ESP32-S3 / Raspberry Pi)
- Frame-differencing and PIR gating
- Region-of-interest and per-class alert logic
- Local storage, notification and privacy-preserving design
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32-S3 DevKitC-1 The vector extensions roughly triple TinyML inference speed over the original ESP32. | Dual-core Xtensa LX7 @ 240 MHz, 512 KB SRAM + 8 MB PSRAM, vector instructions for ML, Wi-Fi + BLE 5 | 1 | ₹900 |
| ESP32-CAM (AI-Thinker) + OV2640 No USB-serial on board — needs an FTDI/CP2102 adapter to flash. | ESP32-S chip, 4 MB PSRAM, 2 MP OV2640 camera, microSD slot, on-board LED flash | 1 | ₹620 |
| HC-SR501 PIR motion sensor Give it 60 s to settle after power-up or it fires false triggers. | 3–7 m range, 110° cone, 0.3–200 s adjustable hold, 5 µA idle | 1 | ₹80 |
| microSD card 32 GB A1 class For 24/7 loggers buy a high-endurance card — normal cards die in months. | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 1 | ₹450 |
| TP4056 Li-ion charger + DW01 protection Buy the version *with* protection ICs — the bare charger will over-discharge your cell. | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 1 | ₹45 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | ₹450 |
| Camera module (OV2640/OV5640 or Pi cam) ESP32-S3 for lightweight models; Raspberry Pi for larger detectors | Matched to the compute board; wide-ish FOV for coverage | 1 | ₹500 |
| Raspberry Pi (optional, heavier models) Choose the board to match the model you need | Pi 4/5 for full object detection where ESP32-S3 is too limited | 1 | ₹4,500 |
| IR illuminator (night) | Invisible IR LEDs for low-light detection | 1 | ₹250 |
| Weatherproof housing | IP-rated, clear window, shades the lens from sun/rain | 1 | ₹400 |
Estimated total: ₹8,195, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32-S3 DevKitC-1 | Dual-core Xtensa LX7 @ 240 MHz, 512 KB SRAM + 8 MB PSRAM, vector instructions for ML, Wi-Fi + BLE 5 | 3.3 V logic / 5 V USB | USB-OTG, SPI, I²C, I²S, LCD/camera bus | Datasheet |
| ESP32-CAM (AI-Thinker) + OV2640 | ESP32-S chip, 4 MB PSRAM, 2 MP OV2640 camera, microSD slot, on-board LED flash | 5 V in / 3.3 V logic | UART (programmer required), SPI, I²C | Datasheet |
| HC-SR501 PIR motion sensor | 3–7 m range, 110° cone, 0.3–200 s adjustable hold, 5 µA idle | 4.5–20 V | Digital high on motion | Datasheet |
| microSD card 32 GB A1 class | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 3.3 V | SDIO / SPI | Datasheet |
| TP4056 Li-ion charger + DW01 protection | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 4.5–5.5 V in | micro-USB / pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32-S3 DevKitC-1 | 3.3 V logic / 5 V USB | 180 | The vector extensions roughly triple TinyML inference speed over the original ESP32. |
| ESP32-CAM (AI-Thinker) + OV2640 | 5 V in / 3.3 V logic | 220 | No USB-serial on board — needs an FTDI/CP2102 adapter to flash. |
| HC-SR501 PIR motion sensor | 4.5–20 V | 0.05 | Give it 60 s to settle after power-up or it fires false triggers. |
| microSD card 32 GB A1 class | 3.3 V | 100 | For 24/7 loggers buy a high-endurance card — normal cards die in months. |
| TP4056 Li-ion charger + DW01 protection | 4.5–5.5 V in | 1000 | Buy the version *with* protection ICs — the bare charger will over-discharge your cell. |
Summed typical draw is 1500.05 mA. With a 1.5× design margin the supply should deliver at least 2300 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager → "PubSubClient" by Nick O'Leary |
| TensorFlow Lite for Microcontrollers 2.4.0-alpha | Int8 neural-network inference inside 200 KB of RAM. | Library Manager → "TensorFlowLite_ESP32" |
| FreeRTOS (ESP-IDF) bundled | Task scheduling so networking never blocks sensor sampling. | Bundled with the ESP32 core |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
| sdcard | — | — |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| Camera | parallel/CSI | DVP / CSI | Image capture |
| PIR | OUT | GPIO 13 | Cheap motion wake gate |
| IR illuminator | EN | GPIO 12 | Night lighting |
| microSD | SPI/SDIO | GPIO 39-42 | Local event storage |
| Wi-Fi | on-chip | — | Notifications (snapshot) |
| Status LED | IN | GPIO 2 | Recording/alert indicator |
| TP4056/mains | OUT | 3V3 reg | Supply |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Wake the compute on the PIR (and/or a light frame-difference check) so the classifier is not running on an empty scene draining power/compute.
- On ESP32-S3, use PSRAM for the frame buffer and the model; a detector will not fit or run without it.
- Add an IR illuminator for night detection and switch it via a GPIO tied to a light sensor or schedule.
- Store events to local SD first; treat any cloud/notification path as secondary and secured.
- Shade the lens and keep the housing window clean — rain, glare and a dirty window are the classifier's worst enemies.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
The whole design exists to raise the signal-to-noise ratio of alerts. A camera is useless as a security tool if its notifications are 99% false, because the owner learns to ignore them and thereby misses the 1% that matters. Naive motion detection — triggering on any pixel change — is a false-alarm machine: vegetation moves, light changes, shadows sweep, weather intrudes, exposure auto-adjusts. Turning that into a trustworthy stream of alerts means adding semantic understanding: not "did pixels change?" but "is there a person (or a car, or a specific class) in the scene?". That question can only be answered by a classifier, and answering it well is what makes the camera worth trusting.
Running a classifier on every frame, though, is expensive — in power on a battery device and in compute on a cheap board — and mostly wasted, because most of the time nothing is happening. Hence the two-stage cheap-then-smart architecture. A very cheap gate — a PIR sensor that reacts to warm bodies, and/or a lightweight frame-differencing check that flags gross pixel change — runs continuously at negligible cost and does nothing but decide when to look properly. Only when the gate trips does the system wake the camera and run the neural network on a frame or two. This keeps the average power and compute low while still bringing real intelligence to bear at the moments that matter, which is exactly the trade that makes edge AI practical on modest hardware.
The edge model itself is a small, quantised object detector — the kind that runs in TensorFlow Lite Micro on an ESP32-S3, or a fuller detector on a Raspberry Pi. It takes a captured frame, and outputs the classes and locations of objects it recognises, with confidence scores. The system then applies scene-aware logic: is the detection above a confidence threshold, is it inside a region of interest you care about (the doorway, not the far pavement), and is it a class you want alerts for (a person, perhaps not a cat)? Only when all of that passes does it record the event and send a notification with a snapshot. This layered gating — motion, then class, then confidence, then zone — is what compresses a hundred raw movements into the two or three that genuinely warrant your attention.
Two properties fall out of doing this on the edge, and both matter for a camera. First, privacy: because the frames are captured, analysed and (only if a real event) recorded locally, the video never has to leave the device or go to a third-party cloud — a meaningful assurance for something pointed at your front door. Second, efficiency of everything downstream: storage becomes a compact timeline of real events instead of endless empty footage, bandwidth is spent only on confirmed-event snapshots (vital at low-bandwidth remote sites), and review is fast because there is little to review. The system is candid that a small edge model is fallible — it can miss a person in heavy rain or misjudge an odd angle, and it needs tuning to each scene — and that where higher accuracy is essential a larger model or a cloud stage may be warranted. But the core insight holds: put the intelligence where the camera is, gate it cheaply, and you convert an ignored, noisy sensor into a calm, trustworthy one.
The maths behind it
Two-stage expected cost
Let p = fraction of time the cheap gate trips.
avg_cost ≈ gate_cost + p · classifier_cost
Because p is small (scene mostly static) and
classifier_cost >> gate_cost, the gate slashes average
power/compute versus running the model every frame.
Frame-difference motion gate
Between frames f_t and f_{t-1} (grayscale):
D = Σ |f_t(x,y) − f_{t-1}(x,y)| over the region of interest
motion if D > D_thresh (with a warm-up/adaptation for light)
Slowly adapt a reference frame to absorb gradual lighting
changes so a drifting sun does not count as motion.
Alert decision (layered gating)
alert if:
motion_gate AND
detect(class ∈ classes_of_interest) AND
confidence > C_thresh AND
bbox_centre ∈ region_of_interest
Each clause removes a class of false alarm (empty motion,
wrong object, low-confidence, out-of-zone). Tune per scene.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Choose and set up the compute + camera
Pick the board to match the model: an ESP32-S3 with PSRAM for a lightweight person detector, or a Raspberry Pi for a fuller object detector. Wire the camera and confirm you can capture frames into a buffer the model can consume.
Add an IR illuminator for night operation, switched by a light sensor or schedule.
Wire the cheap motion gate
Connect a PIR to a wake/interrupt pin, and/or implement a lightweight frame-difference check, so the classifier only runs after the gate trips.
Set up storage, notification and housing
Store events to local SD and configure a secured notification path (snapshot on confirmed event). House everything weatherproof with a clean, shaded window.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Implement the gate-then-classify pipeline
On a gate trip, capture a frame, run the classifier, and apply the layered gating (class, confidence, zone) before recording and notifying.
cppedge-pipeline.ino// Runs only after the cheap motion gate trips. struct Detection { int cls; float conf; int cx, cy; }; bool inROI(int cx, int cy) { // region of interest test return cx > ROI_X0 && cx < ROI_X1 && cy > ROI_Y0 && cy < ROI_Y1; } void onMotion() { captureFrame(frameBuf); // wake camera, grab a frame Detection dets[MAX_DETS]; int n = runDetector(frameBuf, dets, MAX_DETS); // TFLite Micro / Pi model bool realEvent = false; for (int i = 0; i < n; i++) { bool wanted = isClassOfInterest(dets[i].cls); // e.g. person if (wanted && dets[i].conf > C_THRESH && inROI(dets[i].cx, dets[i].cy)) { realEvent = true; break; } } if (realEvent) { recordEvent(frameBuf); // save clip/snapshot locally notify(frameBuf); // send snapshot notification } // else: discard, back to watch }void onMotion()This runs only after the cheap gate trips, so the expensive capture-and-classify work happens at the rare moments something actually moved.int n = runDetector(frameBuf, dets, MAX_DETS)The on-device detector returns the objects it recognises with confidence and location — the semantic understanding a raw motion trigger lacks.if (wanted && dets[i].conf > C_THRESH && inROIA detection must be the right class, confident enough, and inside the region of interest — the layered gating that removes trees, low-confidence noise and out-of-zone activity.if (realEvent) {Only a confirmed real event triggers recording and a notification, so storage stays a timeline of meaningful events and alerts stay trustworthy.Tune to the scene
Set the region of interest, the classes of interest, and the confidence threshold for your camera's view; adapt the frame-difference reference to absorb slow lighting changes.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
Smart CCTV Motion Alerts — ESP32-S3 + camera, edge person detection
Two-stage pipeline: a cheap PIR/frame-difference gate wakes an
on-device detector that confirms a real event before recording and
notifying. Local-first for privacy; few, trustworthy alerts.
══════════════════════════════════════════════════════════════════ */
#include "esp_camera.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "person_detect_model.h" // quantised TFLite Micro model
#include <tensorflow/lite/micro/micro_interpreter.h>
#define PIN_PIR 13
#define PIN_IR 12
#define C_THRESH 0.6f
#define ROI_X0 40
#define ROI_X1 200
#define ROI_Y0 20
#define ROI_Y1 160
WiFiClient net; PubSubClient mqtt(net);
// (TFLite Micro interpreter setup elided for brevity — model in PSRAM)
bool detectPerson(camera_fb_t *fb, float &conf) {
// Preprocess fb->buf into the model's input tensor (resize, grayscale,
// quantise), invoke the interpreter, read the person-class score.
preprocessToInput(fb);
invokeModel();
conf = readPersonScore();
return conf > C_THRESH;
}
void recordEvent(camera_fb_t *fb) {
// Save the JPEG to local SD with a timestamped name (event timeline).
saveJpegToSD(fb);
}
void notify(camera_fb_t *fb) {
// Publish a small snapshot + metadata; footage stays local.
mqtt.publish("cctv/1/event", "{\"type\":\"person\",\"conf\":true}");
publishSnapshot(fb); // thumbnail only over the network
}
void setup() {
Serial.begin(115200);
pinMode(PIN_PIR, INPUT);
pinMode(PIN_IR, OUTPUT);
cameraInit(); // PSRAM frame buffer
modelInit(); // load model into PSRAM
WiFi.begin(WIFI_SSID, WIFI_PASS);
mqtt.setServer(MQTT_HOST, 1883);
// Configure PIR as a wake source for deep sleep between events.
esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_PIR, 1);
}
void loop() {
if (!mqtt.connected() && WiFi.status()==WL_CONNECTED) mqtt.connect("cctv-1");
mqtt.loop();
// ── cheap gate: only proceed if the PIR sees motion ──
if (digitalRead(PIN_PIR) == LOW) {
esp_light_sleep_start(); // sleep until PIR wakes us
return;
}
if (isDark()) digitalWrite(PIN_IR, HIGH); // night: illuminate
camera_fb_t *fb = esp_camera_fb_get(); // capture a frame
digitalWrite(PIN_IR, LOW);
if (fb) {
float conf;
if (detectPerson(fb, conf)) { // ── smart confirm ──
recordEvent(fb); // real event: record locally
notify(fb); // and send a snapshot
} // else: discard silently
esp_camera_fb_return(fb);
}
}
Configuration & Calibration
Configuration steps
- Choose the board/model, set the confidence threshold, the region of interest, and the classes you want alerts for.
- Configure the motion gate (PIR sensitivity and/or frame-difference threshold with light adaptation).
- Set local storage retention and the (secured) notification path; keep footage local by default.
- Configure night IR behaviour and any schedule/arming windows.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Scene tuning
Set the ROI to the area you care about and the confidence threshold to balance false alerts against misses; iterate over a day of real events.
Motion gate
Adjust PIR sensitivity/placement and the frame-difference threshold and light-adaptation so ordinary lighting changes and distant vegetation do not wake the classifier constantly.
Night performance
Validate detection under IR illumination; fine-tune with night frames if the model was trained mostly on daytime images.
Dataset, Model & Training
Dataset
The edge detector is a small, quantised person/object model. You can start from a pre-trained lightweight detector (e.g. a MobileNet-SSD or a TFLite Micro person-detection model) and, if needed, fine-tune it on frames from your own cameras so it performs well on your scenes, angles and lighting.
For a home camera, the classes of interest are usually just "person" (and perhaps "vehicle"); a narrower model is smaller, faster and more reliable than a general 80-class detector on constrained hardware.
| Dataset | Size | Licence | Use here |
|---|---|---|---|
| COCO (person/vehicle subset) | ~120k images | CC BY 4.0 (annotations) | Base classes for person/vehicle detection |
| Your-camera fine-tune set | Hundreds–thousands | Your own footage | Adapt to scene, angle, night IR |
| Visual Wake Words | ~100k images | CC BY 4.0 | Tiny person-present model for MCUs |
Data preprocessing
- Resize captured frames to the model's small input (e.g. 96×96 to 300×300 depending on the model and board).
- Convert to the expected format (grayscale for tiny MCU models; RGB for larger detectors) and normalise/quantise to the input tensor's type (usually int8).
- Optionally restrict inference to the region of interest to cut work and false detections.
Model architecture
A lightweight single-shot detector (MobileNet-SSD-class) or, on the tiniest hardware, a binary "person present?" classifier (the Visual Wake Words approach). Both are quantised to int8 so they fit and run in real time on constrained edge hardware.
On a Raspberry Pi you can afford a fuller detector for better accuracy and multiple classes; on an ESP32-S3 you trade down to a tiny model gated hard by the PIR.
| Layer / stage | Shape or configuration | Purpose |
|---|---|---|
| Input | 96×96×1 (MCU) / 300×300×3 (Pi) | Small input keeps inference feasible on the edge |
| Backbone | MobileNet (depthwise-separable) | Efficient feature extraction for edge |
| Head | SSD boxes / binary person score | Detection or simple person-present decision |
| Quantisation | int8 (full-integer) | Fits MCU memory; fast integer inference |
Hyperparameters
| Hyperparameter | Value | Why |
|---|---|---|
| input size | 96–300 px | Smaller = faster/feasible on MCU; larger = more accurate on Pi |
| quantisation | int8 | Required for MCU; big speed/size win |
| confidence threshold | ~0.6 | Trade false positives vs misses per scene |
| NMS IoU | ~0.45 | Merge overlapping boxes |
Training process
- Start from a pre-trained detector to avoid needing huge data; fine-tune on your own labelled frames to close the gap to your specific scenes and night-IR imagery.
- Quantisation-aware training (or careful post-training int8 quantisation with a representative dataset) preserves accuracy after shrinking the model for the edge.
- Validate on held-out frames from the actual cameras, including hard cases (rain, glare, distant/partial people), not just clean images.
Evaluation, Metrics & Deployment
The metrics that matter here are not just detection accuracy but the alert experience: how many false alerts per day, and how many real events missed.
| Metric | Value | What it tells you |
|---|---|---|
| Person mAP (scene set) | ~0.6–0.8 | Detection quality on your fine-tune data (edge model) |
| False alerts / day | target < 1–2 | The number that decides whether alerts stay trusted |
| Missed real events | target ~0 | A missed intrusion is the costly error |
| Inference time | ~50–300 ms | Per frame on the chosen board |
Figures from the reference training run described above — reproduce them before trusting your own changes.
Deployment
- Deploy the quantised model to the board (TFLite Micro on ESP32-S3, TFLite/ONNX on Raspberry Pi) and run it only behind the motion gate.
- Keep the model and footage on-device; send only confirmed-event snapshots off-device.
- Provide a simple way to update the model and re-tune thresholds as scenes/seasons change.
Inference example
# Raspberry Pi variant: gated edge inference with TFLite.
import tflite_runtime.interpreter as tflite
import numpy as np
interp = tflite.Interpreter("person_detect_int8.tflite")
interp.allocate_tensors()
inp = interp.get_input_details()[0]
out = interp.get_output_details()
def is_person(frame, roi, conf_thresh=0.6):
x = preprocess(frame, inp['shape']) # resize + int8 quantise
interp.set_tensor(inp['index'], x)
interp.invoke()
boxes, classes, scores = read_outputs(interp, out)
for b, c, s in zip(boxes, classes, scores):
if c == PERSON and s > conf_thresh and in_roi(b, roi):
return True, s
return False, 0.0
# Only called after the cheap motion gate trips.
def on_motion(frame, roi):
real, score = is_person(frame, roi)
if real:
record_event(frame) # local storage
notify(frame, score) # snapshot only
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Wave a tree branch / shine headlights | Motion gate may trip but the classifier rejects it — no alert |
| Walk a person through the ROI | Detected, recorded, and a snapshot notification sent |
| Person outside the ROI | No alert (zone gating), unless you widen the ROI |
| Leave the scene empty for hours | Few/no classifier runs; minimal storage growth |
| Night with IR | Person still detected; day-trained model may need night fine-tuning |
| Count false alerts over a day | Far fewer than naive motion — the core success metric |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The app shows an event timeline (thumbnails of confirmed detections with time and class), not a continuous stream; storage holds only event clips.
{
"camera": 1,
"type": "person",
"confidence": 0.82,
"roi": true,
"time": "2026-07-27T02:14:07",
"clip": "/sd/events/20260727-021407.jpg"
}
Each entry is a confirmed event with its class, confidence and local clip path — a short, meaningful list, which is exactly what makes the alerts worth leaving switched on.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Gate hard: the PIR/frame-diff stage should keep the classifier idle almost always.
- Use int8 quantised models and PSRAM; keep the input small enough to hit your latency target.
- Deep-sleep between events on battery devices, waking on the PIR.
- Send only snapshots, not video, over the network; keep full footage local.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
- Profile before optimising — print
micros()deltas around each stage and fix the slowest one first.
Safety Precautions
- Respect privacy and law: point the camera at your own property, avoid neighbours' windows and public spaces where recording is restricted.
- Keep footage local by default; if any cloud is used, disclose it and secure it — this device watches private space.
- This confirms object classes, not identities; do not repurpose it as covert or biometric surveillance.
- Do not rely on a single edge camera as a life-safety system; it is a deterrent and awareness tool.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Clean the lens/window; rain, dust and cobwebs degrade detection and cause false motion.
- Re-tune ROI/thresholds seasonally as foliage, sun angle and scene change.
- Update the model periodically and re-validate on recent footage.
- Manage local storage retention so the event timeline does not fill the card.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Rotate the microSD card annually and keep an image of the working system. Cards used as loggers wear out silently.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add on-device tracking to reduce duplicate alerts for one person crossing the scene.
- Add package/vehicle/animal classes with per-class alert rules.
- Add a privacy-preserving option that stores only blurred/redacted footage except on confirmed intrusion.
- Federate several cameras so an event is corroborated across views.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add connectivity — an ESP32 and an MQTT publish turn a local gadget into something you can graph, alert on and analyse over months.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- TensorFlow Lite for MicrocontrollersTensorFlow
- Visual Wake Words — person-present on MCUsResearch
- MobileNet-SSD object detectionResearch
- PIR motion sensors — principlesReference
- Edge AI and privacy — overviewReference