diff --git a/camera.py b/camera.py index 065a321..9e528fb 100644 --- a/camera.py +++ b/camera.py @@ -3,11 +3,27 @@ import imutils import time import numpy as np +import datetime + +video_res = (640, 480) +object_det_res = (320, 240) + +video_frame_rate = 30 +video_out_dir = '/home/pi/' + +background_frame = None +background_update_rate = 120 # 2 mins. Period to update background frame for motion detection. +last_back_update = None +motion_det_min_area = 2000 # Regulate motion detection sensitivity. Smaller value - greater sensitivity. class VideoCamera(object): def __init__(self, flip = False): - self.vs = PiVideoStream().start() + self.vs = PiVideoStream(resolution=video_res, framerate=video_frame_rate).start() self.flip = flip + + self.x_coef = float(video_res[0]) / object_det_res[0] # needed to translate between frame sizes + self.y_coef = float(video_res[1]) / object_det_res[1] + time.sleep(2.0) def __del__(self): @@ -23,10 +39,50 @@ def get_frame(self): ret, jpeg = cv2.imencode('.jpg', frame) return jpeg.tobytes() + def motion_detection(self): + frame = self.flip_if_needed(self.vs.read()) #grabbing frame + + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #graying it + gray = cv2.GaussianBlur(gray, (5, 5), 0) #blurring + + global background_frame + global last_back_update + global background_update_rate + global motion_det_min_area + + if (background_frame is None) or (time.time() - last_back_update) > background_update_rate: + background_frame = gray + last_back_update = time.time() + return None, False + + frameDelta = cv2.absdiff(background_frame, gray) + thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1] + + thresh = cv2.dilate(thresh, None, iterations=2) #fill the holes + (_, cnts, _) = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #find contours of the objects + + found_something = False + + return_obj = frame + # return_obj = gray + + for c in cnts: + # if the contour is too small, ignore it + if cv2.contourArea(c) < motion_det_min_area: + continue + + found_something = True + (x, y, w, h) = cv2.boundingRect(c) + cv2.rectangle(return_obj, (x, y), (x + w, y + h), (153, 0, 204), 2) # different color rectangle for motion detect + + ret, jpeg = cv2.imencode('.jpg', return_obj) + return (jpeg.tobytes(), found_something) + def get_object(self, classifier): found_objects = False frame = self.flip_if_needed(self.vs.read()).copy() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + gray = cv2.resize(gray, object_det_res, interpolation=cv2.INTER_AREA) objects = classifier.detectMultiScale( gray, @@ -41,9 +97,27 @@ def get_object(self, classifier): # Draw a rectangle around the objects for (x, y, w, h) in objects: - cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) + cv2.rectangle(frame + , (int(self.x_coef * x), int(self.y_coef * y)) + , (int(self.x_coef * x + self.x_coef * w), int(self.y_coef * y + self.y_coef * h)) + , (0, 255, 0) + , 2) ret, jpeg = cv2.imencode('.jpg', frame) return (jpeg.tobytes(), found_objects) + + def capture_video(self, send_video_len): + video_loc = video_out_dir + datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S") +'.avi' + fourcc = cv2.VideoWriter_fourcc(*'X264') + out = cv2.VideoWriter(video_loc, fourcc, video_frame_rate, video_res) + + start_time = time.time() + while (time.time() - start_time) <= send_video_len: + frame = self.flip_if_needed(self.vs.read()) + #(_, _, frame) = self.motion_detection() + out.write(frame) + out.release() + + return video_loc diff --git a/mail.py b/mail.py index f1876f6..30c33d8 100644 --- a/mail.py +++ b/mail.py @@ -1,7 +1,9 @@ import smtplib -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEImage import MIMEImage +from os.path import basename +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication +from email.mime.text import MIMEText +from email.mime.image import MIMEImage # Email you want to send the update from (only works with gmail) fromEmail = 'email@gmail.com' @@ -20,8 +22,7 @@ def sendEmail(image): msgRoot.preamble = 'Raspberry pi security camera update' msgAlternative = MIMEMultipart('alternative') - msgRoot.attach(msgAlternative) - msgText = MIMEText('Smart security cam found object') + msgText = MIMEText('Smart security cam found object.') msgAlternative.attach(msgText) msgText = MIMEText('', 'html') @@ -29,6 +30,8 @@ def sendEmail(image): msgImage = MIMEImage(image) msgImage.add_header('Content-ID', '') + + msgRoot.attach(msgAlternative) msgRoot.attach(msgImage) smtp = smtplib.SMTP('smtp.gmail.com', 587) @@ -36,3 +39,33 @@ def sendEmail(image): smtp.login(fromEmail, fromEmailPassword) smtp.sendmail(fromEmail, toEmail, msgRoot.as_string()) smtp.quit() + +def sendVideoEmail(vid, keep_video_after_sending): + msgRoot = MIMEMultipart('related') + msgRoot['Subject'] = 'Security Update' + msgRoot['From'] = fromEmail + msgRoot['To'] = toEmail + msgRoot.preamble = 'Raspberry pi security camera update' + + textStr = 'Smart security cam finished recording video.' + if keep_video_after_sending: + textStr += ' The video is saved on your Raspberry Pi at location: ' + vid + + msgAlternative = MIMEMultipart('alternative') + msgText = MIMEText(textStr) + msgAlternative.attach(msgText) + + with open(vid, "rb") as fil: + part = MIMEApplication(fil.read(), Name=basename(vid)) + + # After the file is closed + part['Content-Disposition'] = 'attachment; filename="%s"' % basename(vid) + + msgRoot.attach(msgAlternative) + msgRoot.attach(part) + + smtp = smtplib.SMTP('smtp.gmail.com', 587) + smtp.starttls() + smtp.login(fromEmail, fromEmailPassword) + smtp.sendmail(fromEmail, toEmail, msgRoot.as_string()) + smtp.quit() diff --git a/main.py b/main.py index e6ed35d..d3127f5 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,21 @@ import cv2 import sys -from mail import sendEmail +from mail import sendEmail, sendVideoEmail from flask import Flask, render_template, Response from camera import VideoCamera from flask_basicauth import BasicAuth import time import threading +import os email_update_interval = 600 # sends an email only once in this time interval -video_camera = VideoCamera(flip=True) # creates a camera object, flip vertically +video_camera = VideoCamera(flip=False) # creates a camera object, flip vertically object_classifier = cv2.CascadeClassifier("models/fullbody_recognition_model.xml") # an opencv classifier +use_motion_detection = False + +send_video = True +send_video_len = 30 #length of the video attached to the second email +keep_video_after_sending = False # App Globals (do not edit) app = Flask(__name__) @@ -21,17 +27,33 @@ last_epoch = 0 def check_for_objects(): - global last_epoch - while True: - try: - frame, found_obj = video_camera.get_object(object_classifier) - if found_obj and (time.time() - last_epoch) > email_update_interval: - last_epoch = time.time() - print "Sending email..." - sendEmail(frame) - print "done!" - except: - print "Error sending email: ", sys.exc_info()[0] + global last_epoch + while True: + try: + global use_motion_detection + if use_motion_detection: + frame, found_obj = video_camera.motion_detection() + if found_obj: + # motion detection is fired only if detected in two frames in a row (reduces false positive) + frame, found_obj = video_camera.motion_detection() + else: + frame, found_obj = video_camera.get_object(object_classifier) + if found_obj and (time.time() - last_epoch) > email_update_interval: + last_epoch = time.time() + print ("Sending email...") + sendEmail(frame) + print ("done!") + if send_video: + print ("Capturing video...") + vid = video_camera.capture_video(send_video_len) + print ("Sending video email...") + sendVideoEmail(vid, keep_video_after_sending) + print ("done!") + if not keep_video_after_sending: + os.remove(vid) + print ("Video file removed") + except: + print ("Error sending email: ", sys.exc_info()[0]) @app.route('/') @basic_auth.required