</>CoderDIY
← Back to list
Article

Build an AI Security Camera with Raspberry Pi

Author: CoderDIY··Đọc bằng tiếng Việt
🕵️

A security camera driven by a plain PIR motion sensor has a classic problem: constant false alarms from wind shaking a branch, moving sunlight, or the neighbor’s cat wandering by. By adding a lightweight object-detection model that runs directly on the Raspberry Pi, the camera can tell “motion happened” apart from “a person is actually there” — only sending an alert when it genuinely matters, with no need to ship footage to the cloud for processing.

How it works

  1. The camera continuously captures frames; a simple motion filter (comparing consecutive frames) runs first to save resources — only when a meaningful change is detected does the system move on to object detection.
  2. Frames with motion are passed through a pre-trained MobileNet SSD (Single Shot Detector) model, optimized to run fast enough on the Raspberry Pi’s CPU without a dedicated GPU.
  3. The model returns a list of detected objects with labels (person, car, dog…) and confidence scores; the system only treats it as a “real alert” when the person label appears above a confidence threshold.
  4. When a person is detected, the system saves a photo/short clip with a timestamp and sends an alert over Telegram with the captured image, so the homeowner can immediately see who’s at the door without opening a separate app.
<rect x="10" y="20" width="140" height="60" rx="10" class="box" />
<text x="80" y="45" text-anchor="middle" class="label">Read frames</text>
<text x="80" y="62" text-anchor="middle" class="label">continuously</text>

<rect x="170" y="20" width="140" height="60" rx="10" class="box" />
<text x="240" y="45" text-anchor="middle" class="label">Motion filter</text>
<text x="240" y="62" text-anchor="middle" class="label">(frame diff)</text>

<rect x="330" y="20" width="140" height="60" rx="10" class="box" />
<text x="400" y="45" text-anchor="middle" class="label">MobileNet SSD</text>
<text x="400" y="62" text-anchor="middle" class="label">detect person</text>

<rect x="490" y="20" width="140" height="60" rx="10" class="box" />
<text x="560" y="45" text-anchor="middle" class="label">Save + alert</text>
<text x="560" y="62" text-anchor="middle" class="label">(Telegram)</text>

<line x1="150" y1="50" x2="170" y2="50" class="arrow" />
<line x1="310" y1="50" x2="330" y2="50" class="arrow" />
<line x1="470" y1="50" x2="490" y2="50" class="arrow" />

<path d="M 560 80 L 560 190 L 80 190 L 80 80" class="arrow" fill="none" />
<text x="320" y="210" text-anchor="middle" class="sublabel">No motion → skip the AI step entirely, saving CPU</text>
Filtering motion first saves resources, so the heavier AI model only runs when it's actually needed.

Parts list

  • A Raspberry Pi 4 (4GB or more, for smoother object-detection performance)
  • A Raspberry Pi Camera Module or a USB webcam
  • A microSD card with enough capacity to store alert clips
  • A pre-trained MobileNet SSD model (a publicly available .caffemodel/.pb file, run through OpenCV’s DNN module)
  • (Optional) A weatherproof enclosure for outdoor mounting, and an infrared light if night monitoring is needed
<rect x="30" y="70" width="170" height="90" rx="10" class="box" />
<text x="115" y="120" text-anchor="middle" class="label">Camera Module</text>

<rect x="240" y="70" width="170" height="90" rx="10" class="box" />
<text x="325" y="120" text-anchor="middle" class="label">Raspberry Pi 4</text>

<rect x="450" y="70" width="170" height="90" rx="10" class="box" />
<text x="535" y="105" text-anchor="middle" class="label">Telegram Bot</text>
<text x="535" y="123" text-anchor="middle" class="label">(homeowner's phone)</text>

<line x1="200" y1="115" x2="240" y2="115" class="wire" />
<text x="220" y="105" text-anchor="middle" class="sublabel">CSI/USB</text>

<line x1="410" y1="115" x2="450" y2="115" class="wire" />
<text x="430" y="105" text-anchor="middle" class="sublabel">WiFi</text>
The camera connects directly to the Raspberry Pi for local processing, only sending data out over Telegram when an actual person is detected.

Sample code

import cv2
import numpy as np
import time
import requests

PROTOTXT = "MobileNetSSD_deploy.prototxt"
MODEL = "MobileNetSSD_deploy.caffemodel"
CONFIDENCE_THRESHOLD = 0.5
BOT_TOKEN = "TELEGRAM_BOT_TOKEN"
CHAT_ID = "TELEGRAM_CHAT_ID"

CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle",
           "bus", "car", "cat", "chair", "cow", "diningtable", "dog",
           "horse", "motorbike", "person", "pottedplant", "sheep", "sofa",
           "train", "tvmonitor"]

net = cv2.dnn.readNetFromCaffe(PROTOTXT, MODEL)
cap = cv2.VideoCapture(0)
prev_gray = None
last_alert = 0
ALERT_COOLDOWN = 60  # seconds, avoids spamming alerts while someone lingers at the door

def send_alert(frame_path):
    with open(frame_path, "rb") as photo:
        requests.post(
            f"https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto",
            data={"chat_id": CHAT_ID, "caption": "Person detected at the door!"},
            files={"photo": photo},
        )

def has_motion(frame):
    global prev_gray
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (21, 21), 0)
    if prev_gray is None:
        prev_gray = gray
        return False
    diff = cv2.absdiff(prev_gray, gray)
    prev_gray = gray
    return np.mean(diff) > 5  # simple threshold, tune for your actual environment

def detect_person(frame):
    h, w = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 0.007843, (300, 300), 127.5)
    net.setInput(blob)
    detections = net.forward()

    for i in range(detections.shape[2]):
        confidence = detections[0, 0, i, 2]
        if confidence < CONFIDENCE_THRESHOLD:
            continue
        class_id = int(detections[0, 0, i, 1])
        if CLASSES[class_id] == "person":
            return True
    return False

while True:
    ret, frame = cap.read()
    if not ret:
        continue

    if has_motion(frame) and detect_person(frame):
        now = time.time()
        if now - last_alert > ALERT_COOLDOWN:
            frame_path = "alert_frame.jpg"
            cv2.imwrite(frame_path, frame)
            send_alert(frame_path)
            last_alert = now
            print("Alert sent!")

    time.sleep(0.5)

Common pitfalls

  • Running the AI model on every single frame overloads the Raspberry Pi and causes it to overheat — always filter for motion first with a simple frame comparison, only running the AI model when something actually changed.
  • No anti-spam mechanism (ALERT_COOLDOWN) floods the phone with dozens of notifications while someone lingers at the door — always enforce a minimum gap between alerts.
  • A confidence threshold (CONFIDENCE_THRESHOLD) set too low causes false alarms on shadows or objects that vaguely resemble a person — start at 0.5 and tune it for your specific installation environment.
  • Poor nighttime lighting makes detection noticeably less accurate than during the day — consider adding an infrared light or a camera with night-vision mode if 24/7 monitoring matters.

Extensions

Add face recognition to distinguish household members from strangers (only alerting on strangers), store full clips on a NAS or private cloud storage instead of just still images, or pair it with the ESP32-CAM smart doorbell project for both monitoring and two-way audio when a visitor arrives.

Tags:#raspberry-pi#opencv#security

Comments