Last active
May 19, 2017 22:01
-
-
Save tacoman784/07e909e8528f412364f044985d7df32e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ************************************** | |
# Simple OpenCV / Python Security Camera | |
# Defines three Zones where activity is looked for. | |
# Each Zone is a region-of-interest for OpenCV. | |
# Simple toggle switches allow video on/off and Zone switching. | |
# Runs on a Raspberry Pi Model B with standard Pi Camera in a IP64 ELectrical box with | |
# Ethernet cable to home network. | |
# Adapted from http://www.pyimagesearch.com/2015/06/01/home-surveillance-and-motion-detection-with-the-raspberry-pi-python-and-opencv/ | |
# | |
# ************************************** | |
from picamera.array import PiRGBArray | |
from picamera import PiCamera | |
import time | |
import datetime | |
import numpy as np | |
import cv2 | |
import glob | |
import os | |
# TO DO | |
# Script to start on boot and cron job to check if running | |
# see http://frederickvandenbosch.be/?p=1701 | |
# ******************** | |
def roi(img,vertices): | |
# ******************** | |
mask = np.zeros_like(img) | |
cv2.fillPoly(mask,vertices,255) | |
masked = cv2.bitwise_and(img,mask) | |
return masked | |
# ******************************** | |
def auto_canny(image, sigma=0.33): | |
# ******************************** | |
# compute the median of the single channel pixel intensities | |
v = np.median(image) | |
# apply automatic Canny edge detection using the computed median | |
lower = int(max(0, (1.0 - sigma) * v)) | |
upper = int(min(255, (1.0 + sigma) * v)) | |
edged = cv2.Canny(image, lower, upper) | |
# return the edged image | |
return edged | |
# ******************************************** | |
def DeleteImages(DaysImagesToKeep,DayOfMonth): | |
# ******************************************** | |
if DaysImagesToKeep == 7: | |
if DayOfMonth <= 7: | |
for x in range(8,31): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 14: | |
for x in range(1,6): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
for x in range(15,31): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 21: | |
for x in range(1,15): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
for x in range(22,31): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 31: | |
for x in range(1,21): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
# ************************* | |
elif DaysImagesToKeep == 14: | |
# ************************* | |
if DayOfMonth <= 7: | |
for x in range(8,21): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 14: | |
for x in range(15,31): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 21: | |
for x in range(1,7): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
for x in range(22,31): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
elif DayOfMonth <= 31: | |
for x in range(1,14): | |
directory= '/home/pi/Desktop/snapshots/Day'+str(x) | |
print 'Deleting from ' + str(directory) | |
try: | |
os.chdir(directory) | |
files=glob.glob('*.jpg') | |
for filename in files: | |
os.unlink(filename) | |
except: | |
pass | |
return | |
# ******************************* | |
def Main(Mode,DEBUG,Method,Zone): | |
# ******************************* | |
# Yellow (0,255,255) | |
# Red | |
# Green (0,255,0) | |
# White (255, 255, 255) | |
# ************* # Set Variables | |
OldMode = Mode | |
ThreshValue = 12 | |
MinArea = 450 | |
OldMinArea = MinArea | |
contouredimage = None | |
min_motion_frames = 10 | |
min_snapshot_seconds = 2 | |
MaxSnapshots = 40 | |
Output1 = True | |
Output2 = False | |
Snapshots = 0 | |
MaxArea = 0 | |
OccupiedCount = 0 | |
DayNumber = 0 | |
PhotoToggle = 'On' | |
DaysImagesToKeep = 7 # ,14 | |
DoneMidnight = False | |
OldContour = 0 # Allows rectangles to suppress redraw | |
VideoToggle = 'Off' | |
# ********************************* | |
# Set Region of Interest dimensions | |
# ********************************* | |
# ******************************************************************** | |
# initialize the camera and grab a reference to the raw camera capture | |
# ******************************************************************** | |
camera = PiCamera() | |
camera.resolution = (640, 480) | |
#camera.framerate = 32 | |
rawCapture = PiRGBArray(camera, size=(640, 480)) | |
# *************** | |
# Camera Settings | |
# http://stackoverflow.com/questions/11420748/setting-camera-parameters-in-opencv-python | |
# *************** | |
# allow the camera to warmup | |
time.sleep(2) | |
avg = None | |
lastUploaded = datetime.datetime.now() | |
lp = 0 | |
month = datetime.datetime.now().month | |
# ****************************** | |
# capture frames from the camera | |
# ****************************** | |
area_List = [] | |
Alert = False | |
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): | |
lp += 1 | |
text = "Unoccupied" | |
ts = datetime.datetime.now().strftime("%A %d %B %Y %H:%M:%S") | |
# SUMMER OR WINTER? (DUSK IS DIFFERENT) | |
if lp < 5 or lp % 5000 == 0: | |
hour = datetime.datetime.now().hour | |
DayNumber = datetime.datetime.now().day | |
minute = datetime.datetime.now().minute | |
# ************** | |
# MIDNIGHT CHECK | |
# ************** | |
if int(hour) == 0: | |
if int(minute) == 1 and DoneMidnight == False: | |
DayOfMonth = datetime.datetime.now().day | |
lp = 0 # reset frame count | |
try: | |
DeleteImages(DaysImagesToKeep,DayOfMonth) | |
DoneMidnight = True | |
except exception as e: | |
print str(e) | |
elif int(minute) == 2: | |
DoneMidnight = False | |
# *********************** | |
# Check every 5000 frames | |
# *********************** | |
if lp % 5000 == 0: | |
DayNumber = datetime.datetime.now().day | |
# Check for Size of local directory | |
# /home/pi/Desktop/snapshots/Dayxx | |
if lp % 5 == 0: | |
OldContour = 0 | |
# WHEN IS DUSK? | |
if month == 1: | |
Dusk = 18 | |
Dawn = 7 | |
elif month == 2: | |
Dusk = 19 | |
Dawn = 7 | |
elif month == 3: | |
Dusk = 19 | |
Dawn = 6 | |
elif month == 4: | |
Dusk = 20 | |
Dawn = 6 | |
elif month == 5: | |
Dusk = 21 | |
Dawn = 6 | |
elif month == 6: | |
Dusk = 22 | |
Dawn = 6 | |
elif month == 7: | |
Dusk = 22 | |
Dawn = 5 | |
elif month == 8: | |
Dusk = 21 | |
Dawn = 5 | |
elif month == 9: | |
Dusk = 21 | |
Dawn = 6 | |
elif month == 10: | |
Dusk = 20 | |
Dawn = 6 | |
elif month == 11: | |
Dusk = 19 | |
Dawn = 6 | |
elif month == 12: | |
Dusk = 18 | |
Dawn = 7 | |
if int(hour) >= Dusk or int(hour) <= Dawn: | |
Offline = True | |
MinArea = OldMinArea - (OldMinArea * 0.1) | |
Mode = 'Camera Offline' | |
else: | |
Offline = False | |
if Mode != 'Video': | |
Mode = OldMode | |
MinArea = OldMinArea | |
# grab the raw NumPy array representing the image - this array | |
# will be 3D, representing the width, height, and # of channels | |
image = frame.array | |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
# Define a Region of Interest (ROI) | |
if Zone == 'PropertyBoundary': | |
TopOfHedge = 395 | |
Pavement = 385 | |
GatePillarTop = 390 | |
pts = np.array([[60,400],[75,400],[200,390],[239,GatePillarTop-1],[242,Pavement+40],[635,Pavement],[635,479],[60,479]], np.int32) | |
gray = roi(gray,[pts]) | |
cv2.polylines(image, [pts], True, (0,0,255), 1) | |
elif Zone == 'Road': | |
pts = np.array([[250,385],[83,395],[83,370],[635,300],[635,380],[252,425]],np.int32) | |
gray = roi(gray,[pts]) | |
cv2.polylines(image, [pts], True, (0,0,255), 1) | |
#vertices = np.array([[5,280],[635,478]]) | |
elif Zone == 'RoadCount': | |
pts = np.array([[400,365],[480,395],[350,420],[00,420]],np.int32) | |
gray = roi(gray,[pts]) | |
cv2.polylines(image, [pts], True, (0,0,255), 1) | |
blurred = cv2.GaussianBlur(gray, (21, 21), 0) # smooth out every 21x21 pixel region | |
# apply Canny edge detection using a wide threshold, tight | |
# threshold, and automatically determined threshold | |
wide = cv2.Canny(blurred, 10, 200) | |
tight = cv2.Canny(blurred, 225, 250) | |
edged = auto_canny(blurred) | |
if avg is None: | |
avg = gray.copy().astype("float") | |
rawCapture.truncate(0) | |
continue | |
# Update the average frame with the new grayscale image | |
# Less weightage is given to the new grayscale image | |
cv2.accumulateWeighted(gray, avg, 0.5) | |
# compute the absolute difference between the current frame and first frame | |
frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg)) | |
return_flag,thresh = cv2.threshold(frameDelta, ThreshValue,255, cv2.THRESH_BINARY) | |
# dilate the thresholded image to fill in holes, then find contours on thresholded image | |
dilated = cv2.dilate(thresh, None, iterations=2) | |
(im2,cnts,hierachy) = cv2.findContours(dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
# People tend to be a vertical orientated rectangle, cars are horizontal orientated rectangle. | |
# ****************************************** | |
if Method == 'PyImage': | |
# ****************************************** | |
area = 0 | |
if lp > 10: # Dont start snapping if just started | |
for c in cnts: | |
area = cv2.contourArea(c) | |
try: | |
if area < MinArea: | |
#print 'C is too small' + str(c) + ' ' + str(int(cv2.contourArea(c))) | |
if text == "Occupied": | |
text = 'Finished' | |
continue | |
else: | |
print 'Found a contour ' + str(area) | |
if area > MaxArea:MaxArea = area | |
area_List.append(area) | |
OccupiedCount += 1 | |
text = "Occupied" | |
if int(area) > int(OldContour): | |
# compute the bounding box for the contour, draw it on the frame, | |
# and update the text | |
(x, y, w, h) = cv2.boundingRect(c) | |
try: | |
print ' Box is ' + str(x) + ',' + str(y) + ',' + str(w) + ',' + str(h) | |
cv2.rectangle(image, (x, y), (x + w, y + h), (0,255,0), 2) | |
#cv2.rectangle(image, (x,y), (x+10 + w, y +10 + h), (0,255,0),-1,1) | |
#cv2.putText(image, "Car", (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255)) | |
except: | |
pass | |
else: | |
OldContour = area | |
except Exception as e: | |
print 'Exception:' + str(e) | |
except: | |
pass | |
# **************************************** | |
# TAKE A SNAPSHOT EVERY X SECS IF OCCUPIED | |
# **************************************** | |
if PhotoToggle == 'On': | |
if text == "Occupied": | |
if int(MaxArea) >= int(area): | |
camera.capture('/home/pi/Desktop/snapshots/Day' + str(DayNumber) + '/'+ str(ts) + '_' + str(MaxArea) + '.jpg') | |
Snapshots += 1 | |
Mode = 'Taking Photo' | |
if Snapshots > MaxSnapshots: # Dont store more than 50 pics | |
Snapshots = 1 | |
else: | |
Snapshots = 1 | |
if len(area_List) > 0: | |
AvgArea = sum(area_List) / float(len(area_List)) | |
else: | |
AvgArea = 0 | |
else: | |
AvgArea = 0 | |
if Offline: | |
cv2.putText(image, "Front of House: {}".format(text), (10, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Mode: {}".format(Mode), (10, 40),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, ts, (330,20), cv2.FONT_HERSHEY_SIMPLEX,0.55, (255, 255, 255), 1) | |
cv2.putText(thresh, "ThreshValue: " + str(ThreshValue), (10, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Occupied #: " + str(OccupiedCount), (10, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Zone: " + str(Zone), (10, 80),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Frame #: " + str(lp), (330, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Photo Toggle (f): " + str(PhotoToggle), (330, 80),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1) | |
cv2.putText(image, "MaxArea: {}".format(str(MaxArea)), (330,40), cv2.FONT_HERSHEY_SIMPLEX,0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Area: {}".format(str(area)), (510,40), cv2.FONT_HERSHEY_SIMPLEX,0.55, (255, 255, 255), 1) | |
cv2.putText(image, "Avg: {}".format(str(AvgArea)), (510,60), cv2.FONT_HERSHEY_SIMPLEX,0.55, (255, 255, 255), 1) | |
else: | |
cv2.putText(image, "Front of House: {}".format(text), (10, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Mode: {}".format(Mode), (10, 40),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, ts, (330,20), cv2.FONT_HERSHEY_SIMPLEX,0.55, (0, 0, 255), 1) | |
cv2.putText(image, "MaxArea: {}".format(str(MaxArea)), (330,40), cv2.FONT_HERSHEY_SIMPLEX,0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Area: {}".format(str(area)), (510,40), cv2.FONT_HERSHEY_SIMPLEX,0.55, (0, 0, 255), 1) | |
cv2.putText(frameDelta, "Delta: {}".format(ThreshValue), (10, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Occupied #: " + str(OccupiedCount), (10, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Zone: " + str(Zone), (10, 80),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Frame #: " + str(lp), (330, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Photo Toggle (f): " + str(PhotoToggle), (330, 80),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1) | |
cv2.putText(image, "Avg: {}".format(str(AvgArea)), (510,60), cv2.FONT_HERSHEY_SIMPLEX,0.55, (0, 0, 255), 1) | |
if text == "Occupied": | |
if Alert == True: | |
cv2.putText(image, "OCCUPIED!", (105,170), cv2.FONT_HERSHEY_SIMPLEX,2.55, (0, 0, 255), 9) | |
Alert = False | |
elif Alert == False: | |
Alert = True | |
# Show the frame and record if the user presses a key | |
# ********************************* | |
# RECORD VIDEO | |
# ********************************* | |
#if Offline == False: | |
if VideoToggle == 'On': | |
if VideoFileName == None: | |
print 'started' | |
hour = datetime.datetime.now().hour | |
minute = datetime.datetime.now().minute | |
VideoFileName = '/home/pi/Desktop/snapshots/Day' + str(DayNumber) + '/' + str(hour) + str(minute) + '.h264' | |
camera.start_preview() | |
camera.start_recording(VideoFileName) | |
else: | |
print 'Recording' | |
if DEBUG: | |
cv2.namedWindow("Delta", cv2.WINDOW_NORMAL) | |
cv2.resizeWindow("Delta", 320,240) | |
cv2.imshow("Delta", frameDelta) | |
#cv2.namedWindow("ROI", cv2.WINDOW_NORMAL) | |
#cv2.resizeWindow("ROI", 320,240) | |
#cv2.imshow("ROI", ROI) | |
cv2.namedWindow("dilated", cv2.WINDOW_NORMAL) | |
cv2.resizeWindow("dilated", 320,240) | |
cv2.imshow("dilated",dilated) | |
#cv2.namedWindow("Thresh", cv2.WINDOW_NORMAL) | |
#cv2.resizeWindow("Thresh", 320,240) | |
#cv2.imshow("Thresh", thresh) | |
cv2.namedWindow("Security Feed", cv2.WINDOW_NORMAL) | |
if DEBUG: cv2.resizeWindow("Security Feed", 320,240) | |
cv2.imshow("Security Feed",image) | |
#cv2.namedWindow("Gray", cv2.WINDOW_NORMAL) | |
#cv2.resizeWindow("Gray", 320,240) | |
#cv2.imshow("Gray", gray) | |
#v2.namedWindow("Edge", cv2.WINDOW_NORMAL) | |
#cv2.resizeWindow("Edge", 320,240) | |
#cv2.imshow("Edge", edged) | |
# show the frame | |
#cv2.imshow("Office", image) | |
key = cv2.waitKey(1) & 0xFF | |
# clear the stream in preparation for the next frame | |
rawCapture.truncate(0) | |
# if the `q` key was pressed, break from the loop | |
if key == ord("q") or key == 27: | |
break | |
elif key == ord("r"): | |
Zone = 'Road' | |
lp = 0 | |
MinArea = 800 | |
AvgArea = 0 | |
MaxArea = 0 | |
OccupiedCount = 0 | |
area_List = [] | |
InCount = 0 | |
OutCount = 0 | |
elif key == ord("p"): | |
Zone = 'PropertyBoundary' | |
MinArea = 450 | |
lp = 0 | |
AvgArea = 0 | |
MaxArea = 0 | |
OccupiedCount = 0 | |
area_List = [] | |
InCount = 0 | |
OutCount = 0 | |
elif key == ord("f"): | |
lp = 0 | |
if PhotoToggle == 'On': | |
PhotoToggle = 'Off' | |
else: | |
PhotoToggle = 'On' | |
elif key == ord("v"): | |
if VideoToggle == 'On': | |
VideoToggle = 'Off' | |
camera.stop_recording() | |
camera.stop_preview() | |
Mode = OldMode | |
elif VideoToggle == 'Off': | |
VideoToggle = 'On' | |
Mode = 'Video' | |
VideoFileName = None | |
# cleanup the camera and close any open windows | |
cv2.VideoCapture(0).release() | |
#out.release() | |
cv2.destroyAllWindows() | |
return | |
# ************************ | |
if __name__ == "__main__": | |
# ************************ | |
DEBUG = 0 | |
Mode = 'Live' | |
Method = 'PyImage' | |
#Zone = 'Road' | |
#Zone = 'RoadCount' | |
Zone = 'PropertyBoundary' | |
Main(Mode,DEBUG,Method,Zone) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment