@@ -102,6 +102,29 @@ def embedding_distance(tracks, detections, metric='cosine'):
102
102
raise NotImplementedError
103
103
return cost_matrix
104
104
105
+ def nearest_embedding_distance (tracks , detections , metric = 'cosine' ):
106
+ """
107
+ different from embedding distance, this func calculate the
108
+ nearest distance among all track history features and detections
109
+
110
+ tracks: list[STrack]
111
+ detections: list[STrack]
112
+ metric: str, cosine or euclidean
113
+
114
+ return:
115
+ cost_matrix, np.ndarray, shape(len(tracks), len(detections))
116
+ """
117
+ cost_matrix = np .zeros ((len (tracks ), len (detections )))
118
+ det_features = np .asarray ([det .features [- 1 ] for det in detections ])
119
+
120
+ for row , track in enumerate (tracks ):
121
+ track_history_features = np .asarray (track .features )
122
+ dist = 1. - cal_cosine_distance (track_history_features , det_features )
123
+ dist = dist .min (axis = 0 )
124
+ cost_matrix [row , :] = dist
125
+
126
+ return cost_matrix
127
+
105
128
def ecu_iou_distance (tracks , detections , img0_shape ):
106
129
"""
107
130
combine eculidian center-point distance and iou distance
@@ -189,90 +212,70 @@ def fuse_motion(kf, cost_matrix, tracks, detections, only_position=False, lambda
189
212
return cost_matrix
190
213
191
214
192
- """
193
- distance metric that combines multi-frame info
194
- used in StrongSORT
195
- TODO: use in DeepSORT
196
- """
197
-
198
- class NearestNeighborDistanceMetric (object ):
215
+ def matching_cascade (
216
+ distance_metric , matching_thresh , cascade_depth , tracks , detections ,
217
+ track_indices = None , detection_indices = None ):
199
218
"""
200
- A nearest neighbor distance metric that, for each target, returns
201
- the closest distance to any sample that has been observed so far.
202
-
203
- Parameters
204
- ----------
205
- metric : str
206
- Either "euclidean" or "cosine".
207
- matching_threshold: float
208
- The matching threshold. Samples with larger distance are considered an
209
- invalid match.
210
- budget : Optional[int]
211
- If not None, fix samples per class to at most this number. Removes
212
- the oldest samples when the budget is reached.
213
-
214
- Attributes
215
- ----------
216
- samples : Dict[int -> List[ndarray]]
217
- A dictionary that maps from target identities to the list of samples
218
- that have been observed so far.
219
-
219
+ Run matching cascade in DeepSORT
220
+
221
+ distance_metirc: function that calculate the cost matrix
222
+ matching_thresh: float, Associations with cost larger than this value are disregarded.
223
+ cascade_path: int, equal to max_age of a tracklet
224
+ tracks: List[STrack], current tracks
225
+ detections: List[STrack], current detections
226
+ track_indices: List[int], tracks that will be calculated, Default None
227
+ detection_indices: List[int], detections that will be calculated, Default None
228
+
229
+ return:
230
+ matched pair, unmatched tracks, unmatced detections: List[int], List[int], List[int]
220
231
"""
232
+ if track_indices is None :
233
+ track_indices = list (range (len (tracks )))
234
+ if detection_indices is None :
235
+ detection_indices = list (range (len (detections )))
221
236
222
- def __init__ (self , metric , matching_threshold , budget = None ):
223
- if metric == "euclidean" :
224
- self ._metric = cal_eculidian_distance
225
- elif metric == "cosine" :
226
- self ._metric = cal_cosine_distance
227
- else :
228
- raise ValueError (
229
- "Invalid metric; must be either 'euclidean' or 'cosine'" )
230
- self .matching_threshold = matching_threshold
231
- self .budget = budget
232
- self .samples = {}
233
-
234
- def partial_fit (self , features , targets , active_targets ):
235
- """Update the distance metric with new data.
236
-
237
- Parameters
238
- ----------
239
- features : ndarray
240
- An NxM matrix of N features of dimensionality M.
241
- targets : ndarray
242
- An integer array of associated target identities.
243
- active_targets : List[int]
244
- A list of targets that are currently present in the scene.
237
+ detections_to_match = detection_indices
238
+ matches = []
245
239
240
+ for level in range (cascade_depth ):
246
241
"""
247
- for feature , target in zip (features , targets ):
248
- self .samples .setdefault (target , []).append (feature )
249
- if self .budget is not None :
250
- self .samples [target ] = self .samples [target ][- self .budget :]
251
- self .samples = {k : self .samples [k ] for k in active_targets }
252
-
253
- def distance (self , features , targets ):
254
- """Compute distance between features and targets.
255
-
256
- Parameters
257
- ----------
258
- features : ndarray
259
- An NxM matrix of N features of dimensionality M.
260
- targets : List[int]
261
- A list of targets to match the given `features` against.
262
-
263
- Returns
264
- -------
265
- ndarray
266
- Returns a cost matrix of shape len(targets), len(features), where
267
- element (i, j) contains the closest squared distance between
268
- `targets[i]` and `features[j]`.
269
-
242
+ match new track with detection firstly
270
243
"""
271
- cost_matrix = np .zeros ((len (targets ), len (features )))
272
- for i , target in enumerate (targets ):
273
- cost_matrix [i , :] = self ._metric (self .samples [target ], features )
274
- return cost_matrix
244
+ if not len (detections_to_match ): # No detections left
245
+ break
246
+
247
+ track_indices_l = [
248
+ k for k in track_indices
249
+ if tracks [k ].time_since_update == 1 + level
250
+ ] # filter tracks whose age is equal to level + 1 (The age of Newest track = 1)
251
+
252
+ if not len (track_indices_l ): # Nothing to match at this level
253
+ continue
254
+
255
+ # tracks and detections which will be mathcted in current level
256
+ track_l = [tracks [idx ] for idx in track_indices_l ] # List[STrack]
257
+ det_l = [detections [idx ] for idx in detections_to_match ] # List[STrack]
258
+
259
+ # calculate the cost matrix
260
+ cost_matrix = distance_metric (track_l , det_l )
261
+
262
+ # solve the linear assignment problem
263
+ matched_row_col , umatched_row , umatched_col = \
264
+ linear_assignment (cost_matrix , matching_thresh )
265
+
266
+ for row , col in matched_row_col : # for those who matched
267
+ matches .append ((track_indices_l [row ], detections_to_match [col ]))
268
+
269
+ umatched_detecion_l = [] # current detections not matched
270
+ for col in umatched_col : # for detections not matched
271
+ umatched_detecion_l .append (detections_to_match [col ])
272
+
273
+ detections_to_match = umatched_detecion_l # update detections to match for next level
274
+ unmatched_tracks = list (set (track_indices ) - set (k for k , _ in matches ))
275
+
276
+ return matches , unmatched_tracks , detections_to_match
275
277
278
+
276
279
277
280
"""
278
281
funcs to cal similarity, copied from UAVMOT
0 commit comments