How To Build Motion Detection Surveillance System with Python

Introduction

In this tutorial, we’ll walk through the process of creating a real-time surveillance system using Python. The system captures video from a webcam, detects motion, and plays an alert sound when motion is detected. It also saves snapshots of the motion events and logs them with timestamps. We’ll use libraries such as OpenCV for video capture and motion detection, Tkinter for the GUI, and Pygame for playing alert sounds.

Prerequisites

Before we begin, ensure you have the following prerequisites installed on your system:

  • Python 3.x
  • pip (Python package installer)

Installation

To get started, you’ll need to install several Python libraries. Open your terminal or command prompt and run the following commands:

pip install opencv-python
pip install Pillow
pip install pygame

Project Structure

Create a new directory for your project and organize it as follows:

surveillance_app/
│
├── surveillance_app.py   # Main application code
├── alert_sound.mp3       # Alert sound file (replace with your own sound file)
└── snap/                 # Directory to save snapshots

Ensure you have an alert_sound.mp3 file in the project directory and create a snap directory to store the snapshots.

Code

Here’s the complete code for the surveillance system. Save this code in a file named surveillance_app.py:

import tkinter as tk
from tkinter import ttk
import cv2
import PIL.Image, PIL.ImageTk
import pygame
import os
import datetime

class SurveillanceApp:
    def __init__(self, window, window_title):
        self.window = window
        self.window.title(window_title)
        self.window.geometry("800x800")
        self.window.configure(bg='#2e2e2e')

        # OpenCV setup for capturing video
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            raise RuntimeError("Failed to open video capture.")

        # Set capture resolution
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

        # Create a canvas that can fit the video source size
        self.canvas = tk.Canvas(window, width=self.cap.get(cv2.CAP_PROP_FRAME_WIDTH), height=self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT), bg='#000000', highlightthickness=0)
        self.canvas.pack(padx=20, pady=20)

        # Initialize pygame mixer for sound
        pygame.mixer.init()
        self.alert_sound = pygame.mixer.Sound('alert_sound.mp3')  # Replace with your sound file

        self.delay = 10  # Refresh delay in milliseconds for updating video frames
        self.playing_alert = False  # Flag to track if alert sound is currently playing
        self.snapshot_counter = 0  # Counter to keep track of snapshots taken
        self.log_file = 'detection_log.txt'  # Log file to store detection details

        self.previous_frame = None  # Variable to store the previous frame for motion detection
        self.motion_threshold = 25  # Initial motion detection sensitivity
        self.monitoring = False  # Flag to control motion detection

        # Status bar
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(window, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, background='#3c3c3c', foreground='white', font=("Helvetica", 12))
        self.status_bar.pack(fill=tk.X, side=tk.BOTTOM, ipady=5)

        # Settings frame
        self.settings_frame = tk.Frame(window, bg='#2e2e2e')
        self.settings_frame.pack(fill=tk.X, pady=5)

        tk.Label(self.settings_frame, text="Motion Sensitivity:", bg='#2e2e2e', fg='white', font=("Helvetica", 12)).pack(side=tk.LEFT, padx=5)
        self.sensitivity_slider = tk.Scale(self.settings_frame, from_=0, to=100, orient=tk.HORIZONTAL, command=self.update_sensitivity, bg='#2e2e2e', fg='white', troughcolor='#3c3c3c', highlightthickness=0, font=("Helvetica", 12))
        self.sensitivity_slider.set(self.motion_threshold)
        self.sensitivity_slider.pack(side=tk.LEFT, padx=5)

        # Control frame
        self.control_frame = tk.Frame(window, bg='#2e2e2e')
        self.control_frame.pack(fill=tk.X, pady=10)

        # Custom button style
        self.style = ttk.Style()
        self.style.configure("TButton", font=("Helvetica", 12), padding=10, background="#FF5733", foreground="white", borderwidth=0)
        self.style.map("TButton", background=[("active", "#DAF7A6")])

        # Start and Stop buttons
        self.start_button = ttk.Button(self.control_frame, text="Start", command=self.start_monitoring, style="TButton")
        self.start_button.pack(side=tk.LEFT, padx=10)
        self.stop_button = ttk.Button(self.control_frame, text="Stop", command=self.stop_monitoring, style="TButton")
        self.stop_button.pack(side=tk.LEFT, padx=10)

        # Start updating the video frames
        self.update()

    def update(self):
        try:
            # Read a frame from the video source
            ret, frame = self.cap.read()
            if ret:
                if self.monitoring:
                    # Perform motion detection
                    motion_detected = self.detect_motion(frame)

                    if motion_detected:
                        # Log detection
                        self.log_detection()

                        # Save the frame as an image
                        self.save_snapshot(frame)

                        # Check if alert sound is not playing and play it
                        if not self.playing_alert:
                            self.play_alert_sound()
                            self.playing_alert = True

                        self.status_var.set("Motion detected")
                    else:
                        self.status_var.set("Monitoring...")

                    # Reset playing_alert flag when no motion is detected
                    if not motion_detected:
                        self.playing_alert = False

                # Convert the frame to RGB format and display it on the canvas
                self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
                self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)

            # Schedule the next update in `self.delay` milliseconds
            self.window.after(self.delay, self.update)

        except Exception as e:
            self.display_error(str(e))

    def detect_motion(self, frame):
        # Convert frame to grayscale for motion detection
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        gray = cv2.GaussianBlur(gray, (21, 21), 0)

        # Initialize previous_frame if it's None
        if self.previous_frame is None:
            self.previous_frame = gray
            return False

        # Compute the absolute difference between the current frame and previous frame
        frame_delta = cv2.absdiff(self.previous_frame, gray)
        self.previous_frame = gray

        # Threshold the delta image to find regions with significant changes
        thresh = cv2.threshold(frame_delta, self.motion_threshold, 255, cv2.THRESH_BINARY)[1]
        thresh = cv2.dilate(thresh, None, iterations=2)
        contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        # If contours are found, motion is detected
        return len(contours) > 0

    def update_sensitivity(self, value):
        self.motion_threshold = int(value)

    def play_alert_sound(self):
        # Play the alert sound
        self.alert_sound.play()

    def log_detection(self):
        # Log detection details with timestamp
        now = datetime.datetime.now()
        log_entry = f"{now.strftime('%Y-%m-%d %H:%M:%S')} - Motion detected\n"

        # Append log entry to the log file
        with open(self.log_file, 'a') as log:
            log.write(log_entry)

    def save_snapshot(self, frame):
        # Save the current frame as an image file
        now = datetime.datetime.now()
        filename = f"snap/snapshot_{now.strftime('%Y%m%d_%H%M%S')}.png"
        cv2.imwrite(filename, frame)

    def display_error(self, message):
        # Display error message in a dialog box or console
        print(f"Error: {message}")

    def start_monitoring(self):
        self.monitoring = True
        self.status_var.set("Monitoring started")

    def stop_monitoring(self):
        self.monitoring = False
        self.status_var.set("Monitoring stopped")
        self.alert_sound.stop()
        self.playing_alert = False

