changeset 1241:ab4c72b9475c

work in progress
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Mon, 05 Feb 2024 17:06:01 -0500
parents bb14f919d1cb
children 4cd8ace3552f
files classifier.cfg scripts/classify-objects.py trafficintelligence/ml.py trafficintelligence/moving.py trafficintelligence/storage.py
diffstat 5 files changed, 65 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/classifier.cfg	Mon Feb 05 14:14:14 2024 -0500
+++ b/classifier.cfg	Mon Feb 05 17:06:01 2024 -0500
@@ -2,6 +2,8 @@
 pbv-svm-filename = modelPBV.xml
 # filename of the cyc/veh SVM classifier
 bv-svm-filename = modelBV.xml
+# filename of a Ultralytics-compatible model, eg Yolov8
+dl-filename = 
 # percent increase of the max of width and height of the bounding box of features extracted for classification
 percent-increase-crop = 0.2
 # min number of pixels in cropped image to classify by SVM
--- a/scripts/classify-objects.py	Mon Feb 05 14:14:14 2024 -0500
+++ b/scripts/classify-objects.py	Mon Feb 05 17:06:01 2024 -0500
@@ -5,6 +5,15 @@
 import numpy as np
 import cv2
 from scipy.stats import norm, lognorm
+from pathlib import Path
+
+try:
+    from ultralytics import YOLO
+    ultralyticsAvailable = True
+except ImportError:
+    #print('OpenCV library could not be loaded (video replay functions will not be available)') # TODO change to logging module
+    ultralyticsAvailable = False
+
 
 from trafficintelligence import cvutils, moving, ml, storage, utils
 
@@ -29,8 +38,15 @@
 if speedAggregationFunc is None:
     sys.exit()
 
-pedBikeCarSVM = ml.SVM_load(classifierParams.pedBikeCarSVMFilename)
-bikeCarSVM = ml.SVM_load(classifierParams.bikeCarSVMFilename)
+if ultralyticsAvailable and Path(classifierParams.dlFilename).is_file(): # use Yolo
+    pedBikeCarSVM = None
+    bikeCarSVM = None
+    yolo = YOLO(classifierParams.dlFilename, task='detect')
+    useYolo = True
+else:
+    useYolo = False
+    pedBikeCarSVM = ml.SVM_load(classifierParams.pedBikeCarSVMFilename)
+    bikeCarSVM = ml.SVM_load(classifierParams.bikeCarSVMFilename)
 
 # log logistic for ped and bik otherwise ((pedBeta/pedAlfa)*((sMean/pedAlfa)**(pedBeta-1)))/((1+(sMean/pedAlfa)**pedBeta)**2.)
 carNorm = norm(classifierParams.meanVehicleSpeed, classifierParams.stdVehicleSpeed)
@@ -61,8 +77,6 @@
 
 objects = storage.loadTrajectoriesFromSqlite(databaseFilename, 'object', args.nObjects, withFeatures = True)
 timeInterval = moving.TimeInterval.unionIntervals([obj.getTimeInterval() for obj in objects])
-if args.startFrame0:
-    timeInterval.first = 0
 
 capture = cv2.VideoCapture(videoFilename)
 width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
@@ -81,8 +95,7 @@
 if capture.isOpened():
     ret = True
     frameNum = timeInterval.first
-    if not args.startFrame0:
-        capture.set(cv2.CAP_PROP_POS_FRAMES, frameNum)
+    capture.set(cv2.CAP_PROP_POS_FRAMES, frameNum)
     lastFrameNum = timeInterval.last
 
     while ret and frameNum <= lastFrameNum:
@@ -91,7 +104,10 @@
             if frameNum%50 == 0:
                 print('frame number: {}'.format(frameNum))
             #if undistort:
