Tutorial 17: Importing Motion Detection
Moving forward we will be creating our own Python 3 scripts for running our home automation security system. We will therefore need to import our motion detection script and use it in the future scripts we write.
In this tutorial I walk you through how to modify the motion detection script such that we can call on it, and how we can use it in a separate Python 3 script.
This tutorial is a little older (published in 2015!). The main principles are still relevant, so I haven't needed to update this lesson.
- How to import a python file as a module in our script
- How to take a picture in Python 3 when motion is detected
- How to execute a python script from the terminal
#!/usr/bin/python # original script by brainflakes, improved by pageauc, peewee2 and Kesthal # www.raspberrypi.org/phpBB3/viewtopic.php?f=43&t=45235 # You need to install PIL to run this script # type "sudo apt-get install python-imaging-tk" in an terminal window to do this # StringIO is not available in Python 3 import io #import StringIO import subprocess import os import time from datetime import datetime from PIL import Image # Motion detection settings: # Threshold - how much a pixel has to change by to be marked as "changed" # Sensitivity - how many changed pixels before capturing an image, needs to be higher if noisy view # ForceCapture - whether to force an image to be captured every forceCaptureTime seconds, values True or False # filepath - location of folder to save photos # filenamePrefix - string that prefixes the file name for easier identification of files. # diskSpaceToReserve - Delete oldest images to avoid filling disk. How much byte to keep free on disk. # cameraSettings - "" = no extra settings; "-hf" = Set horizontal flip of image; "-vf" = Set vertical flip; "-hf -vf" = both horizontal and vertical flip threshold = 10 sensitivity = 20 forceCapture = True forceCaptureTime = 60 * 60 # Once an hour filepath = "/home/pi/picam" filenamePrefix = "capture" diskSpaceToReserve = 40 * 1024 * 1024 # Keep 40 mb free on disk cameraSettings = "" # settings of the photos to save saveWidth = 1296 saveHeight = 972 saveQuality = 15 # Set jpeg quality (0 to 100) # Test-Image settings testWidth = 100 testHeight = 75 # this is the default setting, if the whole image should be scanned for changed pixel testAreaCount = 1 testBorders = [ [[1,testWidth],[1,testHeight]] ] # [ [[start pixel on left side,end pixel on right side],[start pixel on top side,stop pixel on bottom side]] ] # testBorders are NOT zero-based, the first pixel is 1 and the last pixel is testWith or testHeight # with "testBorders", you can define areas, where the script should scan for changed pixel # for example, if your picture looks like this: # # ....XXXX # ........ # ........ # # "." is a street or a house, "X" are trees which move arround like crazy when the wind is blowing # because of the wind in the trees, there will be taken photos all the time. to prevent this, your setting might look like this: # testAreaCount = 2 # testBorders = [ [[1,50],[1,75]], [[51,100],[26,75]] ] # area y=1 to 25 not scanned in x=51 to 100 # even more complex example # testAreaCount = 4 # testBorders = [ [[1,39],[1,75]], [[40,67],[43,75]], [[68,85],[48,75]], [[86,100],[41,75]] ] # in debug mode, a file debug.bmp is written to disk with marked changed pixel an with marked border of scan-area # debug mode should only be turned on while testing the parameters above debugMode = False # False or True # Capture a small test image (for motion detection) def captureTestImage(settings, width, height): command = "raspistill %s -w %s -h %s -t 200 -e bmp -n -o -" % (settings, width, height) #imageData = StringIO.StringIO() imageData = io.BytesIO() imageData.write(subprocess.check_output(command, shell=True)) imageData.seek(0) im = Image.open(imageData) buffer = im.load() imageData.close() return im, buffer # Save a full size image to disk def saveImage(settings, width, height, quality, diskSpaceToReserve): keepDiskSpaceFree(diskSpaceToReserve) time = datetime.now() filename = filepath + "/" + filenamePrefix + "-%04d%02d%02d-%02d%02d%02d.jpg" % (time.year, time.month, time.day, time.hour, time.minute, time.second) subprocess.call("raspistill %s -w %s -h %s -t 200 -e jpg -q %s -n -o %s" % (settings, width, height, quality, filename), shell=True) print("Captured %s" % filename) # Keep free space above given level def keepDiskSpaceFree(bytesToReserve): if (getFreeSpace() < bytesToReserve): for filename in sorted(os.listdir(filepath + "/")): if filename.startswith(filenamePrefix) and filename.endswith(".jpg"): os.remove(filepath + "/" + filename) print("Deleted %s/%s to avoid filling disk" % (filepath,filename)) if (getFreeSpace() > bytesToReserve): return # Get available disk space def getFreeSpace(): st = os.statvfs(filepath + "/") du = st.f_bavail * st.f_frsize return du def motion(): # Get first image image1, buffer1 = captureTestImage(cameraSettings, testWidth, testHeight) # Reset last capture time lastCapture = time.time() while (True): # Get comparison image image2, buffer2 = captureTestImage(cameraSettings, testWidth, testHeight) # Count changed pixels changedPixels = 0 takePicture = False if (debugMode): # in debug mode, save a bitmap-file with marked changed pixels and with visible testarea-borders debugimage = Image.new("RGB",(testWidth, testHeight)) debugim = debugimage.load() for z in range(0, testAreaCount): # = xrange(0,1) with default-values = z will only have the value of 0 = only one scan-area = whole picture for x in range(testBorders[z]-1, testBorders[z]): # = xrange(0,100) with default-values for y in range(testBorders[z]-1, testBorders[z]): # = xrange(0,75) with default-values; testBorders are NOT zero-based, buffer1[x,y] are zero-based (0,0 is top left of image, testWidth-1,testHeight-1 is botton right) if (debugMode): debugim[x,y] = buffer2[x,y] if ((x == testBorders[z]-1) or (x == testBorders[z]-1) or (y == testBorders[z]-1) or (y == testBorders[z]-1)): # print "Border %s %s" % (x,y) debugim[x,y] = (0, 0, 255) # in debug mode, mark all border pixel to blue # Just check green channel as it's the highest quality channel pixdiff = abs(buffer1[x,y] - buffer2[x,y]) if pixdiff > threshold: changedPixels += 1 if (debugMode): debugim[x,y] = (0, 255, 0) # in debug mode, mark all changed pixel to green # Save an image if pixels changed if (changedPixels > sensitivity): takePicture = True # will shoot the photo later if ((debugMode == False) and (changedPixels > sensitivity)): break # break the y loop if ((debugMode == False) and (changedPixels > sensitivity)): break # break the x loop if ((debugMode == False) and (changedPixels > sensitivity)): break # break the z loop if (debugMode): debugimage.save(filepath + "/debug.bmp") # save debug image as bmp print("debug.bmp saved, %s changed pixel" % changedPixels) # else: # print "%s changed pixel" % changedPixels # Check force capture if forceCapture: if time.time() - lastCapture > forceCaptureTime: takePicture = True if takePicture: lastCapture = time.time() return True #saveImage(cameraSettings, saveWidth, saveHeight, saveQuality, diskSpaceToReserve) else: return False # Swap comparison buffers image1 = image2 buffer1 = buffer2
I also write a second script called cookieMain.py
import P3picam import picamera from datetime import datetime from subprocess import call motionState = False picPath = "/home/pi/Desktop/cookie/images/" def captureImage(currentTime, picPath): # Generate the picture's name picName = currentTime.strftime("%Y.%m.%d-%H%M%S") + '.jpg' with picamera.PiCamera() as camera: camera.resolution = (1280, 720) camera.capture(picPath + picName) print("We have taken a picture.") return picName def getTime(): # Fetch the current time currentTime = datetime.now() return currentTime def timeStamp(currentTime, picPath, picName): # Variable for file path filepath = picPath + picName # Create message to stamp on picture message = currentTime.strftime("%Y.%m.%d - %H:%M:%S") # Create command to execute timestampCommand = "/usr/bin/convert " + filepath + " -pointsize 36 \ -fill red -annotate +700+650 '" + message + "' " + filepath # Execute the command call([timestampCommand], shell=True) print("We have timestamped our picture.") while True: motionState = P3picam.motion() print(motionState) if motionState: currentTime = getTime() picName = captureImage(currentTime, picPath) timeStamp(currentTime, picPath, picName)