Raspberry Pi Camera Project

How to Build a Residential Intrusion Detection System #raspiSeries — Episode 2

J3
Jungletronics
12 min readAug 24, 2024

--

Discover how to create a robust 
residential intrusion detection system
using a Raspberry Pi camera.

This guide will walk you through
setting up the hardware,
configuring the software,
and leveraging intelligent algorithms
to detect unauthorized access effectively.

By the end, you'll have a fully functional
system that enhances home security using affordable,
readily available technology.

Main Features

**Motion Detection:**
Utilizes PIR sensors to detect movements at critical entry points.
Captures images upon detecting motion and records the events.

**Web Interface:**
Provides a user-friendly interface to view captured images and manage the system.
Features buttons to check the most recent photos and clear the camera directory.

**Security:**
Implements user authentication and session management for secure access.
Utilizes secret keys for enhanced web application security.
Set up the PIR sensor on GPIO4, the LED on GPIO17, and connect your camera to the CSI camera slot. Visit my Prezi about Raspberry PI.

1 Here is the first Code:

# cam6.py

import RPi.GPIO as GPIO
import time

PIR_PIN = 4

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)

MOV_DETECT_THREASHOLD = 3.0
last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THREASHOLD:
print("Take Photo and Send it by Email")
last_pir_state = pir_state
except KeyboardInterrupt:
GPIO.cleanup()

This Python script sets up a PIR motion sensor on GPIO pin 4 of a Raspberry Pi to detect motion. When motion is detected and sustained for more than 3 seconds (MOV_DETECT_THRESHOLD), it prints Take Photo and Send it by Email. The script continuously monitors the PIR sensor state, tracking changes to determine when the threshold is exceeded, and gracefully exits by cleaning up the GPIO settings when interrupted.

One problem: It takes too many photos. How to fix it?

2 To prevent multiple photos:

To prevent the script from taking too many photos, you need to add a delay between taking photos after motion is detected. Currently, the script only checks if motion is detected for a sustained period but does not implement a cool-down period to prevent multiple photos from being taken in rapid succession.

Here’s how you can modify the script to include these changes:

# cam7.py

import RPi.GPIO as GPIO
import time

PIR_PIN = 4

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)

MOV_DETECT_THRESHOLD = 3.0 # Time threshold for sustained motion
MIN_DURATION_BETWEEN_PHOTOS = 5.0 # Minimum time between two photos (in seconds)

last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()
last_time_photo_taken = 0 # Initialize last photo time to 0

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)

# Detecting the start of motion
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()

# Sustained motion detection
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THRESHOLD:
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
print("Take Photo and Send it by Email")
last_time_photo_taken = time.time() # Update the last photo taken time

last_pir_state = pir_state

except KeyboardInterrupt:
GPIO.cleanup()

Here’s how you can fix this issue:

  1. Introduce a Minimum Time Interval Between Photos: Add a variable to keep track of the time when the last photo was taken.
  2. Check the Time Since the Last Photo: Before taking a new photo, check if the required interval has passed since the last photo.
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
# now take a photo

Here is the result:

Now, we will only take a photo if the time between photos is longer than 5 seconds. This is much better!

3 Set a visual indicator by adding an LED to signal when movement is detected:

# cam8.py

import RPi.GPIO as GPIO
import time

PIR_PIN = 4
LED_PIN = 17

# Setup GPIOs
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)
GPIO. setup(LED_PIN, GPIO.OUT)
GPIO.output(LED_PIN, GPIO.LOW)
print("GPIOs setup ok.")

MOV_DETECT_THRESHOLD = 3.0 # Time threshold for sustained motion
MIN_DURATION_BETWEEN_PHOTOS = 5.0 # Minimum time between two photos (in seconds)

last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()
last_time_photo_taken = 0 # Initialize last photo time to 0

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)
# Activate LED when movement is detected.
if pir_state == GPIO.HIGH:
GPIO.output(LED_PIN, GPIO.HIGH)
else:
GPIO.output(LED_PIN, GPIO.LOW)

# Detecting the start of motion
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()

# Sustained motion detection
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THRESHOLD:
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
print("Take Photo and Send it by Email")
last_time_photo_taken = time.time() # Update the last photo taken time

last_pir_state = pir_state

except KeyboardInterrupt:
GPIO.cleanup()

Modified Parts Explanation:

  1. LED Setup:
  • An LED is added as a visual indicator for motion detection. The LED is connected to GPIO 17 (LED_PIN), and the GPIO pin is set up as an output (GPIO.setup(LED_PIN, GPIO.OUT)). Initially, the LED is turned off (GPIO.output(LED_PIN, GPIO.LOW)).
  1. LED Activation:
  • Inside the while loop, the LED is turned on (GPIO.output(LED_PIN, GPIO.HIGH)) whenever motion is detected (pir_state == GPIO.HIGH). If no motion is detected, the LED is turned off (GPIO.output(LED_PIN, GPIO.LOW)).