-            #    img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR)            
+            #    img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR)
+            if useYolo:
+                results = yolo.predict(img, classes=list(moving.cocoTypeNames.keys()), verbose=False)
+
             for obj in objects[:]:
                 if obj.getFirstInstant() <= frameNum: # if images are skipped
                     obj.initClassifyUserTypeHoGSVM(speedAggregationFunc, pedBikeCarSVM, bikeCarSVM, classifierParams.maxPedestrianSpeed, classifierParams.maxCyclistSpeed, classifierParams.nFramesIgnoreAtEnds, invHomography, intrinsicCameraMatrix, distortionCoefficients)
@@ -99,12 +115,18 @@
                     objects.remove(obj)
 
             for obj in currentObjects[:]:
-                if obj.getLastInstant() <= frameNum:  # if images are skipped
+                if obj.getLastInstant() <= frameNum:
                     obj.classifyUserTypeHoGSVM(minSpeedEquiprobable = classifierParams.minSpeedEquiprobable, speedProbabilities = speedProbabilities, maxPercentUnknown = classifierParams.maxPercentUnknown)
                     pastObjects.append(obj)
                     currentObjects.remove(obj)
                 else:
-                    obj.classifyUserTypeHoGSVMAtInstant(img, frameNum, width, height, classifierParams.percentIncreaseCrop, classifierParams.percentIncreaseCrop, classifierParams.minNPixels, classifierParams.hogRescaleSize, classifierParams.hogNOrientations, classifierParams.hogNPixelsPerCell, classifierParams.hogNCellsPerBlock, classifierParams.hogBlockNorm)
+                    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
+                    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:
                         print('obj {}@{}: {}'.format(obj.getNum(), frameNum, moving.userTypeNames[obj.userTypes[frameNum]]))
         frameNum += 1
--- a/trafficintelligence/ml.py	Mon Feb 05 14:14:14 2024 -0500
+++ b/trafficintelligence/ml.py	Mon Feb 05 17:06:01 2024 -0500
@@ -70,6 +70,7 @@
             return svm
         else:
             print('Provided filename {} does not exist: model not loaded!'.format(filename))
+            return None
         
 #####################
 # Clustering
--- a/trafficintelligence/moving.py	Mon Feb 05 14:14:14 2024 -0500
+++ b/trafficintelligence/moving.py	Mon Feb 05 17:06:01 2024 -0500
@@ -305,6 +305,9 @@
         projected = cvutils.homographyProject(array([[self.x], [self.y]]), homography)
         return Point(projected[0], projected[1])
 
