Deploying a Raspberry Pi as surveillance camera requires moving past legacy tutorials that rely on deprecated software stacks. To build a reliable, modern system in 2026, you need a Raspberry Pi 5, the IMX708-based Camera Module 3, and a Python script utilizing the picamera2 library for hardware-accelerated encoding and frame-differencing motion detection.
This guide provides the exact hardware spec sheet, pin mappings, and a complete, compilable Python script that combines software-based pixel analysis with a hardware PIR sensor for dual-trigger recording. We will also cover the exact error strings you will encounter when the MIPI bus or libcamera stack misbehaves.
Project Overview & Difficulty Rating
Difficulty: Intermediate (Requires basic Linux CLI and Python familiarity).
Time to Build: 90 minutes.
Estimated Cost: $105 - $115 USD.
Hardware Spec Sheet & Pin Mapping
Do not use the original 5MP V1 camera or the legacy picamera Python library; both are obsolete and unsupported on the Pi 5's new camera architecture. The IMX708 sensor in the Module 3 supports Phase Detection Auto Focus (PDAF) and HDR, which are critical for surveillance lighting transitions.
| Component | Exact Model / Variant | Est. Price (2026) |
|---|---|---|
| Compute Board | Raspberry Pi 5 (4GB RAM) | $60.00 |
| Camera Module | Raspberry Pi Camera Module 3 (Standard or Wide) | $30.00 |
| Power Supply | Official 27W USB-C PD Power Supply (5V/5A) | $12.00 |
| Storage | 64GB A2 V30 MicroSD (SanDisk Extreme) | $10.00 |
| Motion Sensor | HC-SR501 PIR Motion Sensor | $3.00 |
GPIO Pin Mapping
The camera connects via the MIPI CSI-2 ribbon cable. The PIR sensor requires 5V power to operate its onboard voltage regulator correctly; do not power it from the 3.3V pin, or the detection range will drop to under 1 meter.
| Component | Pi Physical Pin | Pi BCM GPIO | Wire Color |
|---|---|---|---|
| PIR VCC | Pin 2 | 5V Power | Red |
| PIR GND | Pin 6 | Ground | Black |
| PIR OUT | Pin 11 | GPIO 17 | Yellow |
| Camera MIPI | CSI Port | N/A | Flat Ribbon |
Step-by-Step Assembly & Software Setup
- Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Bookworm (64-bit) to your A2 MicroSD card. Enable SSH and configure your WiFi in the advanced settings.
- Seat the MIPI Cable: De-energize the Pi before connecting the camera. Lift the plastic retaining collar on the CSI port. Insert the ribbon cable with the blue tape facing the Ethernet/USB ports (away from the power button). Push the collar down to lock.
- Wire the PIR: Connect the HC-SR501 VCC to Pin 2, GND to Pin 6, and OUT to Pin 11. Adjust the orange potentiometers on the PIR: turn the delay pot fully counter-clockwise (approx 0.3s) and the sensitivity pot to the middle.
- Install Dependencies: SSH into your Pi and run the following commands to install the modern camera stack and Python GPIO libraries:
sudo apt update sudo apt install -y python3-picamera2 python3-libcamera python3-rpi.gpio python3-numpy
Python Motion Detection Code
This script uses a dual-trigger approach. It monitors the hardware PIR pin for heat-based motion while simultaneously performing software-based frame differencing on the low-resolution video stream. If either trigger fires, it records a 10-second H.264 clip using the Pi's hardware encoder.
import time
import numpy as np
import os
from picamera2 import Picamera2
import RPi.GPIO as GPIO
# --- PIN & CONFIG DEFINITIONS ---
PIR_PIN = 17 # BCM GPIO 17 (Physical Pin 11)
SAVE_DIR = '/home/pi/surveillance_clips'
MOTION_THRESHOLD = 14.0 # Pixel luminance delta threshold
CLIP_DURATION = 10 # Recording length in seconds
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)
os.makedirs(SAVE_DIR, exist_ok=True)
def get_grayscale_frame(picam2):
# Capture low-res RGB frame and convert to grayscale via numpy averaging
lores_data = picam2.capture_array('lores')
return np.mean(lores_data, axis=2)
try:
picam2 = Picamera2()
# Configure main stream for hardware H.264 encoding, lores for fast numpy math
config = picam2.create_video_configuration(
main={'size': (1920, 1080), 'format': 'YUV420'},
lores={'size': (320, 240), 'format': 'RGB888'}
)
picam2.configure(config)
picam2.start()
time.sleep(2) # Allow camera sensor to adjust exposure
prev_frame = get_grayscale_frame(picam2)
print('Surveillance active. Waiting for motion...')
while True:
pir_state = GPIO.input(PIR_PIN)
curr_frame = get_grayscale_frame(picam2)
# Calculate mean absolute difference in luminance
diff = np.mean(np.abs(curr_frame.astype(int) - prev_frame.astype(int)))
software_motion = diff > MOTION_THRESHOLD
if software_motion or pir_state:
timestamp = time.strftime('%Y%m%d-%H%M%S')
filepath = os.path.join(SAVE_DIR, f'motion_{timestamp}.mp4')
print(f'Motion detected! (Diff: {diff:.2f} | PIR: {pir_state}) Recording...')
# Blocking record using hardware encoder
picam2.start_and_record_video(filepath, duration=CLIP_DURATION)
# Update prev_frame after recording to avoid immediate re-trigger on camera settling
curr_frame = get_grayscale_frame(picam2)
prev_frame = curr_frame
time.sleep(0.2)
except RuntimeError as e:
print(f'Camera Hardware Error: {e}')
print('Check MIPI ribbon cable seating and ensure libcamera is installed.')
except KeyboardInterrupt:
print('\nSurveillance stopped by user.')
finally:
if 'picam2' in locals() and picam2.started:
picam2.stop()
GPIO.cleanup()
Debugging: First Three Things to Check & Common Errors
When your script crashes on startup, do not rewrite the code. 95% of camera failures on the Pi 5 are physical or OS-level conflicts. The first three things to check when it fails:
- MIPI Cable Orientation: The blue tab on the ribbon cable must face the outer edge of the board. If inserted backwards, the I2C data lines for the sensor cross with the power lines, and the camera will not enumerate.
- Conflicting Processes: Run
sudo pkill rpicam-vidandsudo pkill libcamera. If a background service or a previous crashed script holds the device node, your Python script will fail to acquire the lock. - Power Supply Sufficiency: The Pi 5 and Camera Module 3 can spike to 12W during H.264 encoding. If you are using a generic 5V/3A phone charger, the Pi will throttle the USB/CSI bus, causing silent frame drops.
Exact Error Strings & Ranked Causes
RuntimeError: Failed to acquire camera: Device or resource busyRanked Causes:
1. Another process (like
rpicam-vid or a running instance of this script in another terminal) holds /dev/video0.2. The MIPI cable is not fully seated, causing the I2C handshake to timeout and leave the driver in a locked state. Reboot required.
ModuleNotFoundError: No module named 'picamera2'Ranked Causes:
1. You ran
pip install picamera. The legacy picamera library does not work on Pi OS Bookworm. You must use sudo apt install python3-picamera2.2. You are running the script in a virtual environment that lacks access to system-wide
libcamera bindings. Run the script with the system Python or symlink the libcamera site-packages.
ValueError: Requested stream lores is not availableRanked Causes:
1. Your
create_video_configuration dictionary has a typo in the stream name (it must be exactly 'lores', 'main', or 'raw').2. The requested resolution for
lores exceeds the sensor's native binning capabilities. Stick to 320x240 or 640x480 for the low-res stream.
Extending and Simplifying the Build
How to extend for remote viewing (RTSP):
The script above saves locally. To stream live to VLC or BlueIris, bypass Python's recording loop and pipe the camera directly to FFmpeg. Run this in your terminal to broadcast an RTSP stream on port 8554:
rpicam-vid -t 0 --inline -o - | ffmpeg -i - -c:v copy -f rtsp rtsp://0.0.0.0:8554/surveillance
How to simplify the build:
If you want to eliminate the HC-SR501 PIR sensor and its jumper wires, delete the RPi.GPIO imports and the pir_state logic from the Python script. Rely entirely on the numpy frame-differencing. The trade-off is that software motion detection will trigger on sudden lighting changes (like a cloud passing over the sun or a car headlight sweeping across the room), whereas the PIR sensor only triggers on infrared heat signatures.
FAQ: Raspberry Pi Surveillance Camera Questions
Can I use a Raspberry Pi as surveillance camera without internet?
Yes. The Pi operates perfectly on an isolated Local Area Network (LAN). You can map the /home/pi/surveillance_clips directory as an SMB/CIFS share to a local NAS or Windows PC. Without internet, you lose remote push notifications and cloud backup, but the local motion detection, recording, and RTSP streaming will function indefinitely as long as the router and Pi remain powered.
How long will a Raspberry Pi surveillance camera last on battery?
The Raspberry Pi 5 draws approximately 3.5W at idle and spikes to 10W-12W during active H.264 video encoding. If you power it via the USB-C port using a standard 20,000mAh (100Wh) 5V power bank, expect roughly 8 to 10 hours of continuous operation. For longer deployments, you must integrate a 12V LiFePO4 battery with a buck converter stepping down to 5V/5A, or use a solar charge controller setup.
Is a Raspberry Pi camera better than an off-the-shelf IP camera?
It depends on your definition of 'better.' A commercial IP camera (like a Reolink or Hikvision) wins on reliability, weatherproofing, and night vision (IR cut filters). However, a Raspberry Pi as surveillance camera wins on edge-compute flexibility. With a Pi, you can run local AI object detection (via YOLO or TensorFlow Lite) to differentiate between a stray cat and a human, trigger custom GPIO relays to turn on floodlights, or push MQTT alerts to your Home Assistant server without paying a monthly cloud subscription.






