changeset 1242:4cd8ace3552f

major update for classification, allowing the use of neural network classification
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Wed, 07 Feb 2024 11:43:03 -0500
parents ab4c72b9475c
children 88eedf79f16a
files scripts/classify-objects.py scripts/dltrack.py scripts/manual-video-analysis.py scripts/polytracktopdtv.py trafficintelligence/cvutils.py trafficintelligence/moving.py trafficintelligence/storage.py
diffstat 7 files changed, 40 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/classify-objects.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/scripts/classify-objects.py	Wed Feb 07 11:43:03 2024 -0500
@@ -52,10 +52,13 @@
 carNorm = norm(classifierParams.meanVehicleSpeed, classifierParams.stdVehicleSpeed)
 pedNorm = norm(classifierParams.meanPedestrianSpeed, classifierParams.stdPedestrianSpeed)
 # numpy lognorm shape, loc, scale: shape for numpy is scale (std of the normal) and scale for numpy is exp(location) (loc=mean of the normal)
-bicLogNorm = lognorm(classifierParams.scaleCyclistSpeed, loc = 0., scale = np.exp(classifierParams.locationCyclistSpeed))
-speedProbabilities = {moving.userTypeNames[1]: lambda s: carNorm.pdf(s),
-                      moving.userTypeNames[2]: lambda s: pedNorm.pdf(s), 
-                      moving.userTypeNames[4]: lambda s: bicLogNorm.pdf(s)}
+cycLogNorm = lognorm(classifierParams.scaleCyclistSpeed, loc = 0., scale = np.exp(classifierParams.locationCyclistSpeed))
+speedProbabilities = {moving.userType2Num['car']: lambda s: carNorm.pdf(s),
+                      moving.userType2Num['pedestrian']: lambda s: pedNorm.pdf(s), 
+                      moving.userType2Num['cyclist']: lambda s: cycLogNorm.pdf(s)}
+if useYolo: # consider other user types
+    for i in [3, 5, 6, 7]:
+        speedProbabilities[i] = speedProbabilities[moving.userType2Num['car']]
 
 if args.plotSpeedDistribution:
     import matplotlib.pyplot as plt
@@ -106,7 +109,7 @@
             #if undistort:
             #    img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR)
             if useYolo:
-                results = yolo.predict(img, classes=list(moving.cocoTypeNames.keys()), verbose=False)
+                results = yolo.predict(img, conf = classifierParams.confidence, classes=list(moving.cocoTypeNames.keys()), verbose=False)
 
             for obj in objects[:]:
                 if obj.getFirstInstant() <= frameNum: # if images are skipped
@@ -116,15 +119,15 @@
 
             for obj in currentObjects[:]:
                 if obj.getLastInstant() <= frameNum:
-                    obj.classifyUserTypeHoGSVM(minSpeedEquiprobable = classifierParams.minSpeedEquiprobable, speedProbabilities = speedProbabilities, maxPercentUnknown = classifierParams.maxPercentUnknown)
+                    obj.classifyUserTypeHoGSVM(classifierParams.minSpeedEquiprobable, speedProbabilities, classifierParams.maxPercentUnknown)
                     pastObjects.append(obj)
                     currentObjects.remove(obj)
                 else:
                     if useYolo:
                         # if one feature falls in bike, it's a bike
                         # could one count all hits in various objects, or one takes majority at the instant?
-                        # obj.classifyUserTypeYoloAtInstant(img, frameNum, width, height, classifierParams.percentIncreaseCrop, classifierParams.percentIncreaseCrop, results[0].boxes)
-                        pass
+                        #print('yolo', len(results[0].boxes))
+                        obj.classifyUserTypeYoloAtInstant(frameNum, results[0].boxes)
                     else:
                         obj.classifyUserTypeHoGSVMAtInstant(img, frameNum, width, height, classifierParams.percentIncreaseCrop, classifierParams.percentIncreaseCrop, classifierParams.minNPixels, classifierParams.hogRescaleSize, classifierParams.hogNOrientations, classifierParams.hogNPixelsPerCell, classifierParams.hogNCellsPerBlock, classifierParams.hogBlockNorm)
                     if args.verbose:
@@ -132,7 +135,7 @@
         frameNum += 1
     
     for obj in currentObjects:
-        obj.classifyUserTypeHoGSVM(minSpeedEquiprobable = classifierParams.minSpeedEquiprobable, speedProbabilities = speedProbabilities, maxPercentUnknown = classifierParams.maxPercentUnknown)
+        obj.classifyUserTypeHoGSVM(classifierParams.minSpeedEquiprobable, speedProbabilities, classifierParams.maxPercentUnknown)
         pastObjects.append(obj)
     print('Saving user types')
     storage.setRoadUserTypes(databaseFilename, pastObjects)