+    def inRectangle(self, xmin, xmax, ymin, ymax):
+        return (xmin <= p.x <= xmax) and (ymin <= p.y <= ymax)
+    
     def inPolygon(self, polygon):
         '''Indicates if the point x, y is inside the polygon
         (array of Nx2 coordinates of the polygon vertices)
@@ -2051,12 +2054,12 @@
         self.setUserType(utils.argmaxDict(userTypeProbabilities))
         return userTypeProbabilities
 
-    def initClassifyUserTypeHoGSVM(self, aggregationFunc, pedBikeCarSVM, bikeCarSVM = None, pedBikeSpeedTreshold = float('Inf'), bikeCarSpeedThreshold = float('Inf'), nInstantsIgnoredAtEnds = 0, homography = None, intrinsicCameraMatrix = None, distortionCoefficients = None):
+    def initClassifyUserTypeHoGSVM(self, aggregationFunc, pedBikeCarSVM, bikeCarSVM = None, pedBikeSpeedThreshold = float('Inf'), bikeCarSpeedThreshold = float('Inf'), nInstantsIgnoredAtEnds = 0, homography = None, intrinsicCameraMatrix = None, distortionCoefficients = None):
         '''Initializes the data structures for classification
 
         TODO? compute speed for longest feature?'''
         self.aggregatedSpeed = aggregationFunc(self.getSpeeds(nInstantsIgnoredAtEnds))
-        if self.aggregatedSpeed < pedBikeSpeedTreshold or bikeCarSVM is None:
+        if self.aggregatedSpeed < pedBikeSpeedThreshold or bikeCarSVM is None:
             self.appearanceClassifier = pedBikeCarSVM
         elif self.aggregatedSpeed < bikeCarSpeedThreshold:
             self.appearanceClassifier = bikeCarSVM
@@ -2082,7 +2085,24 @@
         else:
             self.userTypes[instant] = userType2Num['unknown']
 
-    def classifyUserTypeHoGSVM(self, pedBikeCarSVM = None, width = 0, height = 0, homography = None, images = None, bikeCarSVM = None, pedBikeSpeedTreshold = float('Inf'), bikeCarSpeedThreshold = float('Inf'), minSpeedEquiprobable = -1, speedProbabilities = None, aggregationFunc = median, maxPercentUnknown = 0.5, nInstantsIgnoredAtEnds = 0, px = 0.2, py = 0.2, minNPixels = 800, rescaleSize = (64, 64), orientations = 9, pixelsPerCell = (8,8), cellsPerBlock = (2,2)):
+    def classifyUserTypeYoloAtInstant(self, img, instant, width, height, px, py, minNPixels, bboxes):
+        '''Finds the user type based on where the feature fall in detected bboxes'''
+        userTypes = []
+        if self.hasFeatures():
+            for f in self.getFeatures():
+                if f.existsAtInstant(frameNum):
+                    p = f.getPositionAtInstant(frameNum)
+                    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())])
+        if len(userTypes) > 0:
+            pass
+        else:
+            self.userTypes[instant] = userType2Num['unknown']
+
+    def classifyUserTypeHoGSVM(self, minSpeedEquiprobable = -1, speedProbabilities = None, aggregationFunc = median, maxPercentUnknown = 0.5):
         '''Agregates SVM detections in each image and returns probability
         (proportion of instants with classification in each category)
 
@@ -2090,14 +2110,14 @@
         With default parameters, the general (ped-bike-car) classifier will be used
 
         Considered categories are the keys of speedProbabilities'''
-        if not hasattr(self, 'aggregatedSpeed') or not hasattr(self, 'userTypes'):
-            print('Initializing the data structures for classification by HoG-SVM')
-            self.initClassifyUserTypeHoGSVM(aggregationFunc, pedBikeCarSVM, bikeCarSVM, pedBikeSpeedTreshold, bikeCarSpeedThreshold, nInstantsIgnoredAtEnds)
+        #if not hasattr(self, 'aggregatedSpeed') or not hasattr(self, 'userTypes'):
+        #    print('Initializing the data structures for classification by HoG-SVM')
+        #    self.initClassifyUserTypeHoGSVM(aggregationFunc, pedBikeCarSVM, bikeCarSVM, pedBikeSpeedTreshold, bikeCarSpeedThreshold, nInstantsIgnoredAtEnds)
 
-        if len(self.userTypes) != self.length() and images is not None: # if classification has not been done previously
-            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)
+        # if len(self.userTypes) != self.length() and images is not None: # if classification has not been done previously
+        #     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.}
--- a/trafficintelligence/storage.py	Mon Feb 05 14:14:14 2024 -0500
+++ b/trafficintelligence/storage.py	Mon Feb 05 17:06:01 2024 -0500
@@ -1509,6 +1509,7 @@
 
         self.pedBikeCarSVMFilename = utils.getRelativeFilename(parentPath, config.get(self.sectionHeader, 'pbv-svm-filename'))
         self.bikeCarSVMFilename = utils.getRelativeFilename(parentPath, config.get(self.sectionHeader, 'bv-svm-filename'))
+        self.dlFilename = utils.getRelativeFilename(parentPath, config.get(self.sectionHeader, 'dl-filename'))
         self.percentIncreaseCrop = config.getfloat(self.sectionHeader, 'percent-increase-crop')
         self.minNPixels = config.getint(self.sectionHeader, 'min-npixels-crop')
         x = config.getint(self.sectionHeader, 'hog-rescale-size')