changeset 824:28526917a583

merged
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Mon, 27 Jun 2016 16:19:34 -0400
parents f6790357f53b (current diff) 41558145e131 (diff)
children 6e4357e9116d 8b74a5176549
files
diffstat 6 files changed, 184 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/python/cvutils.py	Mon Jun 27 16:19:06 2016 -0400
+++ b/python/cvutils.py	Mon Jun 27 16:19:34 2016 -0400
@@ -146,39 +146,53 @@
         newCameraMatrix[1,2] = newImgSize[1]/2.
         return cv2.initUndistortRectifyMap(intrinsicCameraMatrix, array(distortionCoefficients), identity(3), newCameraMatrix, newImgSize, cv2.CV_32FC1)
 
-    def playVideo(filename, firstFrameNum = 0, frameRate = -1, interactive = False, printFrames = True, text = None, rescale = 1., step = 1):
-        '''Plays the video'''
-        windowName = 'frame'
+    def playVideo(filenames, windowNames = None, firstFrameNums = None, frameRate = -1, interactive = False, printFrames = True, text = None, rescale = 1., step = 1):
+        '''Plays the video(s)'''
+        if len(filenames) == 0:
+            print('Empty filename list')
+            return
+        if windowNames is None:
+            windowNames = ['frame{}'.format(i) for i in xrange(len(filenames))]
         wait = 5
         if rescale == 1.:
-            cv2.namedWindow(windowName, cv2.WINDOW_NORMAL)
+            for windowName in windowNames:
+                cv2.namedWindow(windowName, cv2.WINDOW_NORMAL)
         if frameRate > 0:
             wait = int(round(1000./frameRate))
         if interactive:
             wait = 0
-        capture = cv2.VideoCapture(filename)
-        if capture.isOpened():
+        captures = [cv2.VideoCapture(fn) for fn in filenames]
+        if array([cap.isOpened() for cap in captures]).all():
             key = -1
             ret = True
-            frameNum = firstFrameNum
-            capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum)
+            nFramesShown = 0
+            if firstFrameNums is not None:
+                for i in xrange(len(captures)):
+                    captures[i].set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNums[i])
             while ret and not quitKey(key):
-                ret, img = capture.read()
-                if ret:
+                rets = []
+                images = []
+                for cap in captures:
+                    ret, img = cap.read()
+                    rets.append(ret)
+                    images.append(img)
+                if array(rets).all():
                     if printFrames:
-                        print('frame {0}'.format(frameNum))
-                    if text is not None:
-                       cv2.putText(img, text, (10,50), cv2.FONT_HERSHEY_PLAIN, 1, cvRed)
-                    cv2.imshow('frame', img)#cvImshow(windowName, img, rescale)
+                        print('frame shown {0}'.format(nFramesShown))
+                    for i in xrange(len(filenames)):
+                        if text is not None:
+                            cv2.putText(images[i], text, (10,50), cv2.FONT_HERSHEY_PLAIN, 1, cvRed)
+                        cvImshow(windowNames[i], images[i], rescale) # cv2.imshow('frame', img)
                     key = cv2.waitKey(wait)
                     if saveKey(key):
                         cv2.imwrite('image-{}.png'.format(frameNum), img)
-                    frameNum += step
+                    nFramesShown += step
                     if step > 1:
-                        capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, frameNum)
+                        for i in xrange(len(captures)):
+                            captures.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNums[i]+nFramesShown)
             cv2.destroyAllWindows()
         else:
-            print('Video capture for {} failed'.format(filename))
+            print('Video captures for {} failed'.format(filenames))
 
     def infoVideo(filename):
         '''Provides all available info on video '''
--- a/python/metadata.py	Mon Jun 27 16:19:06 2016 -0400
+++ b/python/metadata.py	Mon Jun 27 16:19:34 2016 -0400
@@ -1,13 +1,14 @@
 # from moving import Point
 
-from datetime import datetime
+from datetime import datetime, timedelta
 from os import path
+from math import floor
 
-from sqlalchemy import create_engine, Column, Integer, Float, DateTime, String, ForeignKey
+from sqlalchemy import orm, create_engine, Column, Integer, Float, DateTime, String, ForeignKey, Boolean, Interval
 from sqlalchemy.orm import relationship, backref, sessionmaker
 from sqlalchemy.ext.declarative import declarative_base
 
