diff --git a/MotionDetector.py b/MotionDetector.py index 6709e39..12d4a48 100644 --- a/MotionDetector.py +++ b/MotionDetector.py @@ -1,108 +1,113 @@ import cv2.cv as cv from datetime import datetime +import pygame import time class MotionDetectorInstantaneous(): - - def onChange(self, val): #callback when the user change the detection threshold - self.threshold = val - - def __init__(self,threshold=8, doRecord=True, showWindows=True): - self.writer = None - self.font = None - self.doRecord=doRecord #Either or not record the moving object - self.show = showWindows #Either or not show the 2 windows - self.frame = None - - self.capture=cv.CaptureFromCAM(0) - self.frame = cv.QueryFrame(self.capture) #Take a frame to init recorder - if doRecord: - self.initRecorder() - - self.frame1gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t-1 - cv.CvtColor(self.frame, self.frame1gray, cv.CV_RGB2GRAY) - - #Will hold the thresholded result - self.res = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) - - self.frame2gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t - - self.width = self.frame.width - self.height = self.frame.height - self.nb_pixels = self.width * self.height - self.threshold = threshold - self.isRecording = False - self.trigger_time = 0 #Hold timestamp of the last detection - - if showWindows: - cv.NamedWindow("Image") - cv.CreateTrackbar("Detection treshold: ", "Image", self.threshold, 100, self.onChange) - - def initRecorder(self): #Create the recorder - codec = cv.CV_FOURCC('M', 'J', 'P', 'G') #('W', 'M', 'V', '2') - self.writer=cv.CreateVideoWriter(datetime.now().strftime("%b-%d_%H_%M_%S")+".wmv", codec, 5, cv.GetSize(self.frame), 1) - #FPS set to 5 because it seems to be the fps of my cam but should be ajusted to your needs - self.font = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 1, 1, 0, 2, 8) #Creates a font + + def onChange(self, val): #callback when the user change the detection threshold + self.threshold = val + + def __init__(self,threshold=8, doRecord=True, showWindows=True): + pygame.mixer.init() + self.cameraSound = pygame.mixer.Sound("snapshotsound.ogg") + self.writer = None + self.font = None + self.doRecord=doRecord #Either or not record the moving object + self.show = showWindows #Either or not show the 2 windows + self.frame = None + + self.capture=cv.CaptureFromCAM(0) + self.frame = cv.QueryFrame(self.capture) #Take a frame to init recorder + if doRecord: + self.initRecorder() + + self.frame1gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t-1 + cv.CvtColor(self.frame, self.frame1gray, cv.CV_RGB2GRAY) + + #Will hold the thresholded result + self.res = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) + + self.frame2gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t + + self.width = self.frame.width + self.height = self.frame.height + self.nb_pixels = self.width * self.height + self.threshold = threshold + self.isRecording = False + self.trigger_time = 0 #Hold timestamp of the last detection + + if showWindows: + cv.NamedWindow("Image") + cv.CreateTrackbar("Detection treshold: ", "Image", self.threshold, 100, self.onChange) + + def initRecorder(self): #Create the recorder + codec = cv.CV_FOURCC('D', 'I', 'V', 'X') #('W', 'M', 'V', '2') + self.writer=cv.CreateVideoWriter(datetime.now().strftime("%b-%d_%H_%M_%S")+".avi", codec, 20, cv.GetSize(self.frame), 1) + #FPS set to 20 because it seems to be the fps of my cam but should be ajusted to your needs + self.font = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 1, 1, 0, 2, 8) #Creates a font - def run(self): - started = time.time() - while True: - - curframe = cv.QueryFrame(self.capture) - instant = time.time() #Get timestamp o the frame - - self.processImage(curframe) #Process the image - - if not self.isRecording: - if self.somethingHasMoved(): - self.trigger_time = instant #Update the trigger_time - if instant > started +5:#Wait 5 second after the webcam start for luminosity adjusting etc.. - print datetime.now().strftime("%b %d, %H:%M:%S"), "Something is moving !" - if self.doRecord: #set isRecording=True only if we record a video - self.isRecording = True - else: - if instant >= self.trigger_time +10: #Record during 10 seconds - print datetime.now().strftime("%b %d, %H:%M:%S"), "Stop recording" - self.isRecording = False - else: - cv.PutText(curframe,datetime.now().strftime("%b %d, %H:%M:%S"), (25,30),self.font, 0) #Put date on the frame - cv.WriteFrame(self.writer, curframe) #Write the frame - - if self.show: - cv.ShowImage("Image", curframe) - cv.ShowImage("Res", self.res) - - cv.Copy(self.frame2gray, self.frame1gray) - c=cv.WaitKey(1) % 0x100 - if c==27 or c == 10: #Break if user enters 'Esc'. - break - - def processImage(self, frame): - cv.CvtColor(frame, self.frame2gray, cv.CV_RGB2GRAY) - - #Absdiff to get the difference between to the frames - cv.AbsDiff(self.frame1gray, self.frame2gray, self.res) - - #Remove the noise and do the threshold - cv.Smooth(self.res, self.res, cv.CV_BLUR, 5,5) - cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_OPEN) - cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_CLOSE) - cv.Threshold(self.res, self.res, 10, 255, cv.CV_THRESH_BINARY_INV) + def run(self): + started = time.time() + while True: + + curframe = cv.QueryFrame(self.capture) + instant = time.time() #Get timestamp o the frame + + self.processImage(curframe) #Process the image + + if not self.isRecording: + if self.somethingHasMoved(): + self.trigger_time = instant #Update the trigger_time + if instant > started +5:#Wait 5 second after the webcam start for luminosity adjusting etc.. + print datetime.now().strftime("%b %d, %H:%M:%S"), "Something is moving !" + self.cameraSound.play() + cv.SaveImage('snapshot.png', curframe) + if self.doRecord: #set isRecording=True only if we record a video + self.isRecording = True + else: + if instant >= self.trigger_time +10: #Record during 10 seconds + print datetime.now().strftime("%b %d, %H:%M:%S"), "Stop recording" + self.isRecording = False + else: + cv.PutText(curframe,datetime.now().strftime("%b %d, %H:%M:%S"), (25,30),self.font, 0) #Put date on the frame + cv.WriteFrame(self.writer, curframe) #Write the frame + + if self.show: + cv.ShowImage("Image", curframe) + cv.ShowImage("Res", self.res) + + cv.Copy(self.frame2gray, self.frame1gray) + c=cv.WaitKey(1) % 0x100 + if c==27 or c == 10: #Break if user enters 'Esc'. + break + + def processImage(self, frame): + cv.CvtColor(frame, self.frame2gray, cv.CV_RGB2GRAY) + + #Absdiff to get the difference between to the frames + cv.AbsDiff(self.frame1gray, self.frame2gray, self.res) + + #Remove the noise and do the threshold + cv.Smooth(self.res, self.res, cv.CV_BLUR, 5,5) + cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_OPEN) + cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_CLOSE) + cv.Threshold(self.res, self.res, 10, 255, cv.CV_THRESH_BINARY_INV) - def somethingHasMoved(self): - nb=0 #Will hold the number of black pixels + def somethingHasMoved(self): + nb=0 #Will hold the number of black pixels - for x in range(self.height): #Iterate the hole image - for y in range(self.width): - if self.res[x,y] == 0.0: #If the pixel is black keep it - nb += 1 - avg = (nb*100.0)/self.nb_pixels #Calculate the average of black pixel in the image + for x in range(self.height): #Iterate the hole image + for y in range(self.width): + if self.res[x,y] == 0.0: #If the pixel is black keep it + nb += 1 + avg = (nb*100.0)/self.nb_pixels #Calculate the average of black pixel in the image - if avg > self.threshold:#If over the ceiling trigger the alarm - return True - else: - return False - + if avg > self.threshold:#If over the ceiling trigger the alarm + return True + else: + return False + if __name__=="__main__": - detect = MotionDetectorInstantaneous(doRecord=True) - detect.run() + detect = MotionDetectorInstantaneous(doRecord=True) + detect.run() diff --git a/MotionDetectorContours.py b/MotionDetectorContours.py index 729a53a..c403db3 100644 --- a/MotionDetectorContours.py +++ b/MotionDetectorContours.py @@ -7,7 +7,7 @@ class MotionDetectorAdaptative(): def onChange(self, val): #callback when the user change the detection threshold self.threshold = val - def __init__(self,threshold=25, doRecord=True, showWindows=True): + def __init__(self,threshold=7, doRecord=True, showWindows=True): self.writer = None self.font = None self.doRecord=doRecord #Either or not record the moving object diff --git a/README.md b/README.md index 87bf73d..74fe535 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,23 @@ -Motion-detection-OpenCV +Python Security Camera ======================= Python/OpenCV script that detect motion on webcam and allow record it to a file. ## The simple way ## -The trivial idea is to compute the difference between two frames apply a threshold the separate pixels that have changed from the others and then count all the black pixels. Then the average is calculated with this count and the total number of pixels and depending of the ceil the event is triggered or not. +[![https://www.youtube.com/watch?v=-RUu3EcielI](https://img.youtube.com/vi/-RUu3EcielI/0.jpg)](https://www.youtube.com/watch?v=-RUu3EcielI) -**Additional informations:** +Compare video frames, checking how many pixels changed. -* initRecorder: initialise the recorder with an arbitrary codec it can be changed with problems -* in the run method no motion can be detected in the first 5 second because it is almost the time needed for the webcam to adjust the focus and the luminosity which imply lot's of changes on the image -* processImage: contains all the images operations applied to the image -* somethingHasMoved: The image iteration to count black pixels is contained in this method - - -The result of applying it can be seen here: https://www.youtube.com/watch?v=-RUu3EcielI +## The smart way ## +[![https://www.youtube.com/watch?v=sRIdyfh3054](https://img.youtube.com/vi/sRIdyfh3054/0.jpg)](https://www.youtube.com/watch?v=sRIdyfh3054) -## The smart way ## +Detect moving objects and calculate their surface area. -Iis way to operate is less trivial than the previous one, but the results are identical if not more accurate in the previous method. I inspired myself of the [Motion-tracker]("https://github.com/mattwilliamson/Motion-Tracker/") by Matt Williamson for the operations and filters to apply on the image but all the rest is different. The idea in this method is to find the contours of the moving objects and calculate the area of all of them. Then the average of the surface changing is compared with the total surface of the image and the alarm is triggered if it exceed the given threshold. Note the code shown below does not implement the recording system as it is the case on the previous example, but it can be made easily. +## More info ## -The result of applying it can be seen here: https://www.youtube.com/watch?v=sRIdyfh3054 +* usually takes about 5 seconds to start up +* initRecorder: you can switch the output video format here +* somethingHasMoved: pixel comparison +* processImage: the fun stuff! diff --git a/motionDetectionCV3.py b/motionDetectionCV3.py new file mode 100644 index 0000000..446871b --- /dev/null +++ b/motionDetectionCV3.py @@ -0,0 +1,63 @@ +# coding=utf-8 +""" +Webcam motion detection +""" +import cv2 +import numpy as np +import pygame + +THRESHOLD = 40 +camera = cv2.VideoCapture(0) + +es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,4)) +kernel = np.ones((5,5), np.uint8) +background = None + +# Write test video +fps = 20 #camera.get(cv2.CAP_PROP_FPS) +pygame.mixer.init() +cameraSound = pygame.mixer.Sound("snapshotsound.ogg") +size = (int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)), + int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))) +videoWriter = cv2.VideoWriter('basic_motion_detection.avi', + cv2.VideoWriter_fourcc('D', 'I', 'V', 'X'), + fps, size) + +while (True): + ret, frame = camera.read() + # The first frame as the background + if background is None: + background = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + background = cv2.GaussianBlur(background, (21,21), 0) + continue + + gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + gray_frame = cv2.GaussianBlur(gray_frame, (21,21), 0) + + # Compare the difference between each frame of image and the background + #print(background.shape, gray_frame.shape) + diff = cv2.absdiff(background, gray_frame) + diff = cv2.threshold(diff, THRESHOLD, 255, cv2.THRESH_BINARY)[1] + diff = cv2.dilate(diff, es, iterations=2) + # Calculate the outline of the target in the image + image, cnts, hierarchy = cv2.findContours(diff.copy(), + cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + print ("Detecting " + str(len(cnts)) + " Moving Objects") + if len(cnts) > 0: + cameraSound.play() + + for c in cnts: + if cv2.contourArea(c) < 1500: + continue + # Calculate the bounding box + (x, y, w, h) = cv2.boundingRect(c) + cv2.rectangle(frame, (x,y), (x+w, y+h), (0,255,0), 2) + + cv2.imshow("contours", frame) + videoWriter.write(frame) + cv2.imshow("dif", diff) + # cv2.imwrite('didff.jpg', diff) + if cv2.waitKey(int(1000/12)) &0xff == ord('q'): + break +cv2.destroyAllWindows() +camera.release() diff --git a/snapshotsound.ogg b/snapshotsound.ogg new file mode 100644 index 0000000..52512ac Binary files /dev/null and b/snapshotsound.ogg differ diff --git a/soundDetection.py b/soundDetection.py new file mode 100644 index 0000000..7dc0d99 --- /dev/null +++ b/soundDetection.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +import pyaudio +from numpy import zeros,linspace,short,fromstring,hstack,transpose,log +from scipy import fft +import time +import ctypes + +#Volume Sensitivity, 0.05: Extremely Sensitive, may give false alarms +# 0.1: Probably Ideal volume +# 1: Poorly sensitive, will only go off for relatively loud +SENSITIVITY= 1.0 +# Alarm frequencies (Hz) to detect (Use audacity to record a wave and then do Analyze->Plot Spectrum) +TONE = 1593 +#Bandwidth for detection (i.e., detect frequencies within this margin of error of the TONE) +BANDWIDTH = 30 +#How many 46ms blips before we declare a beep? (Take the beep length in ms, divide by 46ms, subtract a bit) +beeplength=2 +# How many beeps before we declare an alarm? +alarmlength=2 +# How many false 46ms blips before we declare the alarm is not ringing +resetlength=10 +# How many reset counts until we clear an active alarm? +clearlength=30 +# Enable blip, beep, and reset debug output +debug=False +# Show the most intense frequency detected (useful for configuration) +frequencyoutput=False + + +#Set up audio sampler - +NUM_SAMPLES = 2048 +SAMPLING_RATE = 44100 +pa = pyaudio.PyAudio() +_stream = pa.open(format=pyaudio.paInt16, + channels=1, rate=SAMPLING_RATE, + input=True, + frames_per_buffer=NUM_SAMPLES) + +def addToLog(appendText): + print (appendText) + with open("log.txt", "a") as myfile: + myfile.write("\n"+appendText) + +#ctypes.windll.user32.LockWorkStation() +addToLog ("Listening for "+str(alarmlength)+" beeps of "+str(beeplength * 46)+"ms at "+str(TONE)+"Hz") + +blipcount=0 +beepcount=0 +resetcount=0 +clearcount=0 +alarm=False + +while True: + while _stream.get_read_available()< NUM_SAMPLES: time.sleep(0.01) + audio_data = fromstring(_stream.read( + _stream.get_read_available()), dtype=short)[-NUM_SAMPLES:] + # Each data point is a signed 16 bit number, so we can normalize by dividing 32*1024 + normalized_data = audio_data / 32768.0 + intensity = abs(fft(normalized_data))[:int(NUM_SAMPLES/2)] + frequencies = linspace(0.0, float(SAMPLING_RATE)/2, num=NUM_SAMPLES/2) + if frequencyoutput: + which = intensity[1:].argmax()+1 + # use quadratic interpolation around the max + if which != len(intensity)-1: + y0,y1,y2 = log(intensity[which-1:which+2:]) + x1 = (y2 - y0) * .5 / (2 * y1 - y2 - y0) + # find the frequency and output it + thefreq = (which+x1)*SAMPLING_RATE/NUM_SAMPLES + else: + thefreq = which*SAMPLING_RATE/NUM_SAMPLES + if debug: print ("\t\t\t\tfreq=",thefreq) + if max(intensity[(frequencies < TONE+BANDWIDTH) & (frequencies > TONE-BANDWIDTH )]) > max(intensity[(frequencies < TONE-1000) & (frequencies > TONE-2000)]) + SENSITIVITY: + blipcount+=1 + resetcount=0 + if debug: print ("\t\tBlip",blipcount) + if (blipcount>=beeplength): + blipcount=0 + resetcount=0 + beepcount+=1 + if debug: print ("\tBeep",beepcount) + if (beepcount>=alarmlength): + if not alarm: + datetime = time.strftime('%Y-%m-%d %H:%M:%S') + humantime = time.strftime('%I:%M:%S %p %Z') + addToLog ("Alarm triggered at "+datetime+" ("+humantime+")") + clearcount=0 + alarm=True + if debug: print ("Alarm!") + beepcount=0 + else: + blipcount=0 + resetcount+=1 + if debug: print ("\t\t\treset",resetcount) + if (resetcount>=resetlength): + resetcount=0 + beepcount=0 + if alarm: + clearcount+=1 + if debug: print ("\t\tclear",clearcount) + if clearcount>=clearlength: + clearcount=0 + addToLog ("Listening...") + alarm=False + else: + if debug: print ("No alarm") + time.sleep(0.01)