Siddhant Kumar
Project 049 · Security

Smart CCTV Motion Alerts.

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.

Advanced 14–22 hours 33 min read CameraEdgeAlerts
Jump to source Bill of materials
Smart CCTV Motion Alerts — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Advanced
Build time
14–22 hours
Indicative cost
₹2,500 – ₹7,000 (board-dependent)
Platform
ESP32-S3 DevKitC-1
Category
Security
Last updated
28 July 2026
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.

A wall-mounted CCTV surveillance camera
A security camera becomes trustworthy when it alerts only on real events, not every movement. Photograph sourced from Wikimedia Commons — CCTV camera.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Home securityA 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 / premisesAfter-hours intrusion alerts with event-only recording, reducing storage and review time for a shopkeeper or office.
Package / porch monitoringDetecting a person approaching the door (and, with tuning, a delivery) without a flood of false triggers.
Remote property / outbuildingsLow-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

AttributeValue
Difficulty levelAdvanced
Estimated completion time14–22 hours
Indicative build cost₹2,500 – ₹7,000 (board-dependent)
Primary disciplineSecurity
Reference platformESP32-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.

ComponentKey specificationQtyApprox. 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 51₹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 flash1₹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 idle1₹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 recommended1₹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 protection1₹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 discharge1₹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 coverage1₹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 limited1₹4,500
IR illuminator (night)Invisible IR LEDs for low-light detection1₹250
Weatherproof housingIP-rated, clear window, shades the lens from sun/rain1₹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

PartSpecificationSupplyInterfaceReference
ESP32-S3 DevKitC-1Dual-core Xtensa LX7 @ 240 MHz, 512 KB SRAM + 8 MB PSRAM, vector instructions for ML, Wi-Fi + BLE 53.3 V logic / 5 V USBUSB-OTG, SPI, I²C, I²S, LCD/camera busDatasheet
ESP32-CAM (AI-Thinker) + OV2640ESP32-S chip, 4 MB PSRAM, 2 MP OV2640 camera, microSD slot, on-board LED flash5 V in / 3.3 V logicUART (programmer required), SPI, I²CDatasheet
HC-SR501 PIR motion sensor3–7 m range, 110° cone, 0.3–200 s adjustable hold, 5 µA idle4.5–20 VDigital high on motionDatasheet
microSD card 32 GB A1 classA1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended3.3 VSDIO / SPIDatasheet
TP4056 Li-ion charger + DW01 protection1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection4.5–5.5 V inmicro-USB / padsDatasheet
18650 Li-ion cell 3400 mAh + holder3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge3.0–4.2 VHolder / spot-welded tabsDatasheet

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.

LoadSupply railTypical current (mA)Notes
ESP32-S3 DevKitC-13.3 V logic / 5 V USB180The vector extensions roughly triple TinyML inference speed over the original ESP32.
ESP32-CAM (AI-Thinker) + OV26405 V in / 3.3 V logic220No USB-serial on board — needs an FTDI/CP2102 adapter to flash.
HC-SR501 PIR motion sensor4.5–20 V0.05Give it 60 s to settle after power-up or it fires false triggers.
microSD card 32 GB A1 class3.3 V100For 24/7 loggers buy a high-endurance card — normal cards die in months.
TP4056 Li-ion charger + DW01 protection4.5–5.5 V in1000Buy 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.json under 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 dialout group: sudo usermod -aG dialout $USER and 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

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
TensorFlow Lite for Microcontrollers 2.4.0-alphaInt8 neural-network inference inside 200 KB of RAM.Library Manager → "TensorFlowLite_ESP32"
FreeRTOS (ESP-IDF) bundledTask scheduling so networking never blocks sensor sampling.Bundled with the ESP32 core
ArduinoJson 7.xZero-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.

Smart CCTV Motion Alerts — system block diagramFunctional block diagram of the Smart CCTV Motion Alerts system. Cheap gatePIR / frame-diffwake on changeSmart confirmCameracapture frameEdge modelperson/objectAct (only if real)Record eventlocal SDNotifysnapshotOwnerEvent timelinefew, realrightrightnone
Smart CCTV Motion Alerts — system block diagram

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.