Now, the LED lights up for 5 seconds with each detected movement, providing a visual indication that the system is functioning correctly.
Here it is, working smoothly on my workbench.

4 Configuring the Camera:

# cam9.py

import RPi.GPIO as GPIO
import time
from picamera2 import Picamera2

PIR_PIN = 4
LED_PIN = 17


# Setup camera
camera = Picamera2()
camera.resolution = (720, 480)
camera.rotation = 180
print("Waiting 2 seconds to init the camera...")
time.sleep(2)
print("Camera setup ok.")

# Setup GPIOs
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)
GPIO. setup(LED_PIN, GPIO.OUT)
GPIO.output(LED_PIN, GPIO.LOW)
print("GPIOs setup ok.")

MOV_DETECT_THRESHOLD = 3.0 # Time threshold for sustained motion
MIN_DURATION_BETWEEN_PHOTOS = 5.0 # Minimum time between two photos (in seconds)

last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()
last_time_photo_taken = 0 # Initialize last photo time to 0

print("Everything has been setup.")

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)
# Activate LED when movement is detected.
if pir_state == GPIO.HIGH:
GPIO.output(LED_PIN, GPIO.HIGH)
else:
GPIO.output(LED_PIN, GPIO.LOW)

# Detecting the start of motion
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()

# Sustained motion detection
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THRESHOLD:
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
print("Take Photo and Send it by Email")
last_time_photo_taken = time.time() # Update the last photo taken time

last_pir_state = pir_state

except KeyboardInterrupt:
GPIO.cleanup()

Modified Parts Explanation:

The added code introduces camera functionality using the Picamera2 library to capture photos when motion is detected:

  1. Camera Setup:
  • camera = Picamera2() initializes the Picamera2 object to interact with the camera hardware.
  • camera.resolution = (720, 480) sets the camera resolution to 720x480 pixels.
  • camera.rotation = 180 rotates the camera image by 180 degrees to match the desired orientation.
  • The script pauses for 2 seconds with time.sleep(2) to allow the camera to initialize properly.

2 . Taking Photos on Motion Detection:

  • The camera setup is now ready to take pictures when motion is detected by the PIR sensor, as managed in the motion detection loop. However, note that the actual code for capturing and sending photos is not provided in this snippet, but the camera initialization steps are necessary for its integration.

This additional setup ensures that the camera is correctly initialized and ready for use when motion is detected.

5 Captured the actual photo:

# cam10.py

import RPi.GPIO as GPIO
import time
from picamera2 import Picamera2
from libcamera import Transform
import os

PIR_PIN = 4
LED_PIN = 17

def take_photo(picam2):
# Ensure the directory exists
if not os.path.exists("/home/pi/Camera"):
os.makedirs("/home/pi/Camera")

file_name = "/home/pi/Camera/img_" + str(time.time()) + ".jpg"
picam2.capture_file(file_name)
print(f"Photo saved: {file_name}")
return file_name

# Setup camera
picam2 = Picamera2()
picam2.configure(picam2.create_still_configuration(transform=Transform(rotation=180)))
picam2.start() # Start the camera

# Pause for 2 seconds to allow the camera to stabilize
time.sleep(2)
print("Camera setup ok.")

# Setup GPIOs
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.output(LED_PIN, GPIO.LOW)
print("GPIOs setup ok.")

MOV_DETECT_THRESHOLD = 3.0 # Time threshold for sustained motion
MIN_DURATION_BETWEEN_PHOTOS = 60.0 # Minimum time between two photos (in seconds)

last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()
last_time_photo_taken = 0 # Initialize last photo time to 0

print("Everything has been set up.")

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)

# Activate LED when movement is detected.
GPIO.output(LED_PIN, GPIO.HIGH if pir_state == GPIO.HIGH else GPIO.LOW)

# Detecting the start of motion
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()

# Sustained motion detection
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THRESHOLD:
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
print("Take Photo and Send it by Email")
take_photo(picam2)
last_time_photo_taken = time.time() # Update the last photo taken time

last_pir_state = pir_state

except KeyboardInterrupt:
GPIO.cleanup()
picam2.stop()

Explanation of the Added Parts:

1. Directory Check and Creation in take_photo(picam2) Function:

if not os.path.exists("/home/pi/Camera"):
os.makedirs("/home/pi/Camera")
  • This code ensures that the directory /home/pi/Camera exists before saving a photo. If the directory does not exist, it is created using os.makedirs(). This prevents errors when the script attempts to save a photo to a non-existent directory.

2. Camera Configuration with Transform:

