DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world
Python Motion Detection Library + Demo
// Python Motion Detection Library + Demo
// 3 files (1 lib, 1 demo, 1 misc)
// Demo is win32-only because of win32 only library for webcam access (easily changeable)
// Can be used for eyetoy or wiimote (IR emitter+wireless mouse hack) like input
#######
#FILE 1: LIBRARY motion.py
#######
# -*- coding: cp1252 -*-
####################################
#MOTION: compare images for change#
####################################
# Copyright (C) 2007 Erol Baykal
# License: GPL
#AUTHOR:
# Baykal Erol
# erol@baykal.be
# baykal.erol@gmail.com
#
#LAST UPDATED 6 SEP 07
#Version: RC1
##################################
#MODULES
########
import Image, ImageDraw, pygame
from math import fabs, sqrt, pi, atan2
#FUNCTIONS
##########
"""
getMotionArray returns an array containing the (pixelwise) X-Y coordinates of the difference between the two compared images
If the two images are identical an empty array will be returned.
"""
def getMotionArray(image1, image2, threshold = 10):
i1 = image1
i2 = image2
#both images need to be the same size in pixels
if (i1.size[0] != i2.size[0]) or (i1.size[1] != i2.size[1]):
return 0 #if not, we return 0
size = i1.size
imgArr1 = list(i1.getdata())
imgArr2 = list(i2.getdata())
t = threshold
motionarray = [] #2D array to store motion areas
i=0
while i < len(imgArr1): #scan through the images
p1 = imgArr1[i]
p2 = imgArr2[i]
if (fabs(p1[0]-p2[0]) > t) or (fabs(p1[1]-p2[1]) > t) or (fabs(p1[2]-p2[2]) > t): #compare each pixel in R,G,B channel
y = i/size[0]
x = i - y*size[0]
motionarray.append((x,y))
i = i+1
return motionarray
def getMotionArrayRGB(image1, image2, threshold = 10):
i1 = image1
i2 = image2
#both images need to be the same size in pixels
if (i1.size[0] != i2.size[0]) or (i1.size[1] != i2.size[1]):
return 0 #if not, we return 0
size = i1.size
imgArr1 = list(i1.getdata())
imgArr2 = list(i2.getdata())
t = threshold
motionarray = [] #2D array to store motion areas
i=0
while i < len(imgArr1): #scan through the images
p1 = imgArr1[i]
p2 = imgArr2[i]
if (fabs(p1[0]-p2[0]) > t) or (fabs(p1[1]-p2[1]) > t) or (fabs(p1[2]-p2[2]) > t): #compare each pixel in R,G,B channel
y = i/size[0]
x = i - y*size[0]
motionarray.append((x,y,p2))
i = i+1
return motionarray
"""
getMotionPoint returns the avarage point of all the motion.
It does this by calculating the medial point for a motion array from getMotionArray
To normalise this point divide pX/imageSizeX and pY/imageSizeY
"""
def getMotionPoint(array):
area = array
i=0
pX = 0
pY = 0
point=(-1,-1)
while i < len(area):
pX = pX+area[i][0]
pY = pY+area[i][1]
i = i+1
if i > 0:
point = (float(pX)/i,float(pY)/i)
return point
def getAvgColor(array):
area = array
i=0
R = 0
G = 0
B = 0
while i < len(area):
R = R+area[i][2][0]
G = G+area[i][2][1]
B = B+area[i][2][2]
i = i+1
if i > 0:
color = (int(R/i),int(G/i),int(B/i))
return color
"""
By examining motion points from two consecutive frames we can detect swing motions.
Swing motions occur when there is enough distance between points
"""
def getSwingMotion(point1, point2, treshold=2):
p1 = point1
p2 = point2
#PYTHAGORAS :) A² = B²+C²
B = p1[0] - p2[0]
C = p1[1] - p2[1]
A = sqrt((B*B)+(C*C))
if A > treshold:
magnitude = A
vector = (B,C) #vector
heading = atan2(B,C)*180/pi
return (magnitude,vector,heading)
else:
return 0
"""
Splits coordinates into groups, where each group consists of adjecent coordinates
Receives an array of coordinates ([(x,y),(x,y)]) and retuns an array of array of coordinates ([[(x,y),(x,y)],[(x,y),(x,y)]])
"""
def splitGroups(arr):
arrGroups= []
while len(arr) > 1:
temparr=[arr[0]]
i = 0
arr.remove(temparr[0]) #bite off the head of the array ^^
while i < len(temparr):
t = checkNeighbours(temparr[i], arr)
if t:
for co in t:
temparr.append(co)
arr.remove(co)
i=i+1
arrGroups.append(temparr)
return arrGroups
"""
takes a coordinate and an array of coordinates. If any neighbouring coordinates of the given coordinate are found in the array, they are returned
"""
def checkNeighbours(co,arr):
temp = []
x = co[0]
y = co[1]
neighbours = [(x-1,y-1),(x-1,y),(x-1,y+1),(x,y-1),(x,y+1),(x+1,y-1),(x+1,y),(x+1,y+1)]
for i in arr:
if (i[0],i[1]) in neighbours:
temp.append(i)
if len(temp) > 0:
return temp
else:
return 0
#DEPRECATED
def getMotionArrayOLD(image1, image2, threshold = 10):
i1 = image1
i2 = image2
#both images need to be the same size in pixels
if (i1.size[0] != i2.size[0]) or (i1.size[1] != i2.size[1]):
return 0 #if not, we return 0
size = i1.size
t = threshold
motionarray = [] #2D array to store motion areas
i=0
while i in range(size[1]): #scan through the images
j=0
while j in range(size[0]):
p1 = i1.getpixel((j,i))
p2 = i2.getpixel((j,i))
if (fabs(p1[0]-p2[0]) > t) or (fabs(p1[1]-p2[1]) > t) or (fabs(p1[2]-p2[2]) > t): #compare each pixel in R,G,B channel
motionarray.append((j,i))
j = j+1
i = i+1
return motionarray
def getMotionArrayRGBOLD(image1, image2, treshold = 10):
i1 = image1
i2 = image2
#both images need to be the same size in pixels
if (i1.size[0] != i2.size[0]) or (i1.size[1] != i2.size[1]):
return 0 #if not, we return 0
size = i1.size
t = treshold
motionarray = [] #2D array to store motion areas
i=0
while i in range(size[1]): #scan through the images
j=0
while j in range(size[0]):
p1 = i1.getpixel((j,i))
p2 = i2.getpixel((j,i))
if (fabs(p1[0]-p2[0]) > t) or (fabs(p1[1]-p2[1]) > t) or (fabs(p1[2]-p2[2]) > t): #compare each pixel in R,G,B channel
motionarray.append((j,i,p2))#by deducting these values we mirror the coordinates to mimick a mirror-image
#also return p2; the RGB value for the pixel from the second (newer) image
j = j+1
i = i+1
return motionarray
def compare(image1, image2, treshold = 10, showPixels=0):
#showPixels
# if 0: returns bunding box ((x1,y1),(x2,y2)) encompassing all motion
# if 1: returns motion mask:
# white image with same dimensions of supplied images, where motion areas are marked with a color
# if 2: returns motion mask:
# white image with same dimensions as supplied images, where motion areas are real pixels of supplied images
# if 3: returns motion array:
# an array of points, each representing an area of motion on the images
#images need to have same dimensions
i1 = image1
i2 = image2
#if not, we return 0
if (i1.size[0] != i2.size[0]) or (i1.size[1] != i2.size[1]):
return 0
size = i1.size
t = treshold
#2D array to store motion areas
motionarray = []
#new empty image for motionmask, only create if motion mask is needed
if (showPixels == 1) or (showPixels == 2):
ni = Image.new("RGB",i1.size,(0,0,0))
draw = ImageDraw.Draw(ni)
i=0
#variables for second boundingbox system
first = 1
lX = 0
hX = 0
lY = 0
hY = 0
while i in range(size[1]):
j=0
while j in range(size[0]):
p1 = i1.getpixel((j,i))
p2 = i2.getpixel((j,i))
if (fabs(p1[0]-p2[0]) > t) or (fabs(p1[1]-p2[1]) > t) or (fabs(p1[2]-p2[2]) > t):
if showPixels == 1:
ni.putpixel((j,i),(255,0,0))
elif showPixels == 2:
ni.putpixel((j,i),p2)
elif showPixels == 3:
motionarray.append((j,i))
else:
if first:
lX = j
lY = i
first = 0
if j < lX:
lX = j
if j > hX:
hX = j
if i < lY:
lY = i
if i > hY:
hY = i
j = j+1
i = i+1
area = ((lX,lY),(hX,hY))
if showPixels == 1:
del draw
return ni
elif showPixels == 2:
del draw
return ni
elif showPixels == 3:
return motionarray
else:
return area
#######
#FILE 2: BASIC IR-DEMO (hack an IR-webcam and IR emitter for optimal experience)
#######
##################################
#BASIC DEMO:
#Demonstrates basic ability of IRM: get motion data and translate it into motion point coordinates
##################################
# Copyright (C) 2007 Erol Baykal
# License: GPL
#ONLY WORKS ON WINDOWS, this is due to videocapture being windows 32 only
#NEEDS:
# PIL (python image library http://www.pythonware.com/products/pil/)
# Pygame (http://www.pygame.org/)
# Videocapture (http://videocapture.sourceforge.net/)
# Psyco (http://psyco.sourceforge.net/)
#AUTHOR:
# Baykal Erol (erol@baykal.be)
#LAST UPDATE: 6 SEP 07
##################################
#
#MODULES
########
import Image, ImageDraw, motion, time, pygame, sys, random, psyco, performance
from VideoCapture import Device
#GLOBAL VARIABLES
#################
#options
########
FULLSCREEN = 1
COLORTHRESHOLD = 60 #color threshold for motion detection, the higher the more strict (avg. 50 is ok)
SIZE = 320,240 # resolution of the screen (and camera, but can be seperated), needs to be 4/3 (I think)
CSIZE = 160,120 #compressed size, needs to be 4/3 (widescreen hack?)
#init vars
##########
psyco.full() #PSYCO speeds up python
pygame.init() #We need to initialize pygame early on, so that certain stuff works (loading sound..)
ratio = SIZE[0]/CSIZE[0] #ratio of compression
fps = performance.FpsMeter() #an FPS meter
font = pygame.font.Font(None, 36) #a font for writing :)
#select cam and set resolution
cam = Device(devnum=0)
cam.setResolution(SIZE[0],SIZE[1])
#the pygame screen
if FULLSCREEN:
screen = pygame.display.set_mode(SIZE, pygame.FULLSCREEN)
else:
screen = pygame.display.set_mode(SIZE)
#new empty images, we will be using them in the main loop
#ni = newest image
#nci = newest compressed image
#oci = older compressed image
cci = Image.new("RGB",CSIZE,(255,255,255))
ni = Image.new("RGB",SIZE,(255,255,255))
oci = cci
tracklist = [(10,10)]
#a new surface the size of our window, will serve as a bg
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
#################################################
#MAIN LOOP
##########
do = 1
while do:
for event in pygame.event.get():
if event.type == pygame.QUIT:
do = 0
del cam
sys.exit()
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
do = 0
del cam
sys.exit()
#Get the images
###############
#oci and nci are used for motion detection, they don't show up on screen
#if you want to show something use ni
oci = cci #make the current compressed image the old one
ni = cam.getImage() #get new image for compression
ni = ni.transpose(Image.FLIP_LEFT_RIGHT) #Flip image for mirror movement, this way topleft == (0,0)
cci = ni.resize(CSIZE,Image.BILINEAR) #compress the new image and make it the current one
#Get motion from images
#######################
motionArray = motion.getMotionArray(oci,cci,COLORTHRESHOLD) #compare the images and get the array of pixels with difference
motionPoint = motion.getMotionPoint(motionArray) #calculate the avarage point
if motionPoint[0] > 0 and motionPoint[1] > 0: #chek X and Y values
#if a real avarage point of motion has been returned
wp = motionPoint
px = wp[0]*ratio #multiply by compression ratio to get the position
py = wp[1]*ratio #of the coordinate on the uncompressed image
wp = (px,py)
if len(tracklist) < 240: #we keep track of some frames' avarage points
tracklist.append(wp)
else:
tracklist.pop(0)
tracklist.append(wp)
#Draw everything
#################
background.fill((255,255,255)) #clear the background
pygame.draw.lines(background, (0,255,0), 0, tracklist)
screen.blit(background,(0,0))
txtFPS = font.render(str(fps.go()), 1, (100, 100, 100))
screen.blit(txtFPS, (10,10))
pygame.display.flip()
del screen # delete the screen
pygame.quit()
del cam #delete the cam on exit of loop
#######
#FILE 3: performance.py
#######
import time
class FpsMeter:
def __init__(self):
self.boundary = time.time()
self.count = 0;
self.returned = 0
def go(self):
self.count = self.count + 1
if time.time() - self.boundary > 1:
self.boundary = time.time()
self.returned = self.count
self.count = 0
return self.returned