Smart CCTV Motion Alerts — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32-S3 DevKitC-13.3 V logic / 5 V USBCameraDVP / CSIImage capturePIRGPIO 13Cheap motion wakegateIR illuminatorGPIO 12Night lightingmicroSDGPIO 39-42Local event storageWi-FiNotifications(snapshot)Status LEDGPIO 2Recording/alertindicatorTP4056/mains3V3 regSupply
Smart CCTV Motion Alerts — wiring schematic
PeripheralPeripheral pinController pinSignal
Cameraparallel/CSIDVP / CSIImage capture
PIROUTGPIO 13Cheap motion wake gate
IR illuminatorENGPIO 12Night lighting
microSDSPI/SDIOGPIO 39-42Local event storage
Wi-Fion-chipNotifications (snapshot)
Status LEDINGPIO 2Recording/alert indicator
TP4056/mainsOUT3V3 regSupply

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.
A USB webcam
The camera module captures a frame only when the cheap motion gate trips. Photograph sourced from Wikimedia Commons — Webcam.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

Smart CCTV Motion Alerts — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32-S3 DevKitC-1 · HC-SR501 PIR motion sensorDriver layerwifi · pubsub · tflmicro · esptaskApplication logicsampling loop · filtering · thresholds · state machinePresentation layerlocal display · serial console · logged output
Smart CCTV Motion Alerts — architecture stack

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

plainTwo-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

plainFrame-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)

plainAlert 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.

Smart CCTV Motion Alerts — firmware flowchartControl flow through the main program loop. Sleep / low-power watchMotion gate tripped?Capture frame(s)Sleep / low-power watchCapture frame(s)Run edge classifierObject of interestpresent?Record + notify with snapshotDiscard; back to watchRecord + notify with snapshotDiscard; back to watch
Smart CCTV Motion Alerts — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. 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.

  2. 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.

  3. 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.

  1. 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.
  2. 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.

