├── MotionDetector.py ├── MotionDetectorContours.py └── README.md /MotionDetector.py: -------------------------------------------------------------------------------- 1 | import cv2.cv as cv 2 | from datetime import datetime 3 | import time 4 | 5 | class MotionDetectorInstantaneous(): 6 | 7 | def onChange(self, val): #callback when the user change the detection threshold 8 | self.threshold = val 9 | 10 | def __init__(self,threshold=8, doRecord=True, showWindows=True): 11 | self.writer = None 12 | self.font = None 13 | self.doRecord=doRecord #Either or not record the moving object 14 | self.show = showWindows #Either or not show the 2 windows 15 | self.frame = None 16 | 17 | self.capture=cv.CaptureFromCAM(0) 18 | self.frame = cv.QueryFrame(self.capture) #Take a frame to init recorder 19 | if doRecord: 20 | self.initRecorder() 21 | 22 | self.frame1gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t-1 23 | cv.CvtColor(self.frame, self.frame1gray, cv.CV_RGB2GRAY) 24 | 25 | #Will hold the thresholded result 26 | self.res = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) 27 | 28 | self.frame2gray = cv.CreateMat(self.frame.height, self.frame.width, cv.CV_8U) #Gray frame at t 29 | 30 | self.width = self.frame.width 31 | self.height = self.frame.height 32 | self.nb_pixels = self.width * self.height 33 | self.threshold = threshold 34 | self.isRecording = False 35 | self.trigger_time = 0 #Hold timestamp of the last detection 36 | 37 | if showWindows: 38 | cv.NamedWindow("Image") 39 | cv.CreateTrackbar("Detection treshold: ", "Image", self.threshold, 100, self.onChange) 40 | 41 | def initRecorder(self): #Create the recorder 42 | codec = cv.CV_FOURCC('M', 'J', 'P', 'G') #('W', 'M', 'V', '2') 43 | self.writer=cv.CreateVideoWriter(datetime.now().strftime("%b-%d_%H_%M_%S")+".wmv", codec, 5, cv.GetSize(self.frame), 1) 44 | #FPS set to 5 because it seems to be the fps of my cam but should be ajusted to your needs 45 | self.font = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 1, 1, 0, 2, 8) #Creates a font 46 | 47 | def run(self): 48 | started = time.time() 49 | while True: 50 | 51 | curframe = cv.QueryFrame(self.capture) 52 | instant = time.time() #Get timestamp o the frame 53 | 54 | self.processImage(curframe) #Process the image 55 | 56 | if not self.isRecording: 57 | if self.somethingHasMoved(): 58 | self.trigger_time = instant #Update the trigger_time 59 | if instant > started +5:#Wait 5 second after the webcam start for luminosity adjusting etc.. 60 | print datetime.now().strftime("%b %d, %H:%M:%S"), "Something is moving !" 61 | if self.doRecord: #set isRecording=True only if we record a video 62 | self.isRecording = True 63 | else: 64 | if instant >= self.trigger_time +10: #Record during 10 seconds 65 | print datetime.now().strftime("%b %d, %H:%M:%S"), "Stop recording" 66 | self.isRecording = False 67 | else: 68 | cv.PutText(curframe,datetime.now().strftime("%b %d, %H:%M:%S"), (25,30),self.font, 0) #Put date on the frame 69 | cv.WriteFrame(self.writer, curframe) #Write the frame 70 | 71 | if self.show: 72 | cv.ShowImage("Image", curframe) 73 | cv.ShowImage("Res", self.res) 74 | 75 | cv.Copy(self.frame2gray, self.frame1gray) 76 | c=cv.WaitKey(1) % 0x100 77 | if c==27 or c == 10: #Break if user enters 'Esc'. 78 | break 79 | 80 | def processImage(self, frame): 81 | cv.CvtColor(frame, self.frame2gray, cv.CV_RGB2GRAY) 82 | 83 | #Absdiff to get the difference between to the frames 84 | cv.AbsDiff(self.frame1gray, self.frame2gray, self.res) 85 | 86 | #Remove the noise and do the threshold 87 | cv.Smooth(self.res, self.res, cv.CV_BLUR, 5,5) 88 | cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_OPEN) 89 | cv.MorphologyEx(self.res, self.res, None, None, cv.CV_MOP_CLOSE) 90 | cv.Threshold(self.res, self.res, 10, 255, cv.CV_THRESH_BINARY_INV) 91 | 92 | def somethingHasMoved(self): 93 | nb=0 #Will hold the number of black pixels 94 | min_threshold = (self.nb_pixels/100) * self.threshold #Number of pixels for current threshold 95 | nb = self.nb_pixels - cv.CountNonZero(self.res) 96 | if (nb) > min_threshold: 97 | return True 98 | else: 99 | return False 100 | 101 | if __name__=="__main__": 102 | detect = MotionDetectorInstantaneous(doRecord=True) 103 | detect.run() 104 | -------------------------------------------------------------------------------- /MotionDetectorContours.py: -------------------------------------------------------------------------------- 1 | import cv2.cv as cv 2 | from datetime import datetime 3 | import time 4 | 5 | class MotionDetectorAdaptative(): 6 | 7 | def onChange(self, val): #callback when the user change the detection threshold 8 | self.threshold = val 9 | 10 | def __init__(self,threshold=25, doRecord=True, showWindows=True): 11 | self.writer = None 12 | self.font = None 13 | self.doRecord=doRecord #Either or not record the moving object 14 | self.show = showWindows #Either or not show the 2 windows 15 | self.frame = None 16 | 17 | self.capture=cv.CaptureFromCAM(0) 18 | self.frame = cv.QueryFrame(self.capture) #Take a frame to init recorder 19 | if doRecord: 20 | self.initRecorder() 21 | 22 | self.gray_frame = cv.CreateImage(cv.GetSize(self.frame), cv.IPL_DEPTH_8U, 1) 23 | self.average_frame = cv.CreateImage(cv.GetSize(self.frame), cv.IPL_DEPTH_32F, 3) 24 | self.absdiff_frame = None 25 | self.previous_frame = None 26 | 27 | self.surface = self.frame.width * self.frame.height 28 | self.currentsurface = 0 29 | self.currentcontours = None 30 | self.threshold = threshold 31 | self.isRecording = False 32 | self.trigger_time = 0 #Hold timestamp of the last detection 33 | 34 | if showWindows: 35 | cv.NamedWindow("Image") 36 | cv.CreateTrackbar("Detection treshold: ", "Image", self.threshold, 100, self.onChange) 37 | 38 | def initRecorder(self): #Create the recorder 39 | codec = cv.CV_FOURCC('M', 'J', 'P', 'G') 40 | self.writer=cv.CreateVideoWriter(datetime.now().strftime("%b-%d_%H_%M_%S")+".wmv", codec, 5, cv.GetSize(self.frame), 1) 41 | #FPS set to 5 because it seems to be the fps of my cam but should be ajusted to your needs 42 | self.font = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 1, 1, 0, 2, 8) #Creates a font 43 | 44 | def run(self): 45 | started = time.time() 46 | while True: 47 | 48 | currentframe = cv.QueryFrame(self.capture) 49 | instant = time.time() #Get timestamp o the frame 50 | 51 | self.processImage(currentframe) #Process the image 52 | 53 | if not self.isRecording: 54 | if self.somethingHasMoved(): 55 | self.trigger_time = instant #Update the trigger_time 56 | if instant > started +10:#Wait 5 second after the webcam start for luminosity adjusting etc.. 57 | print "Something is moving !" 58 | if self.doRecord: #set isRecording=True only if we record a video 59 | self.isRecording = True 60 | cv.DrawContours (currentframe, self.currentcontours, (0, 0, 255), (0, 255, 0), 1, 2, cv.CV_FILLED) 61 | else: 62 | if instant >= self.trigger_time +10: #Record during 10 seconds 63 | print "Stop recording" 64 | self.isRecording = False 65 | else: 66 | cv.PutText(currentframe,datetime.now().strftime("%b %d, %H:%M:%S"), (25,30),self.font, 0) #Put date on the frame 67 | cv.WriteFrame(self.writer, currentframe) #Write the frame 68 | 69 | if self.show: 70 | cv.ShowImage("Image", currentframe) 71 | 72 | c=cv.WaitKey(1) % 0x100 73 | if c==27 or c == 10: #Break if user enters 'Esc'. 74 | break 75 | 76 | def processImage(self, curframe): 77 | cv.Smooth(curframe, curframe) #Remove false positives 78 | 79 | if not self.absdiff_frame: #For the first time put values in difference, temp and moving_average 80 | self.absdiff_frame = cv.CloneImage(curframe) 81 | self.previous_frame = cv.CloneImage(curframe) 82 | cv.Convert(curframe, self.average_frame) #Should convert because after runningavg take 32F pictures 83 | else: 84 | cv.RunningAvg(curframe, self.average_frame, 0.05) #Compute the average 85 | 86 | cv.Convert(self.average_frame, self.previous_frame) #Convert back to 8U frame 87 | 88 | cv.AbsDiff(curframe, self.previous_frame, self.absdiff_frame) # moving_average - curframe 89 | 90 | cv.CvtColor(self.absdiff_frame, self.gray_frame, cv.CV_RGB2GRAY) #Convert to gray otherwise can't do threshold 91 | cv.Threshold(self.gray_frame, self.gray_frame, 50, 255, cv.CV_THRESH_BINARY) 92 | 93 | cv.Dilate(self.gray_frame, self.gray_frame, None, 15) #to get object blobs 94 | cv.Erode(self.gray_frame, self.gray_frame, None, 10) 95 | 96 | 97 | def somethingHasMoved(self): 98 | 99 | # Find contours 100 | storage = cv.CreateMemStorage(0) 101 | contours = cv.FindContours(self.gray_frame, storage, cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_SIMPLE) 102 | 103 | self.currentcontours = contours #Save contours 104 | 105 | while contours: #For all contours compute the area 106 | self.currentsurface += cv.ContourArea(contours) 107 | contours = contours.h_next() 108 | 109 | avg = (self.currentsurface*100)/self.surface #Calculate the average of contour area on the total size 110 | self.currentsurface = 0 #Put back the current surface to 0 111 | 112 | if avg > self.threshold: 113 | return True 114 | else: 115 | return False 116 | 117 | 118 | if __name__=="__main__": 119 | detect = MotionDetectorAdaptative(doRecord=True) 120 | detect.run() 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Motion-detection-OpenCV 2 | ======================= 3 | 4 | Python/OpenCV script that detect motion on webcam and allow record it to a file. 5 | 6 | ## The simple way ## 7 | 8 | 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. 9 | 10 | **Additional informations:** 11 | 12 | * initRecorder: initialise the recorder with an arbitrary codec it can be changed with problems 13 | * 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 14 | * processImage: contains all the images operations applied to the image 15 | * somethingHasMoved: The image iteration to count black pixels is contained in this method 16 | 17 | 18 | The result of applying it can be seen here: https://www.youtube.com/watch?v=-RUu3EcielI 19 | 20 | 21 | ## The smart way ## 22 | 23 | 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. 24 | 25 | The result of applying it can be seen here: https://www.youtube.com/watch?v=sRIdyfh3054 26 | --------------------------------------------------------------------------------