Skip to content
IntermediateESP32esp32-camface-detectionsecurity-camera

ESP32-CAM Face Detection Security Camera with Motion Alerts

Turn a $15 ESP32-CAM into a smart security camera with face detection, live video streaming, and motion alerts — all running on-device with no cloud needed.

SolderHub10/17/2026 14 min read 0 views

Overview

The ESP32-CAM is one of the best value components in electronics -- for under $10 you get a WiFi-enabled microcontroller, a 2MP camera, and a flash LED on a board smaller than a matchbox. This project turns that board into a self-contained security camera: live video streaming to any browser on your local network, on-device face detection with basic recognition, and an instant Telegram notification the moment something triggers it. Everything here runs locally on the ESP32 itself -- no cloud vision API, no monthly subscription, no dependency on a service that might change its pricing or shut down.

The result isn't a replacement for a commercial security system with cloud recording and professional monitoring, but for watching a front porch, a side gate, or a workshop entrance and getting a phone notification when someone's there, it covers the core of what most people actually want from one.

Completed ESP32-CAM security camera setup, mounted and streaming live video with motion alerts active

Components

6 items
NameQtyLink
ESP32-CAM module (AI-Thinker)×1—
FTDI programmer / USB-to-TTL adapter (3.3V)×1—
5V 2A power supply×1—
Jumper wires×1—
microSD card, 8-32GB (optional, for local snapshot logging)×1—
External 2.4GHz antenna (optional, for range)×1—

Need these parts? Get this build sheet by email

Parts list with buy links, plus new builds as they're published. By subscribing you agree to our Privacy Policy. Unsubscribe anytime.

Full parts list laid out before assembly -- ESP32-CAM, FTDI adapter, and power supply

Wiring for Flashing

FTDIESP32-CAM
GNDGND
VCC5V
TXU0R
RXU0T
GNDIO0 (for flash mode only)

The IO0-to-GND connection puts the board into bootloader mode for uploading -- it needs to be removed before normal operation, since leaving it grounded on every boot prevents the sketch from ever running.

⚠️ Warning

Power the ESP32-CAM from a proper 5V supply during flashing and normal operation, not the FTDI adapter's own 3.3V/5V pin if it's a small USB-powered unit -- the camera and WiFi radio together draw more current than many FTDI adapters can reliably source, and marginal power is the single most common cause of random resets on this board.

Close-up of the FTDI-to-ESP32-CAM wiring used during firmware flashing, with IO0 grounded for bootloader mode

Uploading the Code

This project builds on the standard camera web server example rather than starting from scratch, since it already handles the streaming and detection groundwork reliably:

Steps

  1. 1Open File → Examples → ESP32 → Camera → CameraWebServer in Arduino IDE
  2. 2Uncomment #define CAMERA_MODEL_AI_THINKER and comment out any other board defines
  3. 3Fill in your WiFi ssid and password in the config section
  4. 4Set Tools → Partition Scheme to Huge APP, and Tools → PSRAM to Enabled
  5. 5Flash at 115200 baud with IO0 grounded, then remove the IO0 jumper and press reset
  6. 6Open Serial Monitor at 115200 baud and note the IP address printed after WiFi connects

Enabling Face Detection and Basic Recognition

Open the board's IP address in a browser -- Chrome renders the control panel most reliably, though most modern browsers work fine. Scroll down through the settings panel and enable:

  • Face Detection -- ON
  • Face Recognition -- ON (optional)

Detection alone draws a bounding box around any face in frame and needs no further setup. Recognition is a separate, heavier feature worth understanding before relying on it: it's a lightweight template-matching system, not a deep-learning face embedding model, and it only works for faces enrolled through the same web UI using the Enroll Face button that appears once recognition is turned on. It compares new frames against those enrolled templates and reports a match or no-match -- reasonably reliable for telling "an enrolled face" from "an unenrolled face" in consistent lighting, considerably less reliable than a proper recognition pipeline at telling specific people apart in poor lighting or from an angle.

CameraWebServer control panel showing the Face Detection and Face Recognition toggles with the Enroll Face button

How the On-Device Recognition Actually Works

Worth being specific about this, since "face recognition" as a phrase implies more than the stock example actually delivers. The enrollment step captures a handful of reference frames and reduces them to a lightweight feature template -- nowhere near the depth of a proper embedding model trained on thousands of faces. That makes it fast and genuinely usable on hardware this small, but it also means it's meaningfully more sensitive to lighting changes, angle, and even things like glasses or a hat than a cloud-based recognition service would be.