-from utils import datetimeFormat
+from utils import datetimeFormat, removeExtension
 
 Base = declarative_base()
 
@@ -20,14 +21,16 @@
     ycoordinate = Column(Float)
     mapImageFilename = Column(String) # path to filename, relative to site name, ie sitename/mapImageFilename
     nUnitsPerPixel = Column(Float) # number of units of distance per pixel in map image
+    worldDistanceUnit = Column(String, default = 'm') # make sure it is default in the database
     
-    def __init__(self, name, description = "", xcoordinate = None, ycoordinate = None, mapImageFilename = None, nUnitsPerPixel = 1.):
+    def __init__(self, name, description = "", xcoordinate = None, ycoordinate = None, mapImageFilename = None, nUnitsPerPixel = 1., worldDistanceUnit = 'm'):
         self.name = name
         self.description = description
         self.xcoordinate = xcoordinate
         self.ycoordinate = ycoordinate
         self.mapImageFilename = mapImageFilename
         self.nUnitsPerPixel = nUnitsPerPixel
+        self.worldDistanceUnit = worldDistanceUnit
 
     def getFilename(self):
         return self.name
@@ -53,23 +56,77 @@
         self.description = description
         self.site = site
 
+class CameraType(Base):
+    ''' Represents parameters of the specific camera used. 
+
+    Taken and adapted from tvalib'''
+    __tablename__ = 'camera_types'
+    idx = Column(Integer, primary_key=True)
+    name = Column(String)
+    resX = Column(Integer)
+    resY = Column(Integer)
+    frameRate = Column(Float)
+    frameRateTimeUnit = Column(String, default = 's')
+    undistort = Column(Boolean)
+    intrinsicCameraMatrixStr = Column(String)
+    distortionCoefficientsStr = Column(String)
+    undistortedImageMultiplication = Column(Float)
+    
+    def __init__(self, name, resX, resY, frameRate, frameRateTimeUnit = 's', trackingConfigurationFilename = None, undistort = None, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = None):
+        self.name = name
+        self.resX = resX
+        self.resY = resY
+        self.frameRate = frameRate
+        self.frameRateTimeUnit = frameRateTimeUnit
+        self.undistort = False
+
+        if trackingConfigurationFilename is not None:
+            from storage import ProcessParameters
+            params = ProcessParameters(trackingConfigurationFilename)
+            if params.undistort:
+                self.undistort = params.undistort
+                self.intrinsicCameraMatrix = params.intrinsicCameraMatrix
+                self.distortionCoefficients = params.distortionCoefficients
+                self.undistortedImageMultiplication = params.undistortedImageMultiplication
+        elif undistort is not None:
+            self.undistort = undistort
+            self.intrinsicCameraMatrix = intrinsicCameraMatrix
+            self.distortionCoefficients = distortionCoefficients
+            self.undistortedImageMultiplication = undistortedImageMultiplication
+
+        # populate the db
+        if hasattr(self, 'intrinsicCameraMatrix') and self.intrinsicCameraMatrix is not None\
+        and hasattr(self, 'distortionCoefficients') and self.distortionCoefficients is not None:
+            self.intrinsicCameraMatrixStr = ' '.join('{}'.format(x) for x in self.intrinsicCameraMatrix.flatten('C'))
+            self.distortionCoefficientsStr = ' '.join('{}'.format(x)for x in self.distortionCoefficients)
+
+    @orm.reconstructor
+    def initOnLoad(self):
+        from numpy import array
+        if len(self.intrinsicCameraMatrixStr) > 0:
+            self.intrinsicCameraMatrix = array([float(x) for x in self.intrinsicCameraMatrixStr.split(" ")]).reshape(3,3)
+        if len(self.distortionCoefficientsStr) > 0:
+            self.distortionCoefficients = [float(x) for x in self.distortionCoefficientsStr.split(" ")]
+        
 class CameraView(Base):
     __tablename__ = 'camera_views'
     idx = Column(Integer, primary_key=True)
-    frameRate = Column(Float)
+    description = Column(String)
     homographyFilename = Column(String) # path to homograph filename, relative to the site name
