Skip to content

Commit c774ee0

Browse files
committed
modify deepsort
1 parent 97fc377 commit c774ee0

File tree

7 files changed

+136
-95
lines changed

7 files changed

+136
-95
lines changed

run_yolov7.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ CUDA_VISIBLE_DEVICES=0 python train_aux.py --dataset mot17 --workers 8 --device
6060

6161
track:
6262
python tracker/track.py --dataset mot17 --data_format yolo --tracker sort --model_path runs/train/yolov7-w6-custom3/weights/best.pt --save_images
63-
python tracker/track.py --dataset mot17 --data_format yolo --tracker bytetrack --model_path weights/best.pt --save_images
63+
python tracker/track.py --dataset mot17 --data_format yolo --tracker bytetrack --model_path weights/best.pt --save_images
64+
python tracker/track.py --dataset mot17 --data_format yolo --tracker deepsort --model_path weights/best.pt --save_images
169 Bytes
Binary file not shown.
1.36 KB
Binary file not shown.
-245 Bytes
Binary file not shown.

tracker/basetrack.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def mark_removed(self):
7373
"""
7474
class STrack(BaseTrack):
7575
def __init__(self, cls, tlwh, score, kalman_format='default',
76-
feature=None, use_avg_of_feature=True) -> None:
76+
feature=None, use_avg_of_feature=True, store_features_budget=100) -> None:
7777
"""
7878
cls: category of this obj
7979
tlwh: positoin score: conf score
@@ -95,6 +95,7 @@ def __init__(self, cls, tlwh, score, kalman_format='default',
9595
self.time_since_update = None
9696

9797
self.features = []
98+
self.store_features_budget = store_features_budget
9899
self.has_feature = True if feature is not None else False
99100
self.use_avg_of_feature = use_avg_of_feature
100101
if feature is not None:
@@ -230,6 +231,8 @@ def activate(self, frame_id):
230231
self.frame_id = frame_id
231232
self.start_frame = frame_id
232233

234+
self.time_since_update = 0
235+
233236
def predict(self):
234237
"""
235238
kalman predict step
@@ -254,6 +257,8 @@ def multi_predict(stracks, kalman):
254257
stracks[i].mean = mean
255258
stracks[i].cov = cov
256259

260+
for strack in stracks: strack.time_since_update += 1
261+
257262
def re_activate(self, new_track, frame_id, new_id=False):
258263
"""
259264
reactivate a lost track
@@ -275,6 +280,8 @@ def re_activate(self, new_track, frame_id, new_id=False):
275280
self.track_id = self.next_id()
276281
self.score = new_track.score
277282

283+
self.time_since_update = 0
284+
278285
def update(self, new_track, frame_id):
279286
"""
280287
update a track
@@ -311,12 +318,14 @@ def update(self, new_track, frame_id):
311318
self.features = [smooth_feat] # as new feature
312319
else:
313320
self.features.append(feature)
321+
self.features = self.features[-self.store_features_budget: ]
314322

315323

316324
# update status
317325
self.state = TrackState.Tracked
318326
self.is_activated = True
319327

328+
self.time_since_update = 0
320329

321330

322331
"""

tracker/deepsort.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import numpy as np
33
from basetrack import TrackState, STrack, BaseTracker
4-
from kalman_filter import KalmanFilter, NaiveKalmanFilter
4+
from kalman_filter import KalmanFilter, NaiveKalmanFilter, chi2inv95
55
from reid_models.deepsort_reid import Extractor
66
import matching
77
import torch
@@ -39,6 +39,43 @@ def get_feature(self, tlbrs, ori_img):
3939
features = np.array([])
4040
return features
4141

42+
def gate_cost_matrix(self, cost_matrix, tracks, dets, max_apperance_thresh=0.15, gated_cost=1e5, only_position=False):
43+
"""
44+
gate cost matrix by calculating the Kalman state distance and constrainted by
45+
0.95 confidence interval of x2 distribution
46+
47+
cost_matrix: np.ndarray, shape (len(tracks), len(dets))
48+
tracks: List[STrack]
49+
dets: List[STrack]
50+
gated_cost: a very largt const to infeasible associations
51+
only_position: use [xc, yc, a, h] as state vector or only use [xc, yc]
52+
53+
return:
54+
updated cost_matirx, np.ndarray
55+
"""
56+
gating_dim = 2 if only_position else 4
57+
gating_threshold = chi2inv95[gating_dim]
58+
measurements = np.asarray([STrack.tlwh2xyah(det.tlwh) for det in dets]) # (len(dets), 4)
59+
60+
cost_matrix[cost_matrix > max_apperance_thresh] = gated_cost
61+
for row, track in enumerate(tracks):
62+
gating_distance = self.kalman.gating_distance(
63+
track.mean, track.cov, measurements, only_position
64+
)
65+
cost_matrix[row, gating_distance > gating_threshold] = gated_cost
66+
return cost_matrix
67+
68+
def gated_metric(self, tracks, dets):
69+
"""
70+
get cost matrix, firstly calculate apperence cost, then filter by Kalman state.
71+
72+
tracks: List[STrack]
73+
dets: List[STrack]
74+
"""
75+
Apperance_dist = matching.nearest_embedding_distance(tracks=tracks, detections=dets, metric='cosine')
76+
cost_matrix = self.gate_cost_matrix(Apperance_dist, tracks, dets, )
77+
return cost_matrix
78+
4279
def update(self, det_results, ori_img):
4380
"""
4481
this func is called by every time step
@@ -85,7 +122,7 @@ def update(self, det_results, ori_img):
85122
features = self.get_feature(bbox_temp, ori_img)
86123

87124
# detections: List[Strack]
88-
detections = [STrack(cls, STrack.xywh2tlwh(xywh), score, kalman_format=self.opts.kalman_format, feature=feature)
125+
detections = [STrack(cls, STrack.xywh2tlwh(xywh), score, kalman_format=self.opts.kalman_format, feature=feature, use_avg_of_feature=False)
89126
for (cls, xywh, score, feature) in zip(det_results[:, -1], det_results[:, :4], det_results[:, 4], features)]
90127

91128
else:
@@ -106,15 +143,9 @@ def update(self, det_results, ori_img):
106143
# Kalman predict, update every mean and cov of tracks
107144
STrack.multi_predict(stracks=strack_pool, kalman=self.kalman)
108145

109-
# calculate apperance distance, shape:(len(strack_pool), len(detections))
110-
Apperance_dist = matching.embedding_distance(tracks=strack_pool, detections=detections, metric='euclidean')
111-
# calculate iou distance, shape:(len(strack_pool), len(detections))
112-
IoU_dist = matching.iou_distance(atracks=strack_pool, btracks=detections)
113-
# fuse
114-
Dist_mat = self.gamma * IoU_dist + (1. - self.gamma) * Apperance_dist
115-
116146
# match thresh=0.9 is same in ByteTrack code
117-
matched_pair0, u_tracks0_idx, u_dets0_idx = matching.linear_assignment(Dist_mat, thresh=0.7)
147+
matched_pair0, u_tracks0_idx, u_dets0_idx = matching.matching_cascade(self.gated_metric, 0.9, self.max_time_lost,
148+
strack_pool, detections)
118149

119150
for itrack_match, idet_match in matched_pair0:
120151
track = strack_pool[itrack_match]
@@ -148,7 +179,6 @@ def update(self, det_results, ori_img):
148179
activated_starcks.append(track)
149180

150181
elif track.state == TrackState.Lost:
151-
exit(0)
152182
track.re_activate(det, self.frame_id, )
153183
refind_stracks.append(track)
154184

@@ -160,10 +190,8 @@ def update(self, det_results, ori_img):
160190
lost_stracks.append(track)
161191

162192
# deal with unconfirmed tracks, match new track of last frame and new high conf det
163-
Apperance_dist = matching.embedding_distance(tracks=unconfirmed, detections=u_det1, metric='euclidean')
164-
IoU_dist = matching.iou_distance(atracks=unconfirmed, btracks=u_det1)
165-
Dist_mat = self.gamma * IoU_dist + (1. - self.gamma) * Apperance_dist
166-
matched_pair2, u_tracks2_idx, u_det2_idx = matching.linear_assignment(Dist_mat, thresh=0.7)
193+
matched_pair2, u_tracks2_idx, u_det2_idx = matching.matching_cascade(self.gated_metric, 0.7, self.max_time_lost,
194+
unconfirmed, u_det1)
167195

168196
for itrack_match, idet_match in matched_pair2:
169197
track = unconfirmed[itrack_match]
@@ -179,7 +207,7 @@ def update(self, det_results, ori_img):
179207
# deal with new tracks
180208
for idx in u_det2_idx:
181209
det = u_det1[idx]
182-
if det.score > self.det_thresh + 0.1:
210+
if det.score > self.det_thresh:
183211
det.activate(self.frame_id)
184212
activated_starcks.append(det)
185213

tracker/matching.py

Lines changed: 80 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,29 @@ def embedding_distance(tracks, detections, metric='cosine'):
102102
raise NotImplementedError
103103
return cost_matrix
104104

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+
105128
def ecu_iou_distance(tracks, detections, img0_shape):
106129
"""
107130
combine eculidian center-point distance and iou distance
@@ -189,90 +212,70 @@ def fuse_motion(kf, cost_matrix, tracks, detections, only_position=False, lambda
189212
return cost_matrix
190213

191214

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):
199218
"""
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]
220231
"""
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)))
221236

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 = []
245239

240+
for level in range(cascade_depth):
246241
"""
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
270243
"""
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
275277

278+
276279

277280
"""
278281
funcs to cal similarity, copied from UAVMOT

0 commit comments

Comments
 (0)