picam2.configure(picam2.create_still_configuration(transform=Transform(rotation=180)))
picam2.start() # Start the camera
  • The camera is configured with a specific setting that includes a rotation transformation (rotation=180). This rotates the captured images by 180 degrees, which may be necessary if the camera is mounted upside down or in a specific orientation.

3. Starting the Camera:

picam2.start()  # Start the camera
  • After configuring the camera, the script starts it with picam2.start(), making it ready to capture photos.

4. Added Cleanup for Camera:

picam2.stop()
  • When the script is interrupted (e.g., by pressing Ctrl+C), this line stops the camera properly, ensuring that all resources are released and the camera is safely shut down.
The variable MIN_DURATION_BETWEEN_PHOTOS, which specifies the minimum time interval between taking two photos (in seconds), is set to 60.

6 Setting timestamp:

# cam11.py

import RPi.GPIO as GPIO
import time, cv2
from picamera2 import Picamera2, MappedArray
from libcamera import Transform
import os

PIR_PIN = 4
LED_PIN = 17
resolution = (800, 600)

def apply_text(request):
# Text options
colour = (255, 255, 255)
origin = (0, 60)
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 1
thickness = 1
# text = "17082024 09:07"
# Get the current time in the format "DDMMYYYY HH:MM"
text = time.strftime("%d%m%Y %H:%M")
# Calculate the text size
text_size, _ = cv2.getTextSize(text, font, scale, thickness)

# Calculate the bottom-right origin
x = resolution[0] - text_size[0] - 10 # 10 pixels padding from the right
y = resolution[1] - 10 # 10 pixels padding from the bottom

origin = (x, y)
with MappedArray(request, "main") as m:
cv2.putText(m.array, text, origin, font, scale, colour, thickness)

def take_photo(picam2):
# Ensure the directory exists
if not os.path.exists("/home/pi/Camera"):
os.makedirs("/home/pi/Camera")

file_name = "/home/pi/Camera/img_" + str(time.time()) + ".jpg"
# picam2.capture_file(file_name)
picam2.switch_mode_and_capture_file(capture_config, file_name)
print(f"Photo saved: {file_name}")
return file_name


# Setup camera
picam2 = Picamera2()
# picam2.configure(picam2.create_still_configuration(transform=Transform(rotation=180)))
# Create two separate configs - one for preview and one for capture.
# Make sure the preview is the same resolution as the capture, to make
# sure the overlay stays the same size
capture_config = picam2.create_still_configuration({"size": resolution}, transform=Transform(hflip=True, vflip=True))
preview_config = picam2.create_preview_configuration({"size": resolution}, transform=Transform(hflip=True, vflip=True))

# Set the current config as the preview config
picam2.configure(preview_config)

# Add the timestamp
picam2.pre_callback = apply_text
# Start the camera
picam2.start(show_preview=True)
picam2.start() # Start the camera

# Pause for 2 seconds to allow the camera to stabilize
time.sleep(2)
print("Camera setup ok.")

# Setup GPIOs
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.output(LED_PIN, GPIO.LOW)
print("GPIOs setup ok.")

MOV_DETECT_THRESHOLD = 3.0 # Time threshold for sustained motion
MIN_DURATION_BETWEEN_PHOTOS = 60.0 # Minimum time between two photos (in seconds)

last_pir_state = GPIO.input(PIR_PIN)
movement_timer = time.time()
last_time_photo_taken = 0 # Initialize last photo time to 0

print("Everything has been set up.")

try:
while True:
time.sleep(0.01)
pir_state = GPIO.input(PIR_PIN)

# Activate LED when movement is detected.
GPIO.output(LED_PIN, GPIO.HIGH if pir_state == GPIO.HIGH else GPIO.LOW)

# Detecting the start of motion
if last_pir_state == GPIO.LOW and pir_state == GPIO.HIGH:
movement_timer = time.time()

# Sustained motion detection
if last_pir_state == GPIO.HIGH and pir_state == GPIO.HIGH:
if time.time() - movement_timer > MOV_DETECT_THRESHOLD:
# Check if enough time has passed since the last photo
if time.time() - last_time_photo_taken > MIN_DURATION_BETWEEN_PHOTOS:
print("Take Photo and Send it by Email")
take_photo(picam2)
last_time_photo_taken = time.time() # Update the last photo taken time

last_pir_state = pir_state

except KeyboardInterrupt:
GPIO.cleanup()
picam2.stop()

Explanation of the Modified Parts:

1. Text Overlay on Photos:

  • The apply_text function has been added to overlay the current date and time on each photo. It uses the OpenCV library (cv2) to draw text on the image.

2. Key Points in apply_text Function:

  • Text Formatting: The color, position, font, scale, and thickness of the text are defined.
  • Dynamic Timestamp: The timestamp format is set to “DDMMYYYY HH”, and the text size is calculated to position it correctly at the bottom right of the image.
  • Text Overlay: The text is overlaid onto the image buffer using cv2.putText.