-    cameraCalibrationFilename = Column(String) # path to full camera calibration, relative to the site name
     siteIdx = Column(Integer, ForeignKey('sites.idx'))
+    cameraTypeIdx = Column(Integer, ForeignKey('camera_types.idx'))
     homographyDistanceUnit = Column(String, default = 'm') # make sure it is default in the database
-    configurationFilename = Column(String) # path to configuration .cfg file, relative to site name
+    trackingConfigurationFilename = Column(String) # path to configuration .cfg file, relative to site name
 
-    site = relationship("Site", backref=backref('camera_views', order_by = idx))
+    site = relationship("Site", backref=backref('sites', order_by = idx))
+    cameraType = relationship('CameraType', backref=backref('camera_views', order_by = idx))
 
-    def __init__(self, frameRate, homographyFilename, cameraCalibrationFilename, site, configurationFilename):
-        self.frameRate = frameRate
+    def __init__(self, description, homographyFilename, site, cameraType, trackingConfigurationFilename):
+        self.description = description
         self.homographyFilename = homographyFilename
         self.site = site
-        self.configurationFilename = configurationFilename
+        self.cameraType = cameraType
+        self.trackingConfigurationFilename = trackingConfigurationFilename
 
     def getHomographyFilename(self, relativeToSiteFilename = True):
         if relativeToSiteFilename:
@@ -77,6 +134,15 @@
         else:
             return self.homographyFilename
 
+    def getTrackingConfigurationFilename(self, relativeToSiteFilename = True):
+        if relativeToSiteFilename:
+            return self.site.getFilename()+path.sep+self.trackingConfigurationFilename
+        else:
+            return self.trackingConfigurationFilename
+
+    def getTrackingParameters(self):
+        return ProcessParameters(getTrackingConfigurationFilename())
+
 class Alignment(Base):
     __tablename__ = 'alignments'
     idx = Column(Integer, primary_key=True)
@@ -107,23 +173,24 @@
     idx = Column(Integer, primary_key=True)
     name = Column(String) # path relative to the the site name
     startTime = Column(DateTime)
-    duration = Column(Float) # video sequence duration
-    durationUnit = Column(String, default = 's')
+    duration = Column(Interval) # video sequence duration
+    databaseFilename = Column(String) # path relative to the the site name
     siteIdx = Column(Integer, ForeignKey('sites.idx'))
     cameraViewIdx = Column(Integer, ForeignKey('camera_views.idx'))
-    configurationFilename = Column(String)
 
     site = relationship("Site", backref=backref('video_sequences', order_by = idx))
     cameraView = relationship("CameraView", backref=backref('video_sequences', order_by = idx))
 
-    def __init__(self, name, startTime, duration, site, cameraView, configurationFilename = None):
-        'startTime is passed as string in utils.datetimeFormat, eg 2011-06-22 10:00:39'
+    def __init__(self, name, startTime, duration, site, cameraView, databaseFilename = None):
+        '''startTime is passed as string in utils.datetimeFormat, eg 2011-06-22 10:00:39
+        duration is a timedelta object'''
         self.name = name
         self.startTime = datetime.strptime(startTime, datetimeFormat)
         self.duration = duration
         self.site = site
         self.cameraView = cameraView
-        self.configurationFilename = configurationFilename
+        if databaseFilename is None and len(self.name) > 0:
+            self.databaseFilename = removeExtension(self.name)+'.sqlite'
 
     def getVideoSequenceFilename(self, relativeToSiteFilename = True):
         if relativeToSiteFilename:
@@ -131,8 +198,16 @@
         else:
             return self.name
 
-        #def getConfigurationFilename(self):
-        #'returns the local configuration filename, or the one of the camera view otherwise'
+    def containsInstant(self, instant):
+        'instant is a datetime'
+        return self.startTime <= instant and self.startTime+self.duration
+        
+    def getFrameNum(self, instant):
+        'Warning, there is no check of correct time units'
+        if self.containsInstant(instant):
+            return int(floor((instant-self.startTime).seconds*self.cameraView.cameraType.frameRate))
+        else:
+            return None
 
 # add class for Analysis: foreign key VideoSequenceId, dataFilename, configFilename (get the one from camera view by default), mask? (no, can be referenced in the tracking cfg file)
 
