Package pyvision :: Package surveillance :: Module BackgroundSubtraction
[hide private]
[frames] | no frames]

Source Code for Module pyvision.surveillance.BackgroundSubtraction

  1  ''' 
  2  Created on Oct 22, 2010 
  3  @author: Stephen O'Hara 
  4  ''' 
  5  # PyVision License 
  6  # 
  7  # Copyright (c) 2006-2008 Stephen O'Hara 
  8  # All rights reserved. 
  9  # 
 10  # Redistribution and use in source and binary forms, with or without 
 11  # modification, are permitted provided that the following conditions 
 12  # are met: 
 13  #  
 14  # 1. Redistributions of source code must retain the above copyright 
 15  # notice, this list of conditions and the following disclaimer. 
 16  #  
 17  # 2. Redistributions in binary form must reproduce the above copyright 
 18  # notice, this list of conditions and the following disclaimer in the 
 19  # documentation and/or other materials provided with the distribution. 
 20  #  
 21  # 3. Neither name of copyright holders nor the names of its contributors 
 22  # may be used to endorse or promote products derived from this software 
 23  # without specific prior written permission. 
 24  #  
 25  #  
 26  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 27  # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 28  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 29  # A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR 
 30  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 31  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 32  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 33  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 34  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 35  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 36  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 37  import scipy as sp 
 38  import pyvision as pv 
 39  import math 
 40  import cv 
 41  #Constants used to identify a background subtraction method, 
 42  # useful, for example, for specifying which method to use in the 
 43  # MotionDetector class. 
 44  BG_SUBTRACT_FD   = "BG_SUBTRACT_FD"  #frame differencer 
 45  BG_SUBTRACT_MCFD = "BG_SUBTRACT_MCFD"  #motion compensated frame differencer 
 46  BG_SUBTRACT_MF   = "BG_SUBTRACT_MF"  #median filter 
 47  BG_SUBTRACT_AMF  = "BG_SUBTRACT_AMF" #approx median filter 
 48   
 49   