3. Camera Configuration Changes:

  • Multiple Configurations: Two configurations are created for the camera:
  • capture_config: Used when taking a still photo with horizontal and vertical flips.
  • preview_config: Used for the live preview, ensuring the resolution matches the capture configuration to maintain consistent overlay size.
  • Camera Initialization:
  • The camera is initialized with the preview_config, and the timestamp overlay is added using picam2.pre_callback = apply_text.
  • The camera preview is started with show_preview=True.

3. Photo Capture with Text Overlay:

  • The take_photo function has been updated to use switch_mode_and_capture_file instead of a simple capture call. This ensures that the configured settings (including the text overlay) are applied when the photo is taken.

4. Camera Setup Enhancements:

  • Camera is stabilized for 2 seconds with time.sleep(2) to ensure a smooth startup.
  • Camera transformation has been adjusted with Transform(hflip=True, vflip=True) to correctly orient the image.

These modifications allow the program to display a live preview with text overlay and save photos with the current timestamp whenever movement is detected.

Now, the photo taken by the Raspberry Pi includes a timestamp, which is a valuable addition to the security system as it records the exact time the photo was captured. Later, we will send this photo via email.

That’s a wrap for now!

In the next installment, we’ll implement logging facilities and Web Interface.

See you soon!

👌 Review all additions.

👉GitHub Repo Episode 2

Note:

In Raspberry Pi programming, the GPIO.setmode(GPIO.BCM) function is used to specify how you want to refer to the General Purpose Input/Output (GPIO) pins on the Raspberry Pi.

The Raspberry Pi has two main numbering systems for its GPIO pins:

  1. BCM (Broadcom SOC Channel) Mode: This refers to the GPIO numbers based on the Broadcom system-on-a-chip (SOC) used in the Raspberry Pi. Each GPIO pin on the Raspberry Pi’s header corresponds to a Broadcom SOC channel, which is identified by a unique number. For example, GPIO pin 2 on the Broadcom SOC is known as GPIO2 in BCM mode.
  2. BOARD Mode: This refers to the physical pin numbers on the Raspberry Pi’s GPIO header. For example, pin 1 is the first pin on the header, pin 2 is the second pin, and so on.

Why Use BCM Mode?

  • Consistency Across Models: Using BCM mode ensures that your code refers to the same GPIO pins regardless of the Raspberry Pi model. Different models have different pin layouts, but the BCM numbering remains consistent for the corresponding SOC channels.
  • Standard Practice: Many tutorials and examples use BCM mode, making it easier to follow and use existing resources.
[Ubuntu Terminal - Host]

sudo scp pi@192.168.0.114:/home/pi/Desktop/*.py /home/j3/Documents/raspi_projects/Episode_2

[Change the Owner for your User Name]

sudo chown -R j3:j3 /home/j3/Documents/raspi_projects/Episode_2

Credits & References

Raspberry Pi OS by raspberrypi.com

Related Posts

Raspberry PI

0#Episode — #raspiSeries — Raspberry Pi Intro — Quick And Easy Way To Install Raspberry Pi OS

1#Episode — #raspiSeries — Raspberry Pi Camera Module — How To Connect the Rpicam, Take Pictures, Record Video, and Apply image effects

2#Episode — #raspiSeries — Raspberry Pi Camera Project — How to Build a Residential Intrusion Detection System (this one)

3#Episode — #raspiSeries — Raspberry Pi Camera Project — How to Build a Residential Intrusion Detection System

OpenCV

00 Episode#Hi Python Computer Vision — PIL! — An Intro To Python Imaging Library #PyVisionSeries

01 Episode# Jupyter-lab — Python — OpenCV — Image Processing Exercises #PyVisionSeries

02 Episode# OpenCV — Image Basics — Create Image From Scratch #PyVisionSeries

03 Episode# OpenCV — Morphological Operations — How To Erode, Dilate, Edge Detect w/ Gradient #PyVisionSeries

04 Episode# OpenCV — Histogram Equalization — HOW TO Equalize Histograms Of Images — #PyVisionSeries

05 Episode# OpenCV — OpenCV — Resize an Image — How To Resize Without Distortion

07 Episode# YOLO — Object Detection — The state of the art in object detection Framework!

08 Episode# OpenCV — HaashCascate — Object Detection — Viola–Jones object detection framework — #PyVisionSeries

--

--

Jungletronics
Jungletronics

Published in Jungletronics

Explore our insights on Django, Python, Rails, Ruby, and more. We share code, hacks, and academic notes for developers and tech enthusiasts. Happy reading!

J3
J3

Written by J3

😎 Gilberto Oliveira Jr | 🖥️ Computer Engineer | 🐍 Python | 🧩 C | 💎 Rails | 🤖 AI & IoT | ✍️