--- a/scripts/learn-motion-patterns.py	Mon Jun 27 16:19:06 2016 -0400
+++ b/scripts/learn-motion-patterns.py	Mon Jun 27 16:19:34 2016 -0400
@@ -11,6 +11,7 @@
 #parser.add_argument('--cfg', dest = 'configFilename', help = 'name of the configuration file')
 parser.add_argument('-d', dest = 'databaseFilename', help = 'name of the Sqlite database file', required = True)
 parser.add_argument('-t', dest = 'trajectoryType', help = 'type of trajectories to display', choices = ['objectfeatures', 'feature', 'object'], default = 'objectfeatures')
+parser.add_argument('--max-nobjectfeatures', dest = 'maxNObjectFeatures', help = 'maximum number of features per object to load', type = int, default = 3)
 parser.add_argument('-n', dest = 'nTrajectories', help = 'number of the object or feature trajectories to load', type = int, default = None)
 parser.add_argument('-e', dest = 'epsilon', help = 'distance for the similarity of trajectory points', type = float, required = True)
 parser.add_argument('--metric', dest = 'metric', help = 'metric for the similarity of trajectory points', default = 'cityblock') # default is manhattan distance
@@ -34,7 +35,7 @@
     features = []
     for o in objects:
         tmp = utils.sortByLength(o.getFeatures(), reverse = True)
-        features += tmp[:min(len(tmp), 3)]
+        features += tmp[:min(len(tmp), args.maxNObjectFeatures)]
     objects = features
 
 trajectories = [o.getPositions().asArray().T for o in objects]
@@ -50,8 +51,10 @@
 
 prototypeIndices, labels = ml.prototypeCluster(trajectories, similarities, args.minSimilarity, lambda x,y : lcss.computeNormalized(x, y), args.minClusterSize) # this line can be called again without reinitializing similarities
 
+print(ml.computeClusterSizes(labels, prototypeIndices, -1))
+
 if args.display:
-    from matplotlib.pyplot import figure
+    from matplotlib.pyplot import figure, show
     figure()
     for i,o in enumerate(objects):
         if i not in prototypeIndices:
@@ -61,5 +64,6 @@
                 o.plot(utils.colors[labels[i]])
     for i in prototypeIndices:
             objects[i].plot(utils.colors[i]+'o')
+    show()
 
 # TODO store the prototypes (if features, easy, if objects, info must be stored about the type)
--- a/scripts/learn-poi.py	Mon Jun 27 16:19:06 2016 -0400
+++ b/scripts/learn-poi.py	Mon Jun 27 16:19:34 2016 -0400
@@ -16,6 +16,7 @@
 parser.add_argument('--covariance-type', dest = 'covarianceType', help = 'type of covariance of Gaussian model', default = "full")
 parser.add_argument('-w', dest = 'worldImageFilename', help = 'filename of the world image')
 parser.add_argument('-u', dest = 'unitsPerPixel', help = 'number of units of distance per pixel', type = float, default = 1.)
+parser.add_argument('--display', dest = 'display', help = 'display points of interests', action = 'store_true') # default is manhattan distance
 
 args = parser.parse_args()
 
@@ -44,18 +45,22 @@
     if not model.converged_:
         print('Warning: model for '+gmmType+' points did not converge')
     # plot
-    fig = plt.figure()
-    if args.worldImageFilename is not None and args.unitsPerPixel is not None:
-        img = plt.imread(args.worldImageFilename)
-        plt.imshow(img)
-    labels = ml.plotGMMClusters(model, points, fig, nUnitsPerPixel = args.unitsPerPixel)
-    plt.axis('image')
-    plt.title(gmmType)
-    print(gmmType+' Clusters:\n{}'.format(ml.computeClusterSizes(labels, range(model.n_components))))
+    if args.display:
+        fig = plt.figure()
+        if args.worldImageFilename is not None and args.unitsPerPixel is not None:
+            img = plt.imread(args.worldImageFilename)
+            plt.imshow(img)
+        labels = ml.plotGMMClusters(model, points, fig, nUnitsPerPixel = args.unitsPerPixel)
+        plt.axis('image')
+        plt.title(gmmType)
+        print(gmmType+' Clusters:\n{}'.format(ml.computeClusterSizes(labels, range(model.n_components))))
     # save
     storage.savePOIs(args.databaseFilename, model, gmmType, gmmId)
     gmmId += 1
