From 06a228111c678060ffa226e273cd4dfcf4263292 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 19:48:40 +0200 Subject: [PATCH 1/8] Python3 branch brought into working state (probably) --- mail.py | 8 ++++---- main.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mail.py b/mail.py index f1876f6..ba2aee9 100644 --- a/mail.py +++ b/mail.py @@ -1,7 +1,7 @@ import smtplib -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEImage import MIMEImage +from email.mime.multipart import MIMEMultipart +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' @@ -10,7 +10,7 @@ fromEmailPassword = 'password' # Email you want to send the update to -toEmail = 'email2@gmail.com' +toEmail = 'vladfedchenko@gmail.com' def sendEmail(image): msgRoot = MIMEMultipart('related') diff --git a/main.py b/main.py index e6ed35d..41b5459 100644 --- a/main.py +++ b/main.py @@ -27,11 +27,11 @@ def check_for_objects(): 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..." + print ("Sending email...") sendEmail(frame) - print "done!" + print ("done!") except: - print "Error sending email: ", sys.exc_info()[0] + print ("Error sending email: ", sys.exc_info()[0]) @app.route('/') @basic_auth.required From 8156fc67b04307cf32828c1e70c67693b530c19c Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 19:51:26 +0200 Subject: [PATCH 2/8] Removing acidentally changed email --- mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mail.py b/mail.py index ba2aee9..b3d2808 100644 --- a/mail.py +++ b/mail.py @@ -10,7 +10,7 @@ fromEmailPassword = 'password' # Email you want to send the update to -toEmail = 'vladfedchenko@gmail.com' +toEmail = 'email2@gmail.com' def sendEmail(image): msgRoot = MIMEMultipart('related') From 9ac069bae9a5d1c53e7a1c8d646fdc2c326b1ca1 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 20:55:26 +0200 Subject: [PATCH 3/8] Motion detection added --- camera.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ main.py | 9 ++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/camera.py b/camera.py index 065a321..5167fa7 100644 --- a/camera.py +++ b/camera.py @@ -4,6 +4,12 @@ import time import numpy as np + +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() @@ -23,6 +29,45 @@ 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, (21, 21), 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 + + for c in cnts: + # if the contour is too small, ignore it + if cv2.contourArea(c) < motion_det_min_area: + continue + + found_something = True + background_frame = gray + (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() diff --git a/main.py b/main.py index 41b5459..d8f95aa 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ email_update_interval = 600 # sends an email only once in this time interval video_camera = VideoCamera(flip=True) # creates a camera object, flip vertically object_classifier = cv2.CascadeClassifier("models/fullbody_recognition_model.xml") # an opencv classifier +use_motion_detection = False # App Globals (do not edit) app = Flask(__name__) @@ -24,7 +25,13 @@ def check_for_objects(): global last_epoch while True: try: - frame, found_obj = video_camera.get_object(object_classifier) + if use_motion_detection: + frame, found_obj = video_camera.motion_detection() + if found_obj: + frame, found_obj = video_camera.motion_detection() # motion detection is fired only if detected in two frames in a row + # (reduces false positives) + 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...") From 6cd9fa54d5afc870b8ab248542a3ff7dd108cbf2 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 21:17:52 +0200 Subject: [PATCH 4/8] Bug fixes --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index d8f95aa..d88f69e 100644 --- a/main.py +++ b/main.py @@ -28,8 +28,8 @@ def check_for_objects(): if use_motion_detection: frame, found_obj = video_camera.motion_detection() if found_obj: - frame, found_obj = video_camera.motion_detection() # motion detection is fired only if detected in two frames in a row - # (reduces false positives) + # 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: From 42a8684f19c7eafefc406c1e9f5596c13c842c04 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 21:52:38 +0200 Subject: [PATCH 5/8] Additional fixes --- camera.py | 5 +++-- main.py | 25 +++++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/camera.py b/camera.py index 5167fa7..76d5085 100644 --- a/camera.py +++ b/camera.py @@ -33,7 +33,7 @@ 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, (21, 21), 0) #blurring + gray = cv2.GaussianBlur(gray, (5, 5), 0) #blurring global background_frame global last_back_update @@ -53,7 +53,8 @@ def motion_detection(self): found_something = False - return_obj = frame + # return_obj = frame + return_obj = gray for c in cnts: # if the contour is too small, ignore it diff --git a/main.py b/main.py index d88f69e..969477c 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ import threading 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 @@ -22,23 +22,24 @@ last_epoch = 0 def check_for_objects(): - global last_epoch - while True: - try: + 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!") - except: - print ("Error sending email: ", sys.exc_info()[0]) + 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]) @app.route('/') @basic_auth.required From d788b1302efd834f367da3031a8b976f1d9869b8 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 22:16:23 +0200 Subject: [PATCH 6/8] Some additional fixes --- camera.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/camera.py b/camera.py index 76d5085..aad5324 100644 --- a/camera.py +++ b/camera.py @@ -53,8 +53,8 @@ def motion_detection(self): found_something = False - # return_obj = frame - return_obj = gray + return_obj = frame + # return_obj = gray for c in cnts: # if the contour is too small, ignore it @@ -62,7 +62,6 @@ def motion_detection(self): continue found_something = True - background_frame = gray (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 From 212edda32eea688d7a6c1bfc8b266ea1bc699f40 Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sat, 27 Oct 2018 22:35:53 +0200 Subject: [PATCH 7/8] Added new feature - split of stream framerate size and object detection framerate size. Larger framerate size produces beter website image while keeping nice performance since the object detection model is feeded with lower resolution frame --- camera.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/camera.py b/camera.py index aad5324..1e15389 100644 --- a/camera.py +++ b/camera.py @@ -4,6 +4,8 @@ import time import numpy as np +video_res = (640, 480) +object_det_res = (320, 240) background_frame = None background_update_rate = 120 # 2 mins. Period to update background frame for motion detection. @@ -12,8 +14,12 @@ class VideoCamera(object): def __init__(self, flip = False): - self.vs = PiVideoStream().start() + self.vs = PiVideoStream(resolution=video_res).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): @@ -72,6 +78,7 @@ 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, @@ -86,7 +93,11 @@ 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) From b3db7bd091467a6b2468eb6a186e8f4955fb499a Mon Sep 17 00:00:00 2001 From: Vladyslav Fedchenko Date: Sun, 28 Oct 2018 00:10:06 +0200 Subject: [PATCH 8/8] Video capturing and sending to email feature added --- camera.py | 20 +++++++++++++++++++- mail.py | 37 +++++++++++++++++++++++++++++++++++-- main.py | 16 +++++++++++++++- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/camera.py b/camera.py index 1e15389..9e528fb 100644 --- a/camera.py +++ b/camera.py @@ -3,10 +3,14 @@ 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 @@ -14,7 +18,7 @@ class VideoCamera(object): def __init__(self, flip = False): - self.vs = PiVideoStream(resolution=video_res).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 @@ -101,5 +105,19 @@ def get_object(self, classifier): 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 b3d2808..30c33d8 100644 --- a/mail.py +++ b/mail.py @@ -1,5 +1,7 @@ import smtplib +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 @@ -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 969477c..d3127f5 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,22 @@ 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=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__) app.config['BASIC_AUTH_USERNAME'] = 'CHANGE_ME_USERNAME' @@ -38,6 +43,15 @@ def check_for_objects(): 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])