Skip to content
201 changes: 103 additions & 98 deletions MotionDetector.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion MotionDetectorContours.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -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!
63 changes: 63 additions & 0 deletions motionDetectionCV3.py
Original file line number Diff line number Diff line change
@@ -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()
Binary file added snapshotsound.ogg
Binary file not shown.
Loading