tracker.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Copyright (c) 2012, Falkor Systems, Inc.  All rights reserved.
00003 
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions are
00006 # met:
00007 
00008 # Redistributions of source code must retain the above copyright notice,
00009 # this list of conditions and the following disclaimer.  Redistributions
00010 # in binary form must reproduce the above copyright notice, this list of
00011 # conditions and the following disclaimer in the documentation and/or
00012 # other materials provided with the distribution.  THIS SOFTWARE IS
00013 # PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
00014 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00015 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
00016 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
00017 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00018 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00019 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00020 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00021 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00022 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00023 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00024 
00025 """
00026 Tracker classes must implement track( frame ) which returns a tuple (x,y,area)
00027 if the tracker does not find an object then it will return None
00028 """
00029 
00030 import numpy as np
00031 import math
00032 import cv2
00033 import time
00034 
00035 lk_params = dict( winSize = ( 30, 30 ),
00036                   maxLevel = 2,
00037                   criteria = ( cv2.TERM_CRITERIA_EPS |
00038                                cv2.TERM_CRITERIA_COUNT, 10, 0.03 ) )
00039 
00040 feature_params = dict( maxCorners = 50,
00041                        qualityLevel = 0.3,
00042                        minDistance = 10,
00043                        blockSize = 10 )
00044 
00045 class DummyTracker:
00046     def track( self, frame ):
00047         (y,x,n) = frame.shape
00048         cv2.imshow( 'Dummy', frame )
00049         return( 50, 50, 50 )
00050 
00051 class LkTracker:
00052     def __init__(self):
00053         self.track_len = 10
00054         self.detect_interval = 5
00055         self.tracks = []
00056         self.frame_idx = 0
00057         self.frame = None
00058         self.mouseDown = False
00059         self.timers = {}
00060 
00061         self.userRect = None
00062 #        cv2.namedWindow( 'LKTracker', cv2.cv.CV_WINDOW_NORMAL )
00063 #        cv2.cv.SetMouseCallback( 'LKTracker', self.on_mouse, None )
00064 
00065         self.vis = None
00066 
00067     def on_mouse( self, event, x, y, flags, param ):
00068         if self.frame is None:
00069             return
00070 
00071         if event == cv2.cv.CV_EVENT_LBUTTONDOWN:
00072             self.mouseDown = True
00073             self.userRect = np.int32( ( ( x, y ), ( x, y ) ) )
00074 
00075         elif event == cv2.cv.CV_EVENT_MOUSEMOVE and self.mouseDown == True:
00076             xmin = min( self.userRect[0,0], self.userRect[1,0], x)
00077             xmax = max( self.userRect[0,0], self.userRect[1,0], x)
00078             ymin = min( self.userRect[0,1], self.userRect[1,1], y)
00079             ymax = max( self.userRect[0,1], self.userRect[1,1], y)
00080             self.userRect = np.int32( ( ( xmin, ymin ), ( xmax, ymax ) ) )
00081             
00082         elif event == cv2.cv.CV_EVENT_LBUTTONUP:
00083             self.mouseDown = False
00084             self.pickFeatures()
00085             self.initCamshift()
00086             self.userRect = None
00087 
00088     def initCamshift(self):
00089         x0,y0,x1,y1 = (self.userRect[0,0],
00090                        self.userRect[0,1],
00091                        self.userRect[1,0],
00092                        self.userRect[1,1])
00093 
00094         hsv_roi = self.hsv[ y0:y1, x0:x1 ]
00095         mask_roi = self.hsv_mask[ y0:y1, x0:x1 ]
00096 
00097         hist = cv2.calcHist( [hsv_roi], [0], mask_roi, [16], [0,180] )
00098         cv2.normalize( hist, hist, 0, 255, cv2.NORM_MINMAX )
00099         self.hist = hist.reshape(-1)
00100         self.track_window = ( x0, y0, x1-x0, y1-y0 )
00101         self.showHist()
00102 
00103     def getCamShift(self):
00104         prob = cv2.calcBackProject( [self.hsv], [0], self.hist, [0,180], 1 )
00105         prob &= self.hsv_mask
00106         term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
00107         track_box, self.track_window = cv2.CamShift( prob, self.track_window, 
00108                                                      term_crit )
00109 #        cv2.imshow( 'Back Projection', prob )
00110         return track_box
00111     
00112     def showHist(self):
00113         bin_count = self.hist.shape[0]
00114         bin_w = 24
00115         img = np.zeros((256, bin_count*bin_w, 3), np.uint8)
00116         for i in xrange(bin_count):
00117             h = int(self.hist[i])
00118             cv2.rectangle(img, (i*bin_w+2, 255), ((i+1)*bin_w-2, 255-h), (int(180.0*i/bin_count), 255, 255), -1)
00119         img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR)
00120 #        cv2.imshow('hist', img)
00121         
00122     def pickFeatures(self):
00123         mask = np.zeros_like( self.frame_gray )
00124 #        import pdb; pdb.set_trace()
00125 
00126         cv2.rectangle( mask, tuple( self.userRect[0] ), tuple( self.userRect[1] ), 255, -1 )
00127 #        cv2.imshow( 'userMask', mask )
00128 
00129         start = cv2.getTickCount() / cv2.getTickFrequency()
00130         p = cv2.goodFeaturesToTrack( self.frame_gray, mask = mask, **feature_params )
00131         self.timers['GoodFeatures'] = cv2.getTickCount() / cv2.getTickFrequency() - start
00132 
00133         if p is not None:
00134             self.tracks = [ [ (x,y) ] for x, y in np.float32(p).reshape(-1, 2) ]
00135 
00136     def equalizeHist(self, frame):
00137         splits = cv2.split( frame )
00138         equalized = map( cv2.equalizeHist, splits )
00139         return cv2.merge( equalized )
00140 
00141     def initializeFrame( self, frame ):
00142         self.frame = cv2.pyrDown( frame )
00143         self.hsv = cv2.cvtColor( self.frame, cv2.COLOR_BGR2HSV )
00144         self.hsv = self.equalizeHist( self.hsv )
00145         self.hsv_mask = cv2.inRange( self.hsv, np.array((0.0, 33.0, 33.0)),
00146                                      np.array((180.0,254.,254.)) )
00147 
00148         # dilate
00149         self.hsv_mask = cv2.dilate( self.hsv_mask, None, iterations = 5 )
00150 
00151 #            self.frame_gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
00152         hsv_split = cv2.split( self.hsv )
00153         self.frame_gray = hsv_split[0]
00154 #        cv2.imshow( 'sat', hsv_split[1] )
00155 #        cv2.imshow( 'vol', hsv_split[2] )
00156         
00157         cv2.imshow( 'gray', self.frame_gray )
00158 #        cv2.imshow( 'hsv_mask', self.hsv_mask )
00159 
00160     def filterOutliers( self, deviations ):
00161         pts = np.int32( [ tr[-1] for tr in self.tracks ] )
00162         if pts.size < 10: # 5 pts (10 bc x/y)
00163             return
00164 
00165         x_median = np.median( pts[:,0] )
00166         y_median = np.median( pts[:,1] )
00167 
00168         distances = ( np.square(pts[:,0] - x_median) +
00169                       np.square(pts[:,1] - y_median) )
00170         distance_median = np.median( distances )
00171 
00172         self.tracks = [ tr for (i,tr) in enumerate( self.tracks )
00173                         if distances[i] < distance_median *
00174                         np.square( deviations ) ]
00175 
00176     def runOpticalFlow( self ):
00177         if len(self.tracks) > 0:
00178             img0, img1 = self.prev_gray, self.frame_gray
00179             p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2 )
00180             start = cv2.getTickCount() / cv2.getTickFrequency()
00181             p1, st, err = cv2.calcOpticalFlowPyrLK( img0, img1, p0, None, **lk_params )
00182             p0r, st, err = cv2.calcOpticalFlowPyrLK( img1, img0, p1, None, **lk_params )
00183             self.timers['LKTrack'] = cv2.getTickCount() / cv2.getTickFrequency() - start
00184             
00185             d = abs( p0-p0r ).reshape(-1, 2).max(-1)
00186             good = d < 1
00187             new_tracks = []
00188             for tr, (x, y), good_flag in zip( self.tracks, p1.reshape(-1,2), good ):
00189                 if not good_flag:
00190                     continue
00191                 tr.append((x, y))
00192                 if len(tr) > self.track_len:
00193                     del tr[0]
00194                     
00195                 new_tracks.append(tr)
00196             
00197             self.tracks = new_tracks
00198                     
00199     def reDetect( self, use_camshift = True, expand_pixels = 1 ):
00200         if self.frame_idx % self.detect_interval == 0 and len(self.tracks) > 0:
00201 
00202             if use_camshift:
00203                 mask_camshift = np.zeros_like( self.frame_gray )
00204                 ellipse_camshift = self.getCamShift()
00205                 cv2.ellipse( mask_camshift, ellipse_camshift, 255, -1 )
00206 
00207             mask_tracked = np.zeros_like( self.frame_gray )
00208             pts = np.int32( [ [tr[-1]] for tr in self.tracks ] )
00209 
00210             if len(pts) > 5:
00211                 ellipse_tracked = cv2.fitEllipse( pts )
00212             else:
00213                 ellipse_tracked = cv2.minAreaRect( pts )
00214 
00215             boundingRect = cv2.boundingRect( pts )
00216             # x0,y0,x1,y1 = ( boundingRect[0] - int(boundingRect[2] * 0.05),
00217             #                 boundingRect[1] - int(boundingRect[3] * 0.05),
00218             #                 boundingRect[0] + int(boundingRect[2] * 1.05),
00219             #                 boundingRect[1] + int(boundingRect[3] * 1.05))
00220 
00221 
00222             x0,y0,x1,y1 = ( boundingRect[0] - expand_pixels,
00223                             boundingRect[1] - expand_pixels,
00224                             boundingRect[0] + boundingRect[2] + expand_pixels,
00225                             boundingRect[1] + boundingRect[3] + expand_pixels )
00226             
00227             if len( pts ) > 2:
00228                 cv2.rectangle( mask_tracked, (x0,y0), (x1,y1), 255, -1 )
00229 
00230             cv2.ellipse( mask_tracked, ellipse_tracked, 255, -1 )
00231 
00232 #            cv2.imshow( 'mask_camshift', mask_camshift )
00233 #            cv2.imshow( 'mask_tracked', mask_tracked )
00234 
00235             if use_camshift:
00236                 mask = mask_camshift & mask_tracked
00237             else:
00238                 mask = mask_tracked
00239 
00240             for x, y in [np.int32(tr[-1]) for tr in self.tracks]:
00241                 cv2.circle(mask, (x,y), 5, 0, -1 )
00242 
00243 #            cv2.imshow( 'mask', mask )
00244             start = cv2.getTickCount() / cv2.getTickFrequency()
00245             p = cv2.goodFeaturesToTrack( self.frame_gray, mask = mask, **feature_params )
00246             self.timers['GoodFeatures'] = cv2.getTickCount() / cv2.getTickFrequency() - start
00247 
00248             if p is not None:
00249                 for x, y in np.float32(p).reshape(-1, 2):
00250                     self.tracks.append([(x,y)])
00251 
00252     def drawAndGetTrack( self ):
00253         vis = self.frame.copy()
00254         
00255         if len(self.tracks) > 0:
00256             cv2.polylines( vis, [ np.int32(tr) for tr in self.tracks ], False, ( 0, 255, 0 ) )
00257             pts = np.int32( [ [tr[-1]] for tr in self.tracks ] )
00258 
00259             for [(x,y)] in pts:
00260                 cv2.circle( vis, (x, y), 2, (0, 255, 0), -1 )
00261 
00262             if len( pts ) > 2:
00263                 boundingRect = cv2.boundingRect( pts )
00264                 x0,y0,x1,y1 = ( boundingRect[0], boundingRect[1],
00265                                 boundingRect[0] + boundingRect[2],
00266                                 boundingRect[1] + boundingRect[3] )
00267                 area = boundingRect[3] * boundingRect[2]
00268                 cx,cy = ( boundingRect[0] + boundingRect[2]/2,
00269                           boundingRect[1] + boundingRect[3]/2 )
00270 
00271                 cv2.rectangle( vis, (x0,y0), (x1,y1), (0,255,255), 3, 8, 0 )
00272 
00273                 cv2.circle( vis, (cx,cy), 5, (0,255,255), -1, 8, 0 )
00274 
00275         if self.userRect != None:
00276             cv2.rectangle( vis, tuple( self.userRect[0] ), tuple( self.userRect[1] ), ( 255, 255, 255 ) )
00277 
00278         i = 0
00279         total = 0
00280         for ( timer, time ) in self.timers.items():
00281             i += 1
00282             total += time
00283             cv2.putText( vis, '%s: %.1f ms' % ( timer, time * 1e3 ),
00284                          ( 20, i*20 ), cv2.FONT_HERSHEY_PLAIN, 1.0,
00285                          (255, 255, 255) )
00286             cv2.putText( vis, '%s: %.1f ms' % ( timer, time * 1e3 ),
00287                          ( 20, i*20 ), cv2.FONT_HERSHEY_PLAIN, 1.0,
00288                          ( 0, 0, 0), thickness = 2 )
00289 
00290 
00291         i += 1
00292         cv2.putText( vis, 'Total: %.1f ms' % ( total * 1e3 ),
00293                      ( 20, i*20 ), cv2.FONT_HERSHEY_PLAIN, 1.0,
00294                      (255, 255, 255) )
00295         cv2.putText( vis, 'Total: %.1f ms' % ( total * 1e3 ),
00296                      ( 20, i*20 ), cv2.FONT_HERSHEY_PLAIN, 1.0,
00297                      ( 0, 0, 0), thickness = 2 )
00298 
00299         self.vis = vis.copy()
00300 
00301 #        cv2.imshow( 'LKTracker', self.vis )
00302 #        cv2.waitKey( 1 )
00303 
00304         if len( self.tracks ) > 2:
00305             imageSize = self.frame.shape
00306             imageArea = imageSize[0]*imageSize[1]
00307 
00308             xRel = cx*100/imageSize[1]
00309             yRel = cy*100/imageSize[0]
00310             areaRel = math.sqrt( float( area ) / float( imageArea ) ) * 100
00311             return xRel,yRel,areaRel,cx,cy
00312         else:
00313             return None
00314 
00315     def get_vis( self ):
00316         return self.vis
00317 
00318     def track(self, frame):
00319         self.initializeFrame( frame )
00320         self.runOpticalFlow()
00321         self.filterOutliers( 3 )
00322 
00323         self.reDetect()
00324         trackData = self.drawAndGetTrack()
00325 
00326         self.frame_idx += 1
00327         self.prev_gray = self.frame_gray
00328 
00329         return trackData
00330 
00331 class CascadeTracker( LkTracker ):
00332     def __init__( self, cascadeFn ):
00333         self.cascade = cv2.CascadeClassifier( cascadeFn )
00334         self.trackData = None
00335         self.cascade_interval = 30
00336 
00337         LkTracker.__init__( self )
00338 
00339     def initCamshift( self ):
00340         pass
00341 
00342     def track( self, frame ):
00343         self.frame = frame # cv2.pyrDown( frame )
00344         self.frame_gray = cv2.cvtColor( self.frame, cv2.COLOR_BGR2GRAY )
00345         self.frame_gray = cv2.equalizeHist( self.frame_gray )
00346 
00347         # do optical flow if we have it
00348         self.runOpticalFlow()
00349         self.trackData = self.drawAndGetTrack()
00350 
00351         # now look for the object every 5th frame
00352         detectedObject = self.detectObject()
00353 
00354         # if we don't have it redetect optical flow
00355         if detectedObject == None:
00356             self.filterOutliers( 4 )
00357             self.reDetect( False, 0 )
00358 
00359         # If we do have it, then re-select optical flow features
00360         else:
00361 #            import pdb; pdb.set_trace()
00362             self.userRect = [ [ detectedObject[0],
00363                                 detectedObject[1] ],
00364                               [ detectedObject[0] + detectedObject[2],
00365                                 detectedObject[1] + detectedObject[3] ] ]
00366             self.pickFeatures()
00367 
00368             # Add points at the corners
00369             corners = [ [ ( detectedObject[0], detectedObject[1] ) ],
00370                         [ ( detectedObject[0] + detectedObject[2],
00371                             detectedObject[1] ) ],
00372                         [ ( detectedObject[0],
00373                             detectedObject[1] + detectedObject[3] ) ],
00374                         [ ( detectedObject[0] + detectedObject[2],
00375                             detectedObject[1] + detectedObject[3] ) ]
00376                         ]
00377 
00378             self.tracks.extend( corners )
00379 
00380             self.userRect = None
00381             self.trackData = self.drawAndGetTrack()
00382 
00383             # imageSize = frame.shape
00384             # imageArea = imageSize[0]*imageSize[1]
00385 
00386             # area = detectedObject[2] * detectedObject[3]
00387             # cx = detectedObject[0] + detectedObject[2]/2
00388             # cy = detectedObject[1] + detectedObject[3]/2
00389 
00390             # xRel = cx*100/imageSize[1]
00391             # yRel = cy*100/imageSize[0]
00392             # areaRel = math.sqrt( float( area ) / float( imageArea ) ) * 100
00393             # trackData = xRel,yRel,areaRel
00394 
00395         self.frame_idx += 1
00396         self.prev_gray = self.frame_gray
00397 
00398         return self.trackData
00399         
00400     def detectObject( self ):
00401         # don't do a cascade every frame
00402         if self.frame_idx % self.cascade_interval != 0:
00403             return None
00404 
00405         start = cv2.getTickCount() / cv2.getTickFrequency()
00406         objects = self.cascade.detectMultiScale( self.frame_gray,
00407                                                  scaleFactor=1.3,
00408                                                  minNeighbors=4,
00409                                                  minSize=(15, 15),
00410                                                  flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
00411         self.timers['Cascade'] = cv2.getTickCount() / cv2.getTickFrequency() - start
00412 
00413         vis = self.frame_gray.copy()
00414 
00415         if len( objects ) > 0:
00416             for i in objects:
00417                 cv2.rectangle( vis,
00418                               ( int(i[0]), int(i[1]) ),
00419                               ( int(i[0]+i[2]), int(i[1]+i[3]) ),
00420                               cv2.cv.CV_RGB(0,255,0), 3, 8, 0)
00421 
00422 #        cv2.imshow( "objects", vis )
00423 
00424         # if we have only one object return that
00425         # or if we have no trackData, return the first object
00426         # if we have trackData and multiple objects, return the object
00427         # closest to the trackData
00428         if len( objects ) > 0:
00429             if len( objects ) == 1 or not self.trackData:
00430                 return objects[0]
00431             else:
00432                 distances_squared = ( np.square(objects[:,0]+objects[:,2]/2 -
00433                                                 self.trackData[3]) +
00434                                       np.square(objects[:,1]+objects[:,2]/2 -
00435                                                 self.trackData[4]) )
00436                 min_index = np.argmin( distances_squared )
00437                 return objects[min_index]
00438         else:
00439             return None
00440 
00441 def main():
00442     import sys
00443     try: video_src = sys.argv[1]
00444     except: video_src = 0
00445 
00446     tracker = CascadeTracker(
00447         '/home/sameer/ros/falkor_ardrone/cascade/haarcascade_falkorlogopaper.xml' )
00448     cam = cv2.VideoCapture( video_src )
00449 
00450     while True:
00451         ret, frame = cam.read()
00452         if ret:
00453             trackData = tracker.track( frame )
00454 
00455             if trackData:
00456                 print trackData
00457 
00458         ch = 0xFF & cv2.waitKey(1)
00459         if ch == 27:
00460             break
00461 
00462 if __name__ == '__main__':
00463     main()
00464 
00465 
00466 
00467                     
00468                 
00469                 
00470                  
00471             
00472     


falkor_ardrone
Author(s): Sameer Parekh
autogenerated on Tue Jan 7 2014 11:12:14