50 -class AbstractBGModel:
51 - def __init__(self, imageBuffer, thresh=20, soft_thresh=False):
52 ''' 53 @param imageBuffer: An ImageBuffer object that has already been filled 54 with the appropriate number of images. (Provide a full buffer...so a few 55 frames of initialization will be required in most cases to fill up a 56 newly created buffer.) 57 @param thresh: A noise threshold to remove very small differences. 58 ''' 59 self._imageBuffer = imageBuffer 60 self._threshold = thresh 61 self._softThreshold = soft_thresh
62
63 - def _computeBGDiff(self):
64 ''' 65 This private method should be overridden by a concrete background subtraction 66 class to yield a difference image from the background model. 67 ''' 68 raise NotImplemented
69
70 - def getForegroundMask(self):
71 ''' 72 @return: A mask image indicating which pixels are considered foreground. 73 Depending on whether soft-thresholding is used, this may be a binary image 74 with values of [0 or 255], or image of weights [0.0-255.0], which will 75 have to be divided by 255 to get weights [0.0-1.0]. 76 @note: One may wish to perform additional morphological operations 77 on the foreground mask prior to use. 78 ''' 79 diff = self._computeBGDiff() 80 if self._softThreshold: 81 mask = 1 - (math.e)**(-(1.0*diff)/self._threshold) #element-wise exp weighting 82 #mask = (diff > self._threshold) 83 else: 84 mask = (sp.absolute(diff) > self._threshold) 85 #mu = sp.mean(diff) 86 #sigma = sp.std(diff) 87 #mask = sp.absolute((diff-mu)/sigma) > self._threshold 88 return pv.Image(mask*255.0)
89 90
91 -class FrameDifferencer(AbstractBGModel):
92 ''' 93 This class is useful for simple N-frame differencing method of 94 background subtraction. If you have a stationary camera, this can 95 be a simple and effective way to isolate people/moving objects 96 from the background scene. 97 98 FrameDifferencer uses ImageBuffer for operation. Assume the buffer 99 size is 5. The output of the frame differencing operation will 100 be based on the middle image, the 3rd in the buffer. The output 101 is the intersection of the following two absolute differences: 102 abs(Middle-First) AND abs(Last-Middle). 103 ''' 104
105 - def __init__(self, imageBuffer, thresh=20, soft_thresh = False):
106 AbstractBGModel.__init__(self, imageBuffer, thresh, soft_thresh)
107
108 - def _computeBGDiff(self):
109 prevImg = self._imageBuffer[0].asMatrix2D() 110 curImg = self._imageBuffer.getMiddle().asMatrix2D() 111 nextImg = self._imageBuffer[-1].asMatrix2D() 112 113 delta1 = sp.absolute(curImg - prevImg) #frame diff 1 114 delta2 = sp.absolute(nextImg - curImg) #frame diff 2 115 116 #use element-wise minimum of the two difference images, which is what 117 # gets compared to threshold to yield foreground mask 118 return sp.minimum(delta1, delta2)
119
120 -class MotionCompensatedFrameDifferencer(AbstractBGModel):
121 ''' 122 This class represents a more sophisticated frame differencing 123 algorithm that takes into account potential camera motion, and 124 applies a registration method to align subsequent images prior 125 to frame subtraction. 126 ''' 127
128 - def __init__(self, imageBuffer, thresh=20, soft_thresh = False):
129 AbstractBGModel.__init__(self, imageBuffer, thresh, soft_thresh) 130 self._flow = pv.OpticalFlow() 131 if imageBuffer.isFull(): 132 self._initFlow() #if a non-full buffer is given, then we
133 #must assume the caller will perform 134 #flow initiation when appropriate. 135
136 - def _initFlow(self):
137 ''' 138 Should be called after buffer is full to compute the optical flow 139 information on the buffered frames. Only needs to be called once, 140 prior to first call of _computeBGDiff(), because from then on, 141 the flow will be updated as new frames are added to the buffer. 142 ''' 143 for i in range( len(self._imageBuffer)): 144 self._flow.update( self._imageBuffer[i])
145 146
147 - def getOpticalFlow(self):
148 ''' 149 @return: A handle to the pv.OpticalFlow object being used by this object. 150 ''' 151 return self._flow
152
153 - def setOpticalFlow(self, OF_Object):
154 ''' 155 This is an optional method that allows the user to provide an 156 optical flow object (pv.OpticalFlow) with non-default settings. 157 @param OF_Object: The optical flow object desired for use in computing the 158 motion compensated frame difference. 159 ''' 160 self._flow = OF_Object
161
162 - def _computeBGDiff(self):
163 self._flow.update( self._imageBuffer.getLast() ) 164 165 n = len(self._imageBuffer) 166 prev_im = self._imageBuffer[0] 167 forward = None 168 for i in range(0,n/2): 169 if forward == None: 170 forward = self._imageBuffer[i].to_next 171 else: 172 forward = forward * self._imageBuffer[i].to_next 173 174 w,h = size = prev_im.size 175 mask = cv.CreateImage(size,cv.IPL_DEPTH_8U,1) 176 cv.Set(mask,0) 177 interior = cv.GetSubRect(mask, pv.Rect(2,2,w-4,h-4).asOpenCV()) 178 cv.Set(interior,255) 179 mask = pv.Image(mask) 180 181 prev_im = forward(prev_im) 182 prev_mask = forward(mask) 183 184 185 next_im = self._imageBuffer[n-1] 186 back = None 187 for i in range(n-1,n/2,-1): 188 if back == None: 189 back = self._imageBuffer[i].to_prev 190 else: 191 back = back * self._imageBuffer[i].to_prev 192 193 next_im = back(next_im) 194 next_mask = back(mask) 195 196 curr_im = self._imageBuffer[n/2] 197 198 199 prevImg = prev_im.asMatrix2D() 200 curImg = curr_im.asMatrix2D() 201 nextImg = next_im.asMatrix2D() 202 prevMask = prev_mask.asMatrix2D() 203 nextMask = next_mask.asMatrix2D() 204 205 # Compute transformed images 206 delta1 = sp.absolute(curImg - prevImg) #frame diff 1 207 delta2 = sp.absolute(nextImg - curImg) #frame diff 2 208 209 delta1 = sp.minimum(delta1,prevMask) 210 delta2 = sp.minimum(delta2,nextMask) 211 212 #use element-wise minimum of the two difference images, which is what 213 # gets compared to threshold to yield foreground mask 214 return sp.minimum(delta1, delta2)
215
216 -class MedianFilter(AbstractBGModel):
217 ''' 218 Uses median pixel values of the images in a buffer to 219 approximate a background model. 220 '''
221 - def __init__(self, imageBuffer, thresh=20, soft_thresh = False):
222 AbstractBGModel.__init__(self, imageBuffer, thresh, soft_thresh)
223
224 - def _getMedianVals(self):
225 ''' 226 @return: A scipy matrix representing the gray-scale median values of the image stack. 227 If you want a pyvision image, just wrap the result in pv.Image(result). 228 ''' 229 self._imageStack = self._imageBuffer.asStackBW() 230 medians = sp.median(self._imageStack, axis=0) #median of each pixel jet in stack 231 return medians
232
233 - def _computeBGDiff(self):
234 imgGray = self._imageBuffer.getLast().asMatrix2D() 235 imgBG = self._getMedianVals() 236 return (imgGray - imgBG)
237 238
239 -class ApproximateMedianFilter(MedianFilter):
240 ''' 241 Approximates the median pixels via an efficient incremental algorithm that 242 would converge to the true median in a perfect world. It initializes a 243 median image based on the images in the initial image buffer, but 244 then only updates the median image using the last (newest) image in the 245 buffer. 246 '''
247 - def __init__(self, imageBuffer, thresh=20, soft_thresh=False):
248 if not imageBuffer.isFull(): 249 raise ValueError("Image Buffer must be full before initializing Approx. Median Filter.") 250 MedianFilter.__init__(self, imageBuffer, thresh, soft_thresh) 251 self._medians = self._getMedianVals()
252
253 - def _updateMedian(self):
254 curImg = self._imageBuffer.getLast() 255 curMat = curImg.asMatrix2D() 256 median = self._medians 257 up = (curMat > median)*1.0 258 down = (curMat < median)*1.0 259 self._medians = self._medians + up - down
260
261 - def _computeBGDiff(self):
262 self._updateMedian() 263 imgGray = self._imageBuffer.getLast().asMatrix2D() 264 imgBG = self._medians 265 return (imgGray - imgBG)
266