cppsmart-cctv-node.ino
/* ═══════════════════════════════════════════════════════════════
   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);
  }
}
if (digitalRead(PIN_PIR) == LOW) { esp_light_sleep_start();The cheap PIR gate keeps the device asleep until it sees warm-body motion, so the camera and model only ever run when something might be there.
bool detectPerson(camera_fb_t *fb, float &conf)The captured frame is preprocessed and run through the quantised on-device model, returning whether a person is present above the confidence threshold — the semantic confirmation.
if (isDark()) digitalWrite(PIN_IR, HIGH)The IR illuminator is switched on only for the moment of capture at night, giving the detector a usable image without wasting power lighting an empty scene.
recordEvent(fb); // real event: record locallyFootage is saved to local SD, keeping the video private and building an event-only timeline instead of continuous storage.
publishSnapshot(fb); // thumbnail onlyOnly a small snapshot leaves the device on a confirmed event, keeping bandwidth low and full footage local.

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.

  1. 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.

  2. 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.

  3. 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.

DatasetSizeLicenceUse here
COCO (person/vehicle subset)~120k imagesCC BY 4.0 (annotations)Base classes for person/vehicle detection
Your-camera fine-tune setHundreds–thousandsYour own footageAdapt to scene, angle, night IR
Visual Wake Words~100k imagesCC BY 4.0Tiny 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.
Smart CCTV Motion Alerts — ML pipelineFrom raw data through training to deployed inference. 1Motion gatePIR / frame-diff2Captureframe from camera3Preprocessresize + quantise4Detectedge model5Gate resultclass/conf/ROI6Record + notifyif real
Smart CCTV Motion Alerts — ML pipeline

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 / stageShape or configurationPurpose
Input96×96×1 (MCU) / 300×300×3 (Pi)Small input keeps inference feasible on the edge
BackboneMobileNet (depthwise-separable)Efficient feature extraction for edge
HeadSSD boxes / binary person scoreDetection or simple person-present decision
Quantisationint8 (full-integer)Fits MCU memory; fast integer inference

Hyperparameters

HyperparameterValueWhy
input size96–300 pxSmaller = faster/feasible on MCU; larger = more accurate on Pi
quantisationint8Required for MCU; big speed/size win
confidence threshold~0.6Trade false positives vs misses per scene
NMS IoU~0.45Merge 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.

MetricValueWhat it tells you
Person mAP (scene set)~0.6–0.8Detection quality on your fine-tune data (edge model)
False alerts / daytarget < 1–2The number that decides whether alerts stay trusted
Missed real eventstarget ~0A missed intrusion is the costly error
Inference time~50–300 msPer frame on the chosen board

Figures from the reference training run described above — reproduce them before trusting your own changes.

Alerts per day: naive motion vs edge-confirmedIllustrative reduction in false alerts from adding on-device confirmation. Naive motion120 /day+ frame-diff ROI40 /day+ edge classifier2 /day
Alerts per day: naive motion vs edge-confirmed

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

pythoninfer.py
# 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.

TestWhat you should see
Wave a tree branch / shine headlightsMotion gate may trip but the classifier rejects it — no alert
Walk a person through the ROIDetected, recorded, and a snapshot notification sent
Person outside the ROINo alert (zone gating), unless you widen the ROI
Leave the scene empty for hoursFew/no classifier runs; minimal storage growth
Night with IRPerson still detected; day-trained model may need night fine-tuning
Count false alerts over a dayFar 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.

jsoncctv-event.json
{
  "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.

An ESP32 development board with the ESP-WROOM-32 module and USB connector
An ESP32-S3 (or Raspberry Pi) runs the on-device detector that confirms a person before recording or notifying. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Still too many false alerts

Likely cause. Threshold too low, ROI too wide, or gate too sensitive

Fix. Raise the confidence threshold, tighten the ROI, and reduce motion-gate sensitivity

Misses real people

Likely cause. Threshold too high, model weak at night/angle

Fix. Lower the threshold slightly; fine-tune the model on your scenes/night frames

Battery drains fast

Likely cause. Classifier running too often (weak gate)

Fix. Strengthen the PIR/frame-diff gate so the model runs rarely; deep-sleep between events

Model won't fit / run on the board

Likely cause. Model too big for the MCU

Fix. Use a smaller/int8 model or a person-present classifier; move to a Pi for fuller detection

Privacy concern about footage

Likely cause. Assuming cloud upload

Fix. Keep footage local by default; send only snapshots; secure any network path

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

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 a millis() 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_t where 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

Why does it still use a PIR if it has a smart model?

To keep it practical. Running the neural network on every frame would drain a battery and overwhelm a cheap board. The PIR/frame-diff gate runs for almost nothing and only wakes the model when something might be there.

How does it avoid alerting on trees and headlights?

It confirms the class before alerting — it asks "is there a person?" not "did pixels change?" — and applies confidence and region-of-interest gating, so swaying branches and sweeping lights are rejected.

Does my video go to the cloud?

By default, no. Frames are captured, analysed and (only on a real event) recorded locally, and just a snapshot is sent as a notification. Keeping footage local is a deliberate privacy choice.

Will it catch everything?

No — a small edge model can miss or misclassify in hard conditions (heavy rain, glare, odd angles) and needs tuning per scene. It hugely improves the alert-to-noise ratio, but it is not an infallible detector.

Do I need a Raspberry Pi or will an ESP32 do?

An ESP32-S3 with PSRAM can run a tiny person model gated by a PIR; a Raspberry Pi affords a fuller, more accurate detector and multiple classes. Match the board to the accuracy you need.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. TensorFlow Lite for MicrocontrollersTensorFlow
  2. Visual Wake Words — person-present on MCUsResearch
  3. MobileNet-SSD object detectionResearch
  4. PIR motion sensors — principlesReference
  5. Edge AI and privacy — overviewReference