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