--- a/scripts/dltrack.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/scripts/dltrack.py	Wed Feb 07 11:43:03 2024 -0500
@@ -25,6 +25,7 @@
 parser.add_argument('--display', dest = 'display', help = 'show the raw detection and tracking results', action = 'store_true')
 parser.add_argument('-f', dest = 'firstFrameNum', help = 'number of first frame number to process', type = int, default = 0)
 parser.add_argument('-l', dest = 'lastFrameNum', help = 'number of last frame number to process', type = int, default = float('Inf'))
+parser.add_argument('--conf', dest = 'confindence', help = 'object confidence threshold for detection', type = float, default = 0.25)
 parser.add_argument('--bike-prop', dest = 'bikeProportion', help = 'minimum proportion of time a person classified as bike or motorbike to be classified as cyclist', type = float, default = 0.2)
 parser.add_argument('--cyclist-iou', dest = 'cyclistIou', help = 'IoU threshold to associate a bike and ped bounding box', type = float, default = 0.15)
 parser.add_argument('--cyclist-match-prop', dest = 'cyclistMatchingProportion', help = 'minimum proportion of time a bike exists and is associated with a pedestrian to be merged as cyclist', type = float, default = 0.3)
--- a/scripts/manual-video-analysis.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/scripts/manual-video-analysis.py	Wed Feb 07 11:43:03 2024 -0500
@@ -29,9 +29,10 @@
                  'car',
                  'pedestrian',
                  'motorcycle',
-                 'bicycle',
+                 'cyclist',
                  'bus',
-                 'truck']
+                 'truck',
+                 'automated']
 class UserConfiguration(object):
     def __init__(self, name, keyNew, keyAddInstant, nAttributes):
         self.name = name
--- a/scripts/polytracktopdtv.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/scripts/polytracktopdtv.py	Wed Feb 07 11:43:03 2024 -0500
@@ -25,7 +25,7 @@
           "1"  -> "car"
           "2"  -> "pedestrians"
           "3"  -> "motorcycle"
-          "4"  -> "bicycle"
+          "4"  -> "cyclist"
           "5"  -> "bus"
           "6"  -> "truck"
           ... and other type if the objects_type table is defined in SQLite'''
@@ -38,7 +38,7 @@
         typeDict["1"] = "car"
         typeDict["2"] = "pedestrians"
         typeDict["3"] = "motorcycle"
-        typeDict["4"] = "bicycle"
+        typeDict["4"] = "cyclist"
         typeDict["5"] = "bus"
         typeDict["6"] = "truck"
         
--- a/trafficintelligence/cvutils.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/trafficintelligence/cvutils.py	Wed Feb 07 11:43:03 2024 -0500
@@ -365,7 +365,8 @@
                                 cv2.rectangle(img, (xCropMin, yCropMin), (xCropMax, yCropMax), cvBlue[colorType], 1)
                             objDescription = '{} '.format(obj.num)
                             if moving.userTypeNames[obj.userType] != 'unknown':
-                                objDescription += moving.userTypeNames[obj.userType][0].upper()
+                                s = moving.userTypeNames[obj.userType]
+                                objDescription += s[0].upper()+s[1]
                             if len(annotations) > 0: # if we loaded annotations, but there is no match
                                 if frameNum not in toMatches[obj.getNum()]:
                                     objDescription += " FA"
--- a/trafficintelligence/moving.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/trafficintelligence/moving.py	Wed Feb 07 11:43:03 2024 -0500
@@ -306,7 +306,7 @@
         return Point(projected[0], projected[1])
 
     def inRectangle(self, xmin, xmax, ymin, ymax):
-        return (xmin <= p.x <= xmax) and (ymin <= p.y <= ymax)
+        return (xmin <= self.x <= xmax) and (ymin <= self.y <= ymax)
     
     def inPolygon(self, polygon):
         '''Indicates if the point x, y is inside the polygon
@@ -1401,7 +1401,7 @@
                  'car',
                  'pedestrian',
                  'motorcycle',
-                 'bicycle',
+                 'cyclist',
                  'bus',
                  'truck',
                  'automated']
@@ -2081,28 +2081,30 @@
         croppedImg = cvutils.imageBox(img, self, instant, width, height, px, py, minNPixels)
         if croppedImg is not None and len(croppedImg) > 0:
             hog = cvutils.HOG(croppedImg, rescaleSize, orientations, pixelsPerCell, cellsPerBlock, blockNorm)
-            self.userTypes[instant] = self.appearanceClassifier.predict(hog.reshape(1,hog.size))
+            self.userTypes[instant] = int(self.appearanceClassifier.predict(hog.reshape(1,hog.size)))
         else:
             self.userTypes[instant] = userType2Num['unknown']
 