For a security camera use case, the practical takeaway is this: treat "recognized" as "probably the enrolled person, in similar conditions to when they were enrolled" rather than a confident identity match. It's a genuinely useful filter for reducing alert noise -- an unrecognized face is more alert-worthy than a recognized one -- but it's not a system to rely on for anything requiring certainty.

Adding Motion-Triggered Telegram Alerts

The stock example only shows detections in the browser -- getting a phone notification when someone's at the door needs a Telegram bot wired into the detection callback:

C++
Telegram notification received on a phone the moment the camera detects a face

Avoiding Alert Spam

Wiring sendAlert() directly into the raw per-frame detection callback sounds reasonable and goes badly fast -- at even a modest 5-8 detection FPS, a person standing in frame for a few seconds triggers dozens of near-identical Telegram messages before they've even finished walking past. A simple cooldown timer fixes this cleanly:

C++

Thirty seconds is a reasonable starting point for most doorway or entrance placements -- long enough to avoid spamming a single visit as multiple alerts, short enough that a second, genuinely separate visit a few minutes later still gets its own notification. Adjust based on how the specific location gets used; a busy front door might want a longer cooldown, while a rarely-visited side gate can afford a shorter one.

Performance Tuning: Resolution vs. Frame Rate

Resolution has an outsized effect on both streaming smoothness and detection responsiveness on this hardware:

ResolutionApprox. streaming FPSDetection usability
QVGA (320x240)20-25 fpsBest -- detection keeps up comfortably
VGA (640x480)~15 fpsGood balance of quality and responsiveness
SVGA (800x600)~8-10 fpsDetection starts to lag noticeably
UXGA (1600x1200)~3 fpsDetection largely unusable in real time

VGA is the practical sweet spot for a security camera specifically -- enough resolution to make out faces and details in the stream, without dropping frame rate low enough that a fast-moving subject gets missed between frames entirely. Reserve UXGA for still-photo capture use cases rather than live detection.