# Create the main application window
if __name__ == '__main__':
    root = tk.Tk()
    app = SurveillanceApp(root, "Surveillance System")
    root.mainloop()

Code Explanation

Imports and Initialization

The program starts by importing essential libraries:

  • tkinter and ttk for creating the graphical user interface.
  • cv2 (OpenCV) for video capture and processing.
  • PIL for image handling and display.
  • pygame for playing alert sounds.
  • os and datetime for file handling and timestamp management.

SurveillanceApp Class

Initialization

The SurveillanceApp class is responsible for setting up the entire application. The __init__ method initializes the main window, video capture device, and other components.

  1. Main Window Setup: Sets the window title, size, and background color.
  2. Video Capture Setup: Initializes the webcam for video capture. Sets the capture resolution and verifies that the camera is working.
  3. Canvas Creation: Creates a tkinter canvas to display the video frames.
  4. Pygame Initialization: Initializes pygame for playing alert sounds. Loads the alert sound file.
  5. Control Variables: Initializes variables for controlling the refresh delay, motion detection, and logging.
GUI Components

Several GUI components are set up to interact with the user:

  1. Status Bar: Displays the current status of the system (e.g., “Monitoring…” or “Motion detected”).
  2. Settings Frame: Contains a slider to adjust the motion detection sensitivity.
  3. Control Frame: Contains buttons to start and stop the motion detection.
Video Frame Update

The update method is called periodically to read a new frame from the video source and process it:

  1. Frame Reading: Captures a frame from the webcam.
  2. Motion Detection: If monitoring is enabled, the captured frame is checked for motion.
  3. Motion Handling: If motion is detected, logs the event, saves a snapshot, and plays an alert sound.
  4. Frame Display: Converts the frame to a format suitable for displaying on the tkinter canvas and updates the canvas.
Motion Detection

The detect_motion method processes the current frame to detect motion:

  1. Grayscale Conversion: Converts the frame to grayscale for easier processing.
  2. Blurring: Applies a Gaussian blur to reduce noise.
  3. Frame Differencing: Computes the absolute difference between the current frame and the previous frame.
  4. Thresholding: Applies a binary threshold to highlight significant changes.
  5. Contour Detection: Finds contours in the thresholded image to detect regions with significant changes.
Sensitivity Adjustment

The update_sensitivity method updates the motion detection sensitivity based on the slider value.

Alert Handling

The play_alert_sound method plays an alert sound when motion is detected.

Event Logging

The log_detection method logs the motion detection event with a timestamp in a log file.

Snapshot Saving

The save_snapshot method saves the current frame as an image file.

Error Handling

The display_error method displays error messages.

Monitoring Control

The start_monitoring and stop_monitoring methods control the motion detection process.

Main Application

Finally, the main application window is created, and an instance of the SurveillanceApp class is initialized to start the surveillance system.

This setup creates a functional surveillance system that captures video, detects motion, logs events, saves snapshots, and alerts the user with a sound when motion is detected.

Running the Application

To run the application, navigate to the project directory in your terminal or command prompt and execute the following command:

python surveillance_app.py

Conclusion

When you run the code, the application window will open, displaying the live video feed from your webcam. Use the “Start” button to begin motion detection. When motion is detected, an alert sound will play, a snapshot will be saved in the snap directory, and the event will be logged in the detection_log.txt file. You can adjust the motion sensitivity using the slider provided in the settings frame.

This project demonstrates how to integrate video capture, motion detection, and alert mechanisms in a simple GUI application using Python. It’s a great starting point for building more advanced surveillance and security systems.

For more tutorials on Python and Django, be sure to subscribe to our YouTube channel and read our blogs. We regularly post content to help you learn and improve your programming skills.

Happy coding!

1 thought on “How To Build Motion Detection Surveillance System with Python”

Leave a Comment

Your email address will not be published. Required fields are marked *

Shopping Basket