-                     
+
+if args.display:
+    plt.show()
+
 # fig = plt.figure()
 # if args.worldImageFilename is not None and args.pixelsPerUnit is not None:
 #     img = plt.imread(args.worldImageFilename)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/play-synced-videos.py	Mon Jun 27 16:19:34 2016 -0400
@@ -0,0 +1,37 @@
+#! /usr/bin/env python
+
+import sys, argparse, os.path
+import cvutils, utils
+from metadata import createDatabase, Site, VideoSequence
+from datetime import datetime, timedelta
+
+parser = argparse.ArgumentParser(description='The program displays several views of the same site synchronously.')
+parser.add_argument('-i', dest = 'metadataFilename', help = 'name of the metadata file', required = True)
+parser.add_argument('-n', dest = 'siteId', help = 'site id or site name', required = True)
+parser.add_argument('-t', dest = 'startTime', help = 'time to start playing (format %Y-%m-%d %H:%M:%S, eg 2011-06-22 10:00:39)', required = True)
+parser.add_argument('--fps', dest = 'frameRate', help = 'approximate frame rate to replay', default = -1, type = float)
+parser.add_argument('-r', dest = 'rescale', help = 'rescaling factor for the displayed image', default = 1., type = float)
+parser.add_argument('-s', dest = 'step', help = 'display every s image', default = 1, type = int)
+
+args = parser.parse_args()
+
+session = createDatabase(args.metadataFilename)
+
+if str.isdigit(args.siteId):
+    site = session.query(Site).filter(Site.idx == int(args.siteId)).first()
+else:
+    site = session.query(Site).filter(Site.description.like('%'+args.siteId+'%')).first()
+
+if site is None:
+    print('Site {} was not found in {}. Exiting'.format(args.siteId, args.metadataFilename))
+    sys.exit()
+
+dirname = os.path.split(args.metadataFilename)[0]
+
+startTime = datetime.strptime(args.startTime, utils.datetimeFormat)
+videoSequences = session.query(VideoSequence).filter(VideoSequence.site == site).filter(VideoSequence.startTime <= startTime).all()
+videoSequences = [v for v in videoSequences if v.containsInstant(startTime)]
+filenames = [dirname+os.path.sep+v.getVideoSequenceFilename() for v in videoSequences]
+firstFrameNums = [v.getFrameNum(startTime) for v in videoSequences]
+
+cvutils.playVideo(filenames, [v.cameraView.description for v in videoSequences], firstFrameNums, args.frameRate, rescale = args.rescale, step = args.step)
--- a/scripts/play-video.py	Mon Jun 27 16:19:06 2016 -0400
+++ b/scripts/play-video.py	Mon Jun 27 16:19:34 2016 -0400
@@ -7,7 +7,7 @@
 parser = argparse.ArgumentParser(description='The program displays the video.')
 parser.add_argument('-i', dest = 'videoFilename', help = 'name of the video file', required = True)
 parser.add_argument('-f', dest = 'firstFrameNum', help = 'number of first frame number to display', default = 0, type = int)
-parser.add_argument('--fps', dest = 'frameRate', help = 'approximate frame rate to replay', type = float)
+parser.add_argument('--fps', dest = 'frameRate', help = 'approximate frame rate to replay', default = -1, type = float)
 parser.add_argument('-r', dest = 'rescale', help = 'rescaling factor for the displayed image', default = 1., type = float)
 parser.add_argument('-s', dest = 'step', help = 'display every s image', default = 1, type = int)
 
@@ -17,8 +17,4 @@
 if args.firstFrameNum is not None:
     firstFrameNum = args.firstFrameNum
 
-frameRate = -1
-if args.frameRate is not None:
-    frameRate = args.frameRate
-
-cvutils.playVideo(args.videoFilename, firstFrameNum, frameRate, rescale = args.rescale, step = args.step)
+cvutils.playVideo([args.videoFilename], None, [firstFrameNum], args.frameRate, rescale = args.rescale, step = args.step)