A couple of other settings worth checking: confirm the router is broadcasting on 2.4GHz -- the ESP32-CAM's radio doesn't support 5GHz at all, and connecting to the wrong band isn't an option, it simply won't associate. And where range is marginal, an external antenna mod (desoldering the onboard trace antenna's jumper and adding a u.FL connector for an external antenna) measurably improves both connection stability and achievable frame rate at distance, since a weak WiFi signal causes the same kind of stutter and dropped frames as an underpowered board does.

Visual comparison of stream quality and frame rate across QVGA, VGA, and UXGA resolution settings
External 2.4GHz antenna mod on the ESP32-CAM board for improved WiFi range and stream stability

Powering the Camera for 24/7 Operation

A security camera that's only running when someone remembers to plug it in isn't especially useful, so power reliability matters more here than on a project that only runs occasionally. A dedicated 5V 2A wall adapter with a short, thick USB cable is the baseline -- thin phone-charger cables and cheap adapters are the most common cause of intermittent resets on a board that's otherwise wired correctly.

For a semi-permanent install, running the ESP32-CAM off a small 5V buck converter fed from an existing 12V supply (common near a gate, garage, or outdoor lighting circuit) avoids relying on a wall outlet being conveniently nearby, and is worth considering for anything mounted outdoors or in a location where a dangling USB cable isn't practical.

Dedicated 5V power supply setup for continuous, reliable operation of the security camera

Common Mistakes

Alerts firing dozens of times per visit. This is the cooldown timer problem covered above -- if messages are flooding in, the detection callback almost certainly isn't rate-limited yet.

Recognition treated as identity-certain. Worth re-reading the section above on how the on-device recognition actually works before building anything that assumes it's as reliable as a phone's face unlock -- it isn't, and treating it that way leads to false confidence.

Board resets specifically when WiFi connects. Almost always a power issue, not a WiFi issue -- the radio's current draw spikes during connection, and a marginal supply that was fine at idle browns out at exactly that moment.

Enrolled faces "disappearing" after a reboot. Enrolled templates in the stock example live in RAM, not persistent storage, by default -- a power cycle clears them, and faces need re-enrolling unless the sketch has been modified to save templates to flash or SD.

Local Snapshot Logging to microSD as a Backup

Telegram alerts are useful in the moment, but they're not a record you'd want to rely on for reviewing what happened later -- messages scroll past, and there's no easy way to browse "everything that triggered an alert last week." Saving a snapshot to microSD alongside every alert gives a local, browsable log that doesn't depend on Telegram's history at all:

C++

This piggybacks on the same cooldown timer already in place, so it doesn't add alert spam -- it just captures a local copy of the exact frame that triggered each Telegram message, timestamped by filename order, sitting on the card for later review whenever it's convenient.

Folder of timestamped snapshot images saved to microSD, one per triggered alert

Detection-Only vs. PIR-Gated Detection

Running face detection continuously, frame after frame, keeps the ESP32-S busy around the clock even during long empty stretches -- fine for a mains-powered camera with nothing else competing for CPU time, less ideal if the board is also handling streaming to multiple viewers or running on a marginal power budget.

Continuous detection (this guide)PIR-gated detection
ResponsivenessImmediate -- always scanningSmall delay while PIR triggers and camera spins up
CPU / power loadHigher, constantLower, only active on motion
ComplexityLower -- no extra sensorSlightly higher -- needs PIR wiring and a trigger state machine
Best suited toMains-powered, always-streaming setupsBattery-assisted or CPU-constrained builds

For most home installs running off a wall adapter, continuous detection is simpler and the power difference doesn't matter enough to justify the added complexity. It becomes worth adding a PIR gate mainly when the same board is also under load from something else, or when a battery is in the power path even as a UPS-style backup rather than primary power.

Setting Up a Static IP

By default the ESP32-CAM requests an address from the router's DHCP pool, which can quietly change after a router reboot or firmware update -- inconvenient for a security camera you want to reliably reach at the same URL or bookmark. Assigning a static IP in the sketch avoids that:

C++

Place this before the existing WiFi.begin() call, adjusted to an address outside your router's DHCP range so it doesn't collide with another device later. Most routers also support a DHCP reservation tied to the board's MAC address as an alternative -- functionally equivalent, and worth using instead if the router's admin panel makes that easy, since it keeps the IP assignment out of the firmware entirely.

Testing the Full Alert Pipeline Before Relying On It

Before mounting the camera somewhere permanent and trusting it to actually catch something, it's worth deliberately walking through the whole chain end to end rather than assuming each piece works because it compiled:

Steps

  1. 1Confirm the live stream loads reliably from a phone on both WiFi and, separately, cellular data via port forwarding or a VPN if remote viewing is planned
  2. 2Trigger a face detection deliberately and confirm the Telegram message arrives within a few seconds
  3. 3Trigger a second detection immediately after and confirm the cooldown timer correctly suppresses a duplicate alert
  4. 4Wait out the cooldown window and trigger again, confirming the next legitimate alert does come through
  5. 5If snapshot logging is enabled, pull the microSD card and confirm the saved image actually matches the moment of the alert
  6. 6Power-cycle the board and confirm it reconnects to WiFi and resumes streaming without manual intervention

Frequently Asked Questions

Can I view the stream from outside my home network? Not without extra setup -- by default the camera is only reachable on the local network. Port forwarding, a VPN back into the home network, or a reverse-proxy service are the common options, each with its own security trade-offs worth researching before exposing a camera stream to the open internet.

Does the flash LED help with night detection? Marginally, and only at close range -- it's a single white LED meant for brief illumination, not sustained night vision. For genuine nighttime reliability, a separate IR illuminator paired with an IR-capable camera module works considerably better.

How many faces can be enrolled for recognition at once? The stock example supports a small number of enrolled templates -- typically under ten -- and performance and reliability both degrade as more are added, since each new frame gets compared against every enrolled template in sequence.

Will this work with more than one ESP32-CAM on the same network? Yes, each board runs independently with its own IP address and Telegram alert calls -- there's no built-in coordination between multiple units, so a multi-camera setup just means repeating this whole process once per board and giving each one a distinct alert message so you know which camera triggered.

Is the Telegram bot token safe to leave in the sketch? Treat it like a password -- anyone with that token can send messages through the bot and, depending on how it's configured, potentially read messages sent to it. Keeping the sketch out of a public repository, or moving the token to a separate config header excluded from version control, avoids an easy accidental leak.

Where This Fits

For a build that acts on detection rather than just alerting -- unlocking a door instead of sending a notification -- see the ESP32-CAM Face Detection Door Lock project, which shares most of the same detection groundwork covered here. For a deeper look at the detection model itself, including its accuracy limits and tuning tips, the ESP32-CAM Face Detection guide covers that in more depth than fits naturally in a single project writeup.

Q&A is currently disabled site-wide.

Related projects