Mercurial > hg > nsaunier > traffic-intelligence
comparison scripts/dltrack.py @ 1245:371c718e57d7
interface updates
| author | Nicolas Saunier <nicolas.saunier@polymtl.ca> |
|---|---|
| date | Thu, 08 Feb 2024 16:10:54 -0500 |
| parents | 4cd8ace3552f |
| children | 2397de73770d |
comparison
equal
deleted
inserted
replaced
| 1244:00b71da2baac | 1245:371c718e57d7 |
|---|---|
| 11 import cv2 | 11 import cv2 |
| 12 | 12 |
| 13 from trafficintelligence import cvutils, moving, storage, utils | 13 from trafficintelligence import cvutils, moving, storage, utils |
| 14 | 14 |
| 15 parser = argparse.ArgumentParser(description='The program tracks objects using the ultralytics models and trakcers.') | 15 parser = argparse.ArgumentParser(description='The program tracks objects using the ultralytics models and trakcers.') |
| 16 parser.add_argument('-i', dest = 'videoFilename', help = 'name of the video file', required = True) | 16 parser.add_argument('--cfg', dest = 'configFilename', help = 'name of the configuration file') |
| 17 parser.add_argument('-d', dest = 'databaseFilename', help = 'name of the Sqlite database file', required = True) | 17 parser.add_argument('-d', dest = 'databaseFilename', help = 'name of the Sqlite database file (overrides the configuration file)') |
| 18 parser.add_argument('-i', dest = 'videoFilename', help = 'name of the video file (overrides the configuration file)') | |
| 18 parser.add_argument('-m', dest = 'detectorFilename', help = 'name of the detection model file', required = True) | 19 parser.add_argument('-m', dest = 'detectorFilename', help = 'name of the detection model file', required = True) |
| 19 parser.add_argument('-t', dest = 'trackerFilename', help = 'name of the tracker file', required = True) | 20 parser.add_argument('-t', dest = 'trackerFilename', help = 'name of the tracker file', required = True) |
| 20 parser.add_argument('-o', dest = 'homographyFilename', help = 'filename of the homography matrix', default = 'homography.txt') | 21 parser.add_argument('-o', dest = 'homographyFilename', help = 'filename of the homography matrix', default = 'homography.txt') |
| 21 parser.add_argument('-k', dest = 'maskFilename', help = 'name of the mask file') | 22 parser.add_argument('-k', dest = 'maskFilename', help = 'name of the mask file') |
| 22 parser.add_argument('--undistort', dest = 'undistort', help = 'undistort the video', action = 'store_true') | 23 parser.add_argument('--undistort', dest = 'undistort', help = 'undistort the video', action = 'store_true') |
| 23 parser.add_argument('--intrinsic', dest = 'intrinsicCameraMatrixFilename', help = 'name of the intrinsic camera file') | 24 parser.add_argument('--intrinsic', dest = 'intrinsicCameraMatrixFilename', help = 'name of the intrinsic camera file') |
| 24 parser.add_argument('--distortion-coefficients', dest = 'distortionCoefficients', help = 'distortion coefficients', nargs = '*', type = float) | 25 parser.add_argument('--distortion-coefficients', dest = 'distortionCoefficients', help = 'distortion coefficients', nargs = '*', type = float) |
| 25 parser.add_argument('--display', dest = 'display', help = 'show the raw detection and tracking results', action = 'store_true') | 26 parser.add_argument('--display', dest = 'display', help = 'show the raw detection and tracking results', action = 'store_true') |
| 27 parser.add_argument('--no-image-coordinates', dest = 'notSavingImageCoordinates', help = 'not saving the raw detection and tracking results', action = 'store_true') | |
| 26 parser.add_argument('-f', dest = 'firstFrameNum', help = 'number of first frame number to process', type = int, default = 0) | 28 parser.add_argument('-f', dest = 'firstFrameNum', help = 'number of first frame number to process', type = int, default = 0) |
| 27 parser.add_argument('-l', dest = 'lastFrameNum', help = 'number of last frame number to process', type = int, default = float('Inf')) | 29 parser.add_argument('-l', dest = 'lastFrameNum', help = 'number of last frame number to process', type = int, default = float('Inf')) |
| 28 parser.add_argument('--conf', dest = 'confindence', help = 'object confidence threshold for detection', type = float, default = 0.25) | 30 parser.add_argument('--conf', dest = 'confindence', help = 'object confidence threshold for detection', type = float, default = 0.25) |
| 29 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) | 31 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) |
| 30 parser.add_argument('--cyclist-iou', dest = 'cyclistIou', help = 'IoU threshold to associate a bike and ped bounding box', type = float, default = 0.15) | 32 parser.add_argument('--cyclist-iou', dest = 'cyclistIou', help = 'IoU threshold to associate a bike and ped bounding box', type = float, default = 0.15) |
| 31 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) | 33 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) |
| 32 parser.add_argument('--max-temp-overal', dest = 'maxTemporalOverlap', help = 'maximum proportion of time to merge 2 bikes associated with same pedestrian', type = float, default = 0.05) | 34 parser.add_argument('--max-temp-overal', dest = 'maxTemporalOverlap', help = 'maximum proportion of time to merge 2 bikes associated with same pedestrian', type = float, default = 0.05) |
| 35 | |
| 33 args = parser.parse_args() | 36 args = parser.parse_args() |
| 37 params, videoFilename, databaseFilename, invHomography, intrinsicCameraMatrix, distortionCoefficients, undistortedImageMultiplication, undistort, firstFrameNum = storage.processVideoArguments(args) | |
| 38 | |
| 39 if args.intrinsicCameraMatrixFilename is not None: | |
| 40 intrinsicCameraMatrix = loadtxt(args.intrinsicCameraMatrixFilename) | |
| 41 if args.distortionCoefficients is not None: | |
| 42 distortionCoefficients = args.distortionCoefficients | |
| 43 if args.firstFrameNum is not None: | |
| 44 firstFrameNum = args.firstFrameNum | |
| 45 if args.lastFrameNum is not None: | |
| 46 lastFrameNum = args.lastFrameNum | |
| 34 | 47 |
| 35 # TODO add option to refine position with mask for vehicles | 48 # TODO add option to refine position with mask for vehicles |
| 49 # TODO work with optical flow (farneback or RAFT) https://pytorch.org/vision/main/models/raft.html | |
| 36 | 50 |
| 37 # use 2 x bytetrack track buffer to remove objects from existing ones | 51 # use 2 x bytetrack track buffer to remove objects from existing ones |
| 38 | 52 |
| 39 # Load a model | 53 # Load a model |
| 40 model = YOLO(args.detectorFilename) # seg yolov8x-seg.pt | 54 model = YOLO(args.detectorFilename) # seg yolov8x-seg.pt |
| 55 | 69 |
| 56 success, frame = capture.read() | 70 success, frame = capture.read() |
| 57 if not success: | 71 if not success: |
| 58 print('Input {} could not be read. Exiting'.format(args.videoFilename)) | 72 print('Input {} could not be read. Exiting'.format(args.videoFilename)) |
| 59 import sys; sys.exit() | 73 import sys; sys.exit() |
| 74 | |
| 60 results = model.track(frame, tracker=args.trackerFilename, classes=list(moving.cocoTypeNames.keys()), persist=True, verbose=False) | 75 results = model.track(frame, tracker=args.trackerFilename, classes=list(moving.cocoTypeNames.keys()), persist=True, verbose=False) |
| 61 # create object with user type and list of 3 features (bottom ones and middle) + projection | |
| 62 while capture.isOpened() and success and frameNum <= lastFrameNum: | 76 while capture.isOpened() and success and frameNum <= lastFrameNum: |
| 63 #for frameNum, result in enumerate(results): | |
| 64 result = results[0] | 77 result = results[0] |
| 65 if frameNum %10 == 0: | 78 if frameNum %10 == 0: |
| 66 print(frameNum, len(result.boxes), 'objects') | 79 print(frameNum, len(result.boxes), 'objects') |
| 67 for box in result.boxes: | 80 for box in result.boxes: |
| 68 #print(box.cls, box.id, box.xyxy) | 81 #print(box.cls, box.id, box.xyxy) |
| 101 obj.setUserType(utils.mostCommon(obj.userTypes)) # improve? mix with speed? | 114 obj.setUserType(utils.mostCommon(obj.userTypes)) # improve? mix with speed? |
| 102 | 115 |
| 103 # add quality control: avoid U-turns | 116 # add quality control: avoid U-turns |
| 104 | 117 |
| 105 # merge bikes and people | 118 # merge bikes and people |
| 106 twowheels = [num for num, obj in objects.items() if obj.getUserType() in (3,4)] | 119 twowheels = [num for num, obj in objects.items() if obj.getUserType() in (moving.userType2Num['motorcyclist'],moving.userType2Num['cyclist'])] |
| 107 pedestrians = [num for num, obj in objects.items() if obj.getUserType() == 2] | 120 pedestrians = [num for num, obj in objects.items() if obj.getUserType() == moving.userType2Num['pedestrian']] |
| 108 | 121 |
| 109 def mergeObjects(obj1, obj2): | 122 def mergeObjects(obj1, obj2): |
| 110 obj1.features = obj1.features+obj2.features | 123 obj1.features = obj1.features+obj2.features |
| 111 obj1.featureNumbers = obj1.featureNumbers+obj2.featureNumbers | 124 obj1.featureNumbers = obj1.featureNumbers+obj2.featureNumbers |
| 112 obj1.timeInterval = moving.TimeInterval(min(obj1.getFirstInstant(), obj2.getFirstInstant()), max(obj1.getLastInstant(), obj2.getLastInstant())) | 125 obj1.timeInterval = moving.TimeInterval(min(obj1.getFirstInstant(), obj2.getFirstInstant()), max(obj1.getLastInstant(), obj2.getLastInstant())) |
| 132 # before matching, scan for pedestrians with good non-overlapping temporal match with different bikes | 145 # before matching, scan for pedestrians with good non-overlapping temporal match with different bikes |
| 133 for pedInd in range(costs.shape[1]): | 146 for pedInd in range(costs.shape[1]): |
| 134 nMatchedBikes = (costs[:,pedInd] < -args.cyclistMatchingProportion).sum() | 147 nMatchedBikes = (costs[:,pedInd] < -args.cyclistMatchingProportion).sum() |
| 135 if nMatchedBikes == 0: # peds that have no bike matching: see if they have been classified as bikes sometimes | 148 if nMatchedBikes == 0: # peds that have no bike matching: see if they have been classified as bikes sometimes |
| 136 userTypeStats = Counter(obj.userTypes) | 149 userTypeStats = Counter(obj.userTypes) |
| 137 if (4 in userTypeStats or (3 in userTypeStats and 4 in userTypeStats and userTypeStats[3]<=userTypeStats[4])) and userTypeStats[3]+userTypeStats[4] > args.bikeProportion*userTypeStats.total(): # 3 is motorcycle and 4 is cyclist (verif if not turning all motorbike into cyclists) | 150 if (moving.userType2Num['cyclist'] in userTypeStats or (moving.userType2Num['motorcyclist'] in userTypeStats and moving.userType2Num['cyclist'] in userTypeStats and userTypeStats[moving.userType2Num['motorcyclist']]<=userTypeStats[moving.userType2Num['cyclist']])) and userTypeStats[moving.userType2Num['motorcyclist']]+userTypeStats[moving.userType2Num['cyclist']] > args.bikeProportion*userTypeStats.total(): # verif if not turning all motorbike into cyclists |
| 138 obj.setUserType(4) | 151 obj.setUserType(moving.userType2Num['cyclist']) |
| 139 elif nMatchedBikes > 1: # try to merge bikes first | 152 elif nMatchedBikes > 1: # try to merge bikes first |
| 140 twIndices = np.nonzero(costs[:,pedInd] < -args.cyclistMatchingProportion)[0] | 153 twIndices = np.nonzero(costs[:,pedInd] < -args.cyclistMatchingProportion)[0] |
| 141 # we have to compute temporal overlaps of all 2 wheels among themselves, then remove the ones with the most overlap (sum over column) one by one until there is little left | 154 # we have to compute temporal overlaps of all 2 wheels among themselves, then remove the ones with the most overlap (sum over column) one by one until there is little left |
| 142 nTwoWheels = len(twIndices) | 155 nTwoWheels = len(twIndices) |
| 143 twTemporalOverlaps = np.zeros((nTwoWheels,nTwoWheels)) | 156 twTemporalOverlaps = np.zeros((nTwoWheels,nTwoWheels)) |
