Mercurial > hg > nsaunier > traffic-intelligence
comparison scripts/dltrack.py @ 1271:b2f90cada58f
removed complex merging of bikes and peds, may result in more fragmentation
| author | Nicolas Saunier <nicolas.saunier@polymtl.ca> |
|---|---|
| date | Fri, 14 Jun 2024 15:56:01 -0400 |
| parents | 20a5e1292321 |
| children | 8e61ff3cd503 |
comparison
equal
deleted
inserted
replaced
| 1270:20a5e1292321 | 1271:b2f90cada58f |
|---|---|
| 36 parser.add_argument('-l', dest = 'lastFrameNum', help = 'number of last frame number to process', type = int, default = inf) | 36 parser.add_argument('-l', dest = 'lastFrameNum', help = 'number of last frame number to process', type = int, default = inf) |
| 37 parser.add_argument('--conf', dest = 'confidence', help = 'object confidence threshold for detection', type = float, default = 0.25) | 37 parser.add_argument('--conf', dest = 'confidence', help = 'object confidence threshold for detection', type = float, default = 0.25) |
| 38 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) | 38 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) |
| 39 parser.add_argument('--cyclist-iou', dest = 'cyclistIou', help = 'IoU threshold to associate a bike and ped bounding box', type = float, default = 0.15) | 39 parser.add_argument('--cyclist-iou', dest = 'cyclistIou', help = 'IoU threshold to associate a bike and ped bounding box', type = float, default = 0.15) |
| 40 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) | 40 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) |
| 41 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) | 41 #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) |
| 42 | 42 |
| 43 args = parser.parse_args() | 43 args = parser.parse_args() |
| 44 params, videoFilename, databaseFilename, homography, invHomography, intrinsicCameraMatrix, distortionCoefficients, undistortedImageMultiplication, undistort, firstFrameNum = storage.processVideoArguments(args) | 44 params, videoFilename, databaseFilename, homography, invHomography, intrinsicCameraMatrix, distortionCoefficients, undistortedImageMultiplication, undistort, firstFrameNum = storage.processVideoArguments(args) |
| 45 | 45 |
| 46 if args.homographyFilename is not None: | 46 if args.homographyFilename is not None: |
| 63 mask = cv2.imread(params.maskFilename, cv2.IMREAD_GRAYSCALE) | 63 mask = cv2.imread(params.maskFilename, cv2.IMREAD_GRAYSCALE) |
| 64 else: | 64 else: |
| 65 mask = None | 65 mask = None |
| 66 if params is not None: | 66 if params is not None: |
| 67 smoothingHalfWidth = params.smoothingHalfWidth | 67 smoothingHalfWidth = params.smoothingHalfWidth |
| 68 minObjectDuration = params.minFeatureTime | |
| 68 else: | 69 else: |
| 69 smoothingHalfWidth = None | 70 smoothingHalfWidth = None |
| 71 minObjectDuration = 3 | |
| 70 | 72 |
| 71 # TODO use mask, remove short objects, smooth | 73 # TODO use mask, remove short objects, smooth |
| 72 | 74 |
| 73 # TODO add option to refine position with mask for vehicles, to save different positions | 75 # TODO add option to refine position with mask for vehicles, to save different positions |
| 74 # TODO work with optical flow (farneback or RAFT) https://pytorch.org/vision/main/models/raft.html | 76 # TODO work with optical flow (farneback or RAFT) https://pytorch.org/vision/main/models/raft.html |
| 140 cv2.destroyAllWindows() | 142 cv2.destroyAllWindows() |
| 141 | 143 |
| 142 # classification | 144 # classification |
| 143 shortObjectNumbers = [] | 145 shortObjectNumbers = [] |
| 144 for num, obj in objects.items(): | 146 for num, obj in objects.items(): |
| 145 if obj.length() < 3: | 147 if obj.length() < minObjectDuration: |
| 146 shortObjectNumbers.append(num) | 148 shortObjectNumbers.append(num) |
| 147 else: | 149 else: |
| 148 obj.setUserType(utils.mostCommon(obj.userTypes)) # improve? mix with speed? | 150 obj.setUserType(utils.mostCommon(obj.userTypes)) # improve? mix with speed? |
| 149 for num in shortObjectNumbers: | 151 for num in shortObjectNumbers: |
| 150 del objects[num] | 152 del objects[num] |
| 179 | 181 |
| 180 if costs.size > 0: | 182 if costs.size > 0: |
| 181 # before matching, scan for pedestrians with good non-overlapping temporal match with different bikes | 183 # before matching, scan for pedestrians with good non-overlapping temporal match with different bikes |
| 182 for pedInd in range(costs.shape[1]): | 184 for pedInd in range(costs.shape[1]): |
| 183 nMatchedBikes = (costs[:,pedInd] < -args.cyclistMatchingProportion).sum() | 185 nMatchedBikes = (costs[:,pedInd] < -args.cyclistMatchingProportion).sum() |
| 184 if nMatchedBikes == 0: # peds that have no bike matching: see if they have been classified as bikes sometimes | 186 if nMatchedBikes == 0: # peds that have no bike matching: see if they have been classified as bikes sometimes (more than args.bikeProportion) |
| 185 userTypeStats = Counter(obj.userTypes) | 187 userTypeStats = Counter(obj.userTypes) |
| 186 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 | 188 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 |
| 187 obj.setUserType(moving.userType2Num['cyclist']) | 189 obj.setUserType(moving.userType2Num['cyclist']) |
| 188 elif nMatchedBikes > 1: # try to merge bikes first | 190 # elif nMatchedBikes > 1: # try to merge bikes first |
| 189 twIndices = np.nonzero(costs[:,pedInd] < -args.cyclistMatchingProportion)[0] | 191 # twIndices = np.nonzero(costs[:,pedInd] < -args.cyclistMatchingProportion)[0] |
| 190 # 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 | 192 # # 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 |
| 191 nTwoWheels = len(twIndices) | 193 # nTwoWheels = len(twIndices) |
| 192 twTemporalOverlaps = np.zeros((nTwoWheels,nTwoWheels)) | 194 # twTemporalOverlaps = np.zeros((nTwoWheels,nTwoWheels)) |
| 193 for i in range(nTwoWheels): | 195 # for i in range(nTwoWheels): |
| 194 for j in range(i): | 196 # for j in range(i): |
| 195 twi = objects[twowheels[twIndices[i]]] | 197 # twi = objects[twowheels[twIndices[i]]] |
| 196 twj = objects[twowheels[twIndices[j]]] | 198 # twj = objects[twowheels[twIndices[j]]] |
| 197 twTemporalOverlaps[i,j] = len(set(twi.bboxes).intersection(set(twj.bboxes)))/max(len(twi.bboxes), len(twj.bboxes)) | 199 # twTemporalOverlaps[i,j] = len(set(twi.bboxes).intersection(set(twj.bboxes)))/max(len(twi.bboxes), len(twj.bboxes)) |
| 198 #twTemporalOverlaps[j,i] = twTemporalOverlaps[i,j] | 200 # #twTemporalOverlaps[j,i] = twTemporalOverlaps[i,j] |
| 199 tw2merge = list(range(nTwoWheels)) | 201 # tw2merge = list(range(nTwoWheels)) |
| 200 while len(tw2merge)>0 and (twTemporalOverlaps[np.ix_(tw2merge, tw2merge)] > args.maxTemporalOverlap).sum(0).max() >= 2: | 202 # while len(tw2merge)>0 and (twTemporalOverlaps[np.ix_(tw2merge, tw2merge)] > args.maxTemporalOverlap).sum(0).max() >= 2: |
| 201 i = (twTemporalOverlaps[np.ix_(tw2merge, tw2merge)] > args.maxTemporalOverlap).sum(0).argmax() | 203 # i = (twTemporalOverlaps[np.ix_(tw2merge, tw2merge)] > args.maxTemporalOverlap).sum(0).argmax() |
| 202 del tw2merge[i] | 204 # del tw2merge[i] |
| 203 twIndices = [twIndices[i] for i in tw2merge] | 205 # twIndices = [twIndices[i] for i in tw2merge] |
| 204 tw1 = objects[twowheels[twIndices[0]]] | 206 # tw1 = objects[twowheels[twIndices[0]]] |
| 205 twCost = costs[twIndices[0],:]*tw1.nBBoxes | 207 # twCost = costs[twIndices[0],:]*tw1.nBBoxes |
| 206 nBBoxes = tw1.nBBoxes | 208 # nBBoxes = tw1.nBBoxes |
| 207 for twInd in twIndices[1:]: | 209 # for twInd in twIndices[1:]: |
| 208 mergeObjects(tw1, objects[twowheels[twInd]]) | 210 # mergeObjects(tw1, objects[twowheels[twInd]]) |
| 209 twCost = twCost + costs[twInd,:]*objects[twowheels[twInd]].nBBoxes | 211 # twCost = twCost + costs[twInd,:]*objects[twowheels[twInd]].nBBoxes |
| 210 nBBoxes += objects[twowheels[twInd]].nBBoxes | 212 # nBBoxes += objects[twowheels[twInd]].nBBoxes |
| 211 twIndicesToKeep = list(range(costs.shape[0])) | 213 # twIndicesToKeep = list(range(costs.shape[0])) |
| 212 for twInd in twIndices[1:]: | 214 # for twInd in twIndices[1:]: |
| 213 twIndicesToKeep.remove(twInd) | 215 # twIndicesToKeep.remove(twInd) |
| 214 del objects[twowheels[twInd]] | 216 # del objects[twowheels[twInd]] |
| 215 twowheels = [twowheels[i] for i in twIndicesToKeep] | 217 # twowheels = [twowheels[i] for i in twIndicesToKeep] |
| 216 costs = costs[twIndicesToKeep,:] | 218 # costs = costs[twIndicesToKeep,:] |
| 217 | 219 |
| 218 twIndices, matchingPedIndices = linear_sum_assignment(costs) | 220 twIndices, matchingPedIndices = linear_sum_assignment(costs) |
| 219 for twInd, pedInd in zip(twIndices, matchingPedIndices): # caution indices in the cost matrix | 221 for twInd, pedInd in zip(twIndices, matchingPedIndices): # caution indices in the cost matrix |
| 220 if -costs[twInd, pedInd] >= args.cyclistMatchingProportion: | 222 if -costs[twInd, pedInd] >= args.cyclistMatchingProportion: |
| 221 tw = objects[twowheels[twInd]] | 223 tw = objects[twowheels[twInd]] |
| 222 ped = objects[pedestrians[pedInd]] | 224 ped = objects[pedestrians[pedInd]] |
| 223 mergeObjects(tw, ped) | 225 mergeObjects(tw, ped) |
| 224 del objects[pedestrians[pedInd]] | 226 del objects[pedestrians[pedInd]] |
| 227 # link ped to each assigned bike, remove bike from cost (and ped is temporal match is high) | |
| 228 | |
| 225 #TODO Verif overlap piéton vélo : si long hors overlap, changement mode (trouver exemples) | 229 #TODO Verif overlap piéton vélo : si long hors overlap, changement mode (trouver exemples) |
| 226 | 230 # TODO continue assigning if leftover bikes (if non temporally overlapping with existing bikes assigned to ped) |
| 231 | |
| 227 # interpolate and save image coordinates | 232 # interpolate and save image coordinates |
| 228 for num, obj in objects.items(): | 233 for num, obj in objects.items(): |
| 229 for f in obj.getFeatures(): | 234 for f in obj.getFeatures(): |
| 230 if f.length() != len(f.tmpPositions): # interpolate | 235 if f.length() != len(f.tmpPositions): # interpolate |
| 231 f.positions = moving.Trajectory.fromPointDict(f.tmpPositions) | 236 f.positions = moving.Trajectory.fromPointDict(f.tmpPositions) |
| 232 else: | 237 else: |
| 233 f.positions = moving.Trajectory.fromPointList(list(f.tmpPositions.values())) | 238 f.positions = moving.Trajectory.fromPointList(list(f.tmpPositions.values())) |
| 234 if not args.notSavingImageCoordinates: | 239 if not args.notSavingImageCoordinates: |
| 235 storage.saveTrajectoriesToSqlite(utils.removeExtension(args.databaseFilename)+'-bb.sqlite', list(objects.values()), 'object') | 240 storage.saveTrajectoriesToSqlite(utils.removeExtension(args.databaseFilename)+'-bb.sqlite', list(objects.values()), 'object') |
| 236 # project, smooth and save | 241 # project and smooth |
| 237 for num, obj in objects.items(): | 242 for num, obj in objects.items(): |
| 238 features = obj.getFeatures() | 243 features = obj.getFeatures() |
| 239 # possible to save bottom pedestrians? not consistent with other users | 244 # possible to save bottom pedestrians? not consistent with other users |
| 240 # if moving.userTypeNames[obj.getUserType()] == 'pedestrian': | 245 # if moving.userTypeNames[obj.getUserType()] == 'pedestrian': |
| 241 # assert len(features) == 2 | 246 # assert len(features) == 2 |
| 249 t.append(moving.Point.agg(points, np.mean).aslist()) | 254 t.append(moving.Point.agg(points, np.mean).aslist()) |
| 250 #t = sum([f.getPositions().asArray() for f in features])/len(features) | 255 #t = sum([f.getPositions().asArray() for f in features])/len(features) |
| 251 #t = (moving.Trajectory.add(t1, t2)*0.5).asArray() | 256 #t = (moving.Trajectory.add(t1, t2)*0.5).asArray() |
| 252 projected = cvutils.imageToWorldProject(np.array(t).T, intrinsicCameraMatrix, distortionCoefficients, homography) | 257 projected = cvutils.imageToWorldProject(np.array(t).T, intrinsicCameraMatrix, distortionCoefficients, homography) |
| 253 featureNum = features[0].getNum() | 258 featureNum = features[0].getNum() |
| 254 obj.features=[moving.MovingObject(featureNum, obj.getTimeInterval(), moving.Trajectory(projected.tolist()))] | 259 feature = moving.MovingObject(featureNum, obj.getTimeInterval(), moving.Trajectory(projected.tolist())) |
| 260 if smoothingHalfWidth is not None: # smoothing | |
| 261 feature.smoothPositions(smoothingHalfWidth, replace = True)#f.positions = f.getPositions().filterMovingWindow(smoothingHalfWidth) | |
| 262 feature.computeVelocities() | |
| 263 obj.features=[feature] | |
| 255 obj.featureNumbers = [featureNum] | 264 obj.featureNumbers = [featureNum] |
| 256 if smoothingHalfWidth is not None: # smoothing | 265 #saving |
| 257 for num, obj in objects.items(): | |
| 258 for f in obj.getFeatures(): | |
| 259 f.smoothPositions(smoothingHalfWidth, replace = True)#f.positions = f.getPositions().filterMovingWindow(smoothingHalfWidth) | |
| 260 f.computeVelocities() | |
| 261 storage.saveTrajectoriesToSqlite(args.databaseFilename, list(objects.values()), 'object') | 266 storage.saveTrajectoriesToSqlite(args.databaseFilename, list(objects.values()), 'object') |
| 262 | 267 |
| 263 | 268 |
| 264 | 269 |
| 265 # todo save bbox and mask to study localization / representation | 270 # todo save bbox and mask to study localization / representation |