-    def classifyUserTypeYoloAtInstant(self, img, instant, width, height, px, py, minNPixels, bboxes):
-        '''Finds the user type based on where the feature fall in detected bboxes'''
+    def classifyUserTypeYoloAtInstant(self, instant, bboxes):
+        '''Finds the user type based on where the feature fall at instant in detected bboxes'''
         userTypes = []
         if self.hasFeatures():
             for f in self.getFeatures():
-                if f.existsAtInstant(frameNum):
-                    p = f.getPositionAtInstant(frameNum)
+                if f.existsAtInstant(instant):
+                    p = f.getPositionAtInstant(instant)
                     for box in bboxes:
-                        if box.id is not None:
-                            xyxy = box.xyxy[0].tolist()
-                            if p.inRectangle(xyxy[0], xyxy[1], xyxy[2], xyxy[3]):
-                                userTypes.append(moving.coco2Types[int(box.cls.item())])
+                        xyxy = box.xyxy[0].tolist()
+                        if p.inRectangle(xyxy[0], xyxy[2], xyxy[1], xyxy[3]):
+                            userTypes.append(coco2Types[int(box.cls.item())])
         if len(userTypes) > 0:
-            pass
+            if userType2Num['cyclist'] in userTypes:
+                self.userTypes[instant] = userType2Num['cyclist']
+            else:
+                self.userTypes[instant] = utils.mostCommon(userTypes)
         else:
             self.userTypes[instant] = userType2Num['unknown']
 
-    def classifyUserTypeHoGSVM(self, minSpeedEquiprobable = -1, speedProbabilities = None, aggregationFunc = median, maxPercentUnknown = 0.5):
+    def classifyUserTypeHoGSVM(self, minSpeedEquiprobable = -1, speedProbabilities = None, maxPercentUnknown = 0.5):
         '''Agregates SVM detections in each image and returns probability
         (proportion of instants with classification in each category)
 
@@ -2118,11 +2120,12 @@
         #     for t in self.getTimeInterval():
         #         if t not in self.userTypes:
         #             self.classifyUserTypeHoGSVMAtInstant(images[t], t, homography, width, height, px, py, minNPixels, rescaleSize, orientations, pixelsPerCell, cellsPerBlock)
+        
         # compute P(Speed|Class)
         if speedProbabilities is None or self.aggregatedSpeed < minSpeedEquiprobable: # equiprobable information from speed
-            userTypeProbabilities = {userType2Num['car']: 1., userType2Num['pedestrian']: 1., userType2Num['bicycle']: 1.}
+            userTypeProbabilities = {userTypeNum: 1. for userTypeNum in speedProbabilities}
         else:
-            userTypeProbabilities = {userType2Num[userTypename]: speedProbabilities[userTypename](self.aggregatedSpeed) for userTypename in speedProbabilities}
+            userTypeProbabilities = {userTypeNum: speedProbabilities[userTypeNum](self.aggregatedSpeed) for userTypeNum in speedProbabilities}
         # compute P(Class|Appearance)
         nInstantsUserType = {userTypeNum: 0 for userTypeNum in userTypeProbabilities}# number of instants the object is classified as userTypename
         nInstantsUserType[userType2Num['unknown']] = 0
@@ -2135,7 +2138,7 @@
         # class is the user type that maximizes usertype probabilities
         if nInstantsUserType[userType2Num['unknown']] >= maxPercentUnknown*self.length() and (speedProbabilities is None or self.aggregatedSpeed < minSpeedEquiprobable): # if no speed information and too many unknowns
             self.setUserType(userType2Num['unknown'])
-        else:
+        else: # if too many unknowns here, probas are just speed probas
             self.setUserType(utils.argmaxDict(userTypeProbabilities))
 
     def classifyUserTypeArea(self, areas, homography):
--- a/trafficintelligence/storage.py	Mon Feb 05 17:06:01 2024 -0500
+++ b/trafficintelligence/storage.py	Wed Feb 07 11:43:03 2024 -0500
@@ -1520,6 +1520,7 @@
         x = config.getint(self.sectionHeader, 'hog-ncells-block')
         self.hogNCellsPerBlock = (x, x)
         self.hogBlockNorm = config.get(self.sectionHeader, 'hog-block-norm')
+        self.confidence = config.getfloat(self.sectionHeader, 'confidence')
         
         self.speedAggregationMethod = config.get(self.sectionHeader, 'speed-aggregation-method')
         self.nFramesIgnoreAtEnds = config.getint(self.sectionHeader, 'nframes-ignore-at-ends')