#!/usr/bin/python # A simple surveillance camera script for the raspberry pi camera. # When the camera detects motion, a video is recorded. # On a default Raspian installation, the package 'python-opencv' is required: # apt-get install python-opencv # Known Issues # # 2917-12-18 (stretch) # picamera.PiCamera has increasing memory usage over days. # This script will exit after self.max_duration_capturing has been reached. # # 2017-05-24 (jessie) # There is an issue with cv2.VideoWriter (or the MJPG codec). # At some point, all newly created files have size zero after a frame # should have been written. # # As a workaround for the issues above, # the following shell script can be used to restart this python script: # # RESTART=1 # while [ "$RESTART" -ne 0 ]; do # ./cam_surveillance.py # RESTART=$? # done # __author__ = "Gernot Walzl" __date__ = "2017-12-18" import os import logging import signal from time import time from datetime import datetime from picamera import PiCamera from picamera.array import PiRGBArray import cv2 class SurveillanceCamera: def __init__(self): self.resolution = (640, 480) self.fps = 5 self.path_output = './camera/' self.min_length_video = 5.0 self.max_duration_capturing = 86400.0 self.camera = None self.capturing = False self.time_stop_capturing = 0.0 self.frame_curr_bgr = None self.frame_curr_gray = None self.frame_prev_bgr = None self.frame_prev_gray = None self.time_last_change = 0.0 self.video_writer = None self.recording = False if not os.path.exists(self.path_output): os.makedirs(self.path_output) logging.basicConfig(filename=self.path_output+'cam_surveillance.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def has_frame_changed(self): result = False frame_diff_gray = cv2.absdiff(self.frame_prev_gray, self.frame_curr_gray) frame_thresh = cv2.threshold(frame_diff_gray, 32, 255, cv2.THRESH_BINARY)[1] num_diff_pixels = frame_thresh.sum() / 255 if num_diff_pixels >= 32: result = True self.time_last_change = time() return result def write_frame(self, frame_bgr): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') frame = frame_bgr.copy() position = (8, 16) font = cv2.FONT_HERSHEY_PLAIN color = (255, 255, 255) cv2.putText(frame, timestamp, position, font, 1.0, color) self.video_writer.write(frame) def start_recording(self): dt_now = datetime.now() dirname = dt_now.strftime('%Y-%m-%d') basename = dt_now.strftime('%Y-%m-%d_%H%M%S') filename = self.path_output + dirname + '/' + basename + '.mjpg' #fourcc = cv2.cv.FOURCC('X', 'V', 'I', 'D') fourcc = cv2.cv.FOURCC('M', 'J', 'P', 'G') if not os.path.exists(self.path_output + dirname): os.makedirs(self.path_output + dirname) logging.info('Start recording to "' + filename + '".') self.recording = True logging.debug('fourcc=' + str(fourcc) + ', fps=' + str(self.fps) + ', res=' + str(self.resolution)) self.video_writer = cv2.VideoWriter(filename, fourcc, self.fps, self.resolution) logging.debug('video_writer=' + str(self.video_writer)+ ' isOpened=' + str(self.video_writer.isOpened())) logging.debug('frame_shape=' + str(self.frame_prev_bgr.shape)) self.write_frame(self.frame_prev_bgr) #self.camera.start_recording(filename) if os.path.getsize(filename) == 0: error_msg = 'Unable to encode frames into file (size=0).' logging.error(error_msg) exit('ERROR: ' + error_msg) def stop_recording(self): if self.video_writer: self.video_writer.release() self.video_writer = None #self.camera.stop_recording() self.recording = False logging.info('Recording stopped.') def handle_frame(self, frame): self.frame_curr_bgr = frame self.frame_curr_gray = cv2.cvtColor(self.frame_curr_bgr, cv2.COLOR_BGR2GRAY) # resizing is faster than cv2.GaussianBlur(frame_curr_gray, (33, 33), 0) self.frame_curr_gray = cv2.resize(self.frame_curr_gray, (160, 120)) if self.frame_prev_gray is not None: if self.has_frame_changed(): if not self.recording: self.start_recording() if self.recording: self.write_frame(self.frame_curr_bgr) if self.time_last_change + self.min_length_video < time(): self.stop_recording() self.frame_prev_bgr = self.frame_curr_bgr self.frame_prev_gray = self.frame_curr_gray def start_capturing(self): try: with PiCamera() as self.camera: self.camera.resolution = self.resolution self.camera.framerate = self.fps #self.camera.rotation = 180 with PiRGBArray(self.camera) as output: logging.info('Start capturing.') self.capturing = True self.time_stop_capturing = time() + self.max_duration_capturing for _ in self.camera.capture_continuous(output, 'bgr', use_video_port=True): self.handle_frame(output.array) output.truncate(0) if not self.capturing: break if not self.recording: if self.time_stop_capturing < time(): msg = 'Maximum capturing duration reached.' logging.info(msg) exit(msg) finally: if self.recording: self.stop_recording() logging.info('Capturing stopped.') def stop_capturing(self): self.capturing = False def handle_signal(self, signum, frame): self.stop_capturing() if __name__ == '__main__': survcam = SurveillanceCamera() signal.signal(signal.SIGINT, survcam.handle_signal) # Ctrl-C signal.signal(signal.SIGTERM, survcam.handle_signal) # kill survcam.start_capturing()