advcloud
commited on
Commit
·
efe15e5
1
Parent(s):
0da82a3
first commit
Browse files- byte_track/__init__.py +3 -0
- byte_track/bytetracker.py +106 -0
- byte_track/tracker/__init__.py +4 -0
- byte_track/tracker/basetrack.py +52 -0
- byte_track/tracker/byte_tracker.py +326 -0
- byte_track/tracker/kalman_filter.py +270 -0
- byte_track/tracker/matching.py +178 -0
byte_track/__init__.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
sys.path.append(os.path.dirname(__file__))
|
byte_track/bytetracker.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .tracker.byte_tracker import BYTETracker
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
class ByteTrack(object):
|
6 |
+
def __init__(self, detector, min_box_area=10):
|
7 |
+
self.min_box_area = min_box_area
|
8 |
+
|
9 |
+
self.rgb_means = (0.485, 0.456, 0.406)
|
10 |
+
self.std = (0.229, 0.224, 0.225)
|
11 |
+
|
12 |
+
self.detector = detector
|
13 |
+
self.input_shape = tuple(detector.model.get_inputs()[0].shape[2:])
|
14 |
+
self.tracker = BYTETracker(frame_rate=30)
|
15 |
+
|
16 |
+
def inference(self, image, conf_thresh=0.25, classes=None):
|
17 |
+
|
18 |
+
dets, image_info = self.detector.detect(image, conf_thres=conf_thresh, input_shape=self.input_shape, classes=classes)
|
19 |
+
|
20 |
+
class_ids=[]
|
21 |
+
ids=[]
|
22 |
+
bboxes=[]
|
23 |
+
scores=[]
|
24 |
+
|
25 |
+
if isinstance(dets, np.ndarray) and len(dets) > 0:
|
26 |
+
class_ids = dets[:, -1].tolist()
|
27 |
+
bboxes, ids, scores = self._tracker_update(
|
28 |
+
dets,
|
29 |
+
image_info,
|
30 |
+
)
|
31 |
+
# image = self.draw_tracking_info(
|
32 |
+
# image,
|
33 |
+
# bboxes,
|
34 |
+
# ids,
|
35 |
+
# scores,
|
36 |
+
# )
|
37 |
+
|
38 |
+
# return image, len(bboxes), class_ids
|
39 |
+
return bboxes, ids, scores, class_ids
|
40 |
+
|
41 |
+
def get_id_color(self, index):
|
42 |
+
temp_index = abs(int(index)) * 3
|
43 |
+
color = ((37 * temp_index) % 255, (17 * temp_index) % 255,
|
44 |
+
(29 * temp_index) % 255)
|
45 |
+
return color
|
46 |
+
|
47 |
+
def draw_tracking_info(
|
48 |
+
self,
|
49 |
+
image,
|
50 |
+
tlwhs,
|
51 |
+
ids,
|
52 |
+
scores,
|
53 |
+
frame_id=0,
|
54 |
+
elapsed_time=0.,
|
55 |
+
):
|
56 |
+
text_scale = 1.5
|
57 |
+
text_thickness = 2
|
58 |
+
line_thickness = 2
|
59 |
+
|
60 |
+
# text = 'frame: %d ' % (frame_id)
|
61 |
+
# text += 'elapsed time: %.0fms ' % (elapsed_time * 1000)
|
62 |
+
# text += 'num: %d' % (len(tlwhs))
|
63 |
+
# cv2.putText(
|
64 |
+
# image,
|
65 |
+
# text,
|
66 |
+
# (0, int(15 * text_scale)),
|
67 |
+
# cv2.FONT_HERSHEY_PLAIN,
|
68 |
+
# 2,
|
69 |
+
# (0, 255, 0),
|
70 |
+
# thickness=text_thickness,
|
71 |
+
# )
|
72 |
+
|
73 |
+
for index, tlwh in enumerate(tlwhs):
|
74 |
+
x1, y1 = int(tlwh[0]), int(tlwh[1])
|
75 |
+
x2, y2 = x1 + int(tlwh[2]), y1 + int(tlwh[3])
|
76 |
+
color = self.get_id_color(ids[index])
|
77 |
+
cv2.rectangle(image, (x1, y1), (x2, y2), color, line_thickness)
|
78 |
+
|
79 |
+
text = str(ids[index])
|
80 |
+
cv2.putText(image, text, (x1, y1 - 5), cv2.FONT_HERSHEY_PLAIN,
|
81 |
+
text_scale, (0, 0, 0), text_thickness + 3)
|
82 |
+
cv2.putText(image, text, (x1, y1 - 5), cv2.FONT_HERSHEY_PLAIN,
|
83 |
+
text_scale, (255, 255, 255), text_thickness)
|
84 |
+
return image
|
85 |
+
|
86 |
+
def _tracker_update(self, dets, image_info):
|
87 |
+
online_targets = []
|
88 |
+
if dets is not None:
|
89 |
+
online_targets = self.tracker.update(
|
90 |
+
dets[:, :-1],
|
91 |
+
[image_info['height'], image_info['width']],
|
92 |
+
[image_info['height'], image_info['width']],
|
93 |
+
)
|
94 |
+
online_tlwhs = []
|
95 |
+
online_ids = []
|
96 |
+
online_scores = []
|
97 |
+
for online_target in online_targets:
|
98 |
+
tlwh = online_target.tlwh
|
99 |
+
track_id = online_target.track_id
|
100 |
+
vertical = tlwh[2] / tlwh[3] > 1.6
|
101 |
+
if tlwh[2] * tlwh[3] > self.min_box_area and not vertical:
|
102 |
+
online_tlwhs.append(tlwh)
|
103 |
+
online_ids.append(track_id)
|
104 |
+
online_scores.append(online_target.score)
|
105 |
+
|
106 |
+
return online_tlwhs, online_ids, online_scores
|
byte_track/tracker/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
sys.path.append(os.path.dirname(__file__))
|
4 |
+
|
byte_track/tracker/basetrack.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from collections import OrderedDict
|
3 |
+
|
4 |
+
|
5 |
+
class TrackState(object):
|
6 |
+
New = 0
|
7 |
+
Tracked = 1
|
8 |
+
Lost = 2
|
9 |
+
Removed = 3
|
10 |
+
|
11 |
+
|
12 |
+
class BaseTrack(object):
|
13 |
+
_count = 0
|
14 |
+
|
15 |
+
track_id = 0
|
16 |
+
is_activated = False
|
17 |
+
state = TrackState.New
|
18 |
+
|
19 |
+
history = OrderedDict()
|
20 |
+
features = []
|
21 |
+
curr_feature = None
|
22 |
+
score = 0
|
23 |
+
start_frame = 0
|
24 |
+
frame_id = 0
|
25 |
+
time_since_update = 0
|
26 |
+
|
27 |
+
# multi-camera
|
28 |
+
location = (np.inf, np.inf)
|
29 |
+
|
30 |
+
@property
|
31 |
+
def end_frame(self):
|
32 |
+
return self.frame_id
|
33 |
+
|
34 |
+
@staticmethod
|
35 |
+
def next_id():
|
36 |
+
BaseTrack._count += 1
|
37 |
+
return BaseTrack._count
|
38 |
+
|
39 |
+
def activate(self, *args):
|
40 |
+
raise NotImplementedError
|
41 |
+
|
42 |
+
def predict(self):
|
43 |
+
raise NotImplementedError
|
44 |
+
|
45 |
+
def update(self, *args, **kwargs):
|
46 |
+
raise NotImplementedError
|
47 |
+
|
48 |
+
def mark_lost(self):
|
49 |
+
self.state = TrackState.Lost
|
50 |
+
|
51 |
+
def mark_removed(self):
|
52 |
+
self.state = TrackState.Removed
|
byte_track/tracker/byte_tracker.py
ADDED
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from .kalman_filter import KalmanFilter
|
3 |
+
import matching
|
4 |
+
from .basetrack import BaseTrack, TrackState
|
5 |
+
|
6 |
+
class STrack(BaseTrack):
|
7 |
+
shared_kalman = KalmanFilter()
|
8 |
+
def __init__(self, tlwh, score):
|
9 |
+
|
10 |
+
# wait activate
|
11 |
+
self._tlwh = np.asarray(tlwh, dtype=np.float)
|
12 |
+
self.kalman_filter = None
|
13 |
+
self.mean, self.covariance = None, None
|
14 |
+
self.is_activated = False
|
15 |
+
|
16 |
+
self.score = score
|
17 |
+
self.tracklet_len = 0
|
18 |
+
|
19 |
+
def predict(self):
|
20 |
+
mean_state = self.mean.copy()
|
21 |
+
if self.state != TrackState.Tracked:
|
22 |
+
mean_state[7] = 0
|
23 |
+
self.mean, self.covariance = self.kalman_filter.predict(mean_state, self.covariance)
|
24 |
+
|
25 |
+
@staticmethod
|
26 |
+
def multi_predict(stracks):
|
27 |
+
if len(stracks) > 0:
|
28 |
+
multi_mean = np.asarray([st.mean.copy() for st in stracks])
|
29 |
+
multi_covariance = np.asarray([st.covariance for st in stracks])
|
30 |
+
for i, st in enumerate(stracks):
|
31 |
+
if st.state != TrackState.Tracked:
|
32 |
+
multi_mean[i][7] = 0
|
33 |
+
multi_mean, multi_covariance = STrack.shared_kalman.multi_predict(multi_mean, multi_covariance)
|
34 |
+
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
|
35 |
+
stracks[i].mean = mean
|
36 |
+
stracks[i].covariance = cov
|
37 |
+
|
38 |
+
def activate(self, kalman_filter, frame_id):
|
39 |
+
"""Start a new tracklet"""
|
40 |
+
self.kalman_filter = kalman_filter
|
41 |
+
self.track_id = self.next_id()
|
42 |
+
self.mean, self.covariance = self.kalman_filter.initiate(self.tlwh_to_xyah(self._tlwh))
|
43 |
+
|
44 |
+
self.tracklet_len = 0
|
45 |
+
self.state = TrackState.Tracked
|
46 |
+
if frame_id == 1:
|
47 |
+
self.is_activated = True
|
48 |
+
# self.is_activated = True
|
49 |
+
self.frame_id = frame_id
|
50 |
+
self.start_frame = frame_id
|
51 |
+
|
52 |
+
def re_activate(self, new_track, frame_id, new_id=False):
|
53 |
+
self.mean, self.covariance = self.kalman_filter.update(
|
54 |
+
self.mean, self.covariance, self.tlwh_to_xyah(new_track.tlwh)
|
55 |
+
)
|
56 |
+
self.tracklet_len = 0
|
57 |
+
self.state = TrackState.Tracked
|
58 |
+
self.is_activated = True
|
59 |
+
self.frame_id = frame_id
|
60 |
+
if new_id:
|
61 |
+
self.track_id = self.next_id()
|
62 |
+
self.score = new_track.score
|
63 |
+
|
64 |
+
def update(self, new_track, frame_id):
|
65 |
+
"""
|
66 |
+
Update a matched track
|
67 |
+
:type new_track: STrack
|
68 |
+
:type frame_id: int
|
69 |
+
:type update_feature: bool
|
70 |
+
:return:
|
71 |
+
"""
|
72 |
+
self.frame_id = frame_id
|
73 |
+
self.tracklet_len += 1
|
74 |
+
|
75 |
+
new_tlwh = new_track.tlwh
|
76 |
+
self.mean, self.covariance = self.kalman_filter.update(
|
77 |
+
self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh))
|
78 |
+
self.state = TrackState.Tracked
|
79 |
+
self.is_activated = True
|
80 |
+
|
81 |
+
self.score = new_track.score
|
82 |
+
|
83 |
+
@property
|
84 |
+
# @jit(nopython=True)
|
85 |
+
def tlwh(self):
|
86 |
+
"""Get current position in bounding box format `(top left x, top left y,
|
87 |
+
width, height)`.
|
88 |
+
"""
|
89 |
+
if self.mean is None:
|
90 |
+
return self._tlwh.copy()
|
91 |
+
ret = self.mean[:4].copy()
|
92 |
+
ret[2] *= ret[3]
|
93 |
+
ret[:2] -= ret[2:] / 2
|
94 |
+
return ret
|
95 |
+
|
96 |
+
@property
|
97 |
+
# @jit(nopython=True)
|
98 |
+
def tlbr(self):
|
99 |
+
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
|
100 |
+
`(top left, bottom right)`.
|
101 |
+
"""
|
102 |
+
ret = self.tlwh.copy()
|
103 |
+
ret[2:] += ret[:2]
|
104 |
+
return ret
|
105 |
+
|
106 |
+
@staticmethod
|
107 |
+
# @jit(nopython=True)
|
108 |
+
def tlwh_to_xyah(tlwh):
|
109 |
+
"""Convert bounding box to format `(center x, center y, aspect ratio,
|
110 |
+
height)`, where the aspect ratio is `width / height`.
|
111 |
+
"""
|
112 |
+
ret = np.asarray(tlwh).copy()
|
113 |
+
ret[:2] += ret[2:] / 2
|
114 |
+
ret[2] /= ret[3]
|
115 |
+
return ret
|
116 |
+
|
117 |
+
def to_xyah(self):
|
118 |
+
return self.tlwh_to_xyah(self.tlwh)
|
119 |
+
|
120 |
+
@staticmethod
|
121 |
+
# @jit(nopython=True)
|
122 |
+
def tlbr_to_tlwh(tlbr):
|
123 |
+
ret = np.asarray(tlbr).copy()
|
124 |
+
ret[2:] -= ret[:2]
|
125 |
+
return ret
|
126 |
+
|
127 |
+
@staticmethod
|
128 |
+
# @jit(nopython=True)
|
129 |
+
def tlwh_to_tlbr(tlwh):
|
130 |
+
ret = np.asarray(tlwh).copy()
|
131 |
+
ret[2:] += ret[:2]
|
132 |
+
return ret
|
133 |
+
|
134 |
+
def __repr__(self):
|
135 |
+
return 'OT_{}_({}-{})'.format(self.track_id, self.start_frame, self.end_frame)
|
136 |
+
|
137 |
+
|
138 |
+
class BYTETracker(object):
|
139 |
+
def __init__(self, track_thresh=0.5,match_thresh=0.8, track_buffer=30, mot20=False, frame_rate=30):
|
140 |
+
self.tracked_stracks = [] # type: list[STrack]
|
141 |
+
self.lost_stracks = [] # type: list[STrack]
|
142 |
+
self.removed_stracks = [] # type: list[STrack]
|
143 |
+
|
144 |
+
self.track_thresh = track_thresh
|
145 |
+
self.track_buffer = track_buffer
|
146 |
+
self.mot20 = mot20
|
147 |
+
self.match_thresh = match_thresh
|
148 |
+
|
149 |
+
self.frame_id = 0
|
150 |
+
self.det_thresh = track_thresh + 0.1
|
151 |
+
self.buffer_size = int(frame_rate / 30.0 * self.track_buffer)
|
152 |
+
self.max_time_lost = self.buffer_size
|
153 |
+
self.kalman_filter = KalmanFilter()
|
154 |
+
|
155 |
+
def update(self, output_results, img_info, img_size):
|
156 |
+
self.frame_id += 1
|
157 |
+
activated_starcks = []
|
158 |
+
refind_stracks = []
|
159 |
+
lost_stracks = []
|
160 |
+
removed_stracks = []
|
161 |
+
|
162 |
+
if output_results.shape[1] == 5:
|
163 |
+
scores = output_results[:, 4]
|
164 |
+
bboxes = output_results[:, :4]
|
165 |
+
else:
|
166 |
+
output_results = output_results.cpu().numpy()
|
167 |
+
scores = output_results[:, 4] * output_results[:, 5]
|
168 |
+
bboxes = output_results[:, :4] # x1y1x2y2
|
169 |
+
img_h, img_w = img_info[0], img_info[1]
|
170 |
+
scale = min(img_size[0] / float(img_h), img_size[1] / float(img_w))
|
171 |
+
bboxes /= scale
|
172 |
+
|
173 |
+
remain_inds = scores > self.track_thresh
|
174 |
+
inds_low = scores > 0.1
|
175 |
+
inds_high = scores < self.track_thresh
|
176 |
+
|
177 |
+
inds_second = np.logical_and(inds_low, inds_high)
|
178 |
+
dets_second = bboxes[inds_second]
|
179 |
+
dets = bboxes[remain_inds]
|
180 |
+
scores_keep = scores[remain_inds]
|
181 |
+
scores_second = scores[inds_second]
|
182 |
+
|
183 |
+
if len(dets) > 0:
|
184 |
+
'''Detections'''
|
185 |
+
detections = [STrack(STrack.tlbr_to_tlwh(tlbr), s) for
|
186 |
+
(tlbr, s) in zip(dets, scores_keep)]
|
187 |
+
else:
|
188 |
+
detections = []
|
189 |
+
|
190 |
+
''' Add newly detected tracklets to tracked_stracks'''
|
191 |
+
unconfirmed = []
|
192 |
+
tracked_stracks = [] # type: list[STrack]
|
193 |
+
for track in self.tracked_stracks:
|
194 |
+
if not track.is_activated:
|
195 |
+
unconfirmed.append(track)
|
196 |
+
else:
|
197 |
+
tracked_stracks.append(track)
|
198 |
+
|
199 |
+
''' Step 2: First association, with high score detection boxes'''
|
200 |
+
strack_pool = joint_stracks(tracked_stracks, self.lost_stracks)
|
201 |
+
# Predict the current location with KF
|
202 |
+
STrack.multi_predict(strack_pool)
|
203 |
+
dists = matching.iou_distance(strack_pool, detections)
|
204 |
+
if not self.mot20:
|
205 |
+
dists = matching.fuse_score(dists, detections)
|
206 |
+
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=self.match_thresh)
|
207 |
+
|
208 |
+
for itracked, idet in matches:
|
209 |
+
track = strack_pool[itracked]
|
210 |
+
det = detections[idet]
|
211 |
+
if track.state == TrackState.Tracked:
|
212 |
+
track.update(detections[idet], self.frame_id)
|
213 |
+
activated_starcks.append(track)
|
214 |
+
else:
|
215 |
+
track.re_activate(det, self.frame_id, new_id=False)
|
216 |
+
refind_stracks.append(track)
|
217 |
+
|
218 |
+
''' Step 3: Second association, with low score detection boxes'''
|
219 |
+
# association the untrack to the low score detections
|
220 |
+
if len(dets_second) > 0:
|
221 |
+
'''Detections'''
|
222 |
+
detections_second = [STrack(STrack.tlbr_to_tlwh(tlbr), s) for
|
223 |
+
(tlbr, s) in zip(dets_second, scores_second)]
|
224 |
+
else:
|
225 |
+
detections_second = []
|
226 |
+
r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]
|
227 |
+
dists = matching.iou_distance(r_tracked_stracks, detections_second)
|
228 |
+
matches, u_track, u_detection_second = matching.linear_assignment(dists, thresh=0.5)
|
229 |
+
for itracked, idet in matches:
|
230 |
+
track = r_tracked_stracks[itracked]
|
231 |
+
det = detections_second[idet]
|
232 |
+
if track.state == TrackState.Tracked:
|
233 |
+
track.update(det, self.frame_id)
|
234 |
+
activated_starcks.append(track)
|
235 |
+
else:
|
236 |
+
track.re_activate(det, self.frame_id, new_id=False)
|
237 |
+
refind_stracks.append(track)
|
238 |
+
|
239 |
+
for it in u_track:
|
240 |
+
track = r_tracked_stracks[it]
|
241 |
+
if not track.state == TrackState.Lost:
|
242 |
+
track.mark_lost()
|
243 |
+
lost_stracks.append(track)
|
244 |
+
|
245 |
+
'''Deal with unconfirmed tracks, usually tracks with only one beginning frame'''
|
246 |
+
detections = [detections[i] for i in u_detection]
|
247 |
+
dists = matching.iou_distance(unconfirmed, detections)
|
248 |
+
if not self.mot20:
|
249 |
+
dists = matching.fuse_score(dists, detections)
|
250 |
+
matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
|
251 |
+
for itracked, idet in matches:
|
252 |
+
unconfirmed[itracked].update(detections[idet], self.frame_id)
|
253 |
+
activated_starcks.append(unconfirmed[itracked])
|
254 |
+
for it in u_unconfirmed:
|
255 |
+
track = unconfirmed[it]
|
256 |
+
track.mark_removed()
|
257 |
+
removed_stracks.append(track)
|
258 |
+
|
259 |
+
""" Step 4: Init new stracks"""
|
260 |
+
for inew in u_detection:
|
261 |
+
track = detections[inew]
|
262 |
+
if track.score < self.det_thresh:
|
263 |
+
continue
|
264 |
+
track.activate(self.kalman_filter, self.frame_id)
|
265 |
+
activated_starcks.append(track)
|
266 |
+
""" Step 5: Update state"""
|
267 |
+
for track in self.lost_stracks:
|
268 |
+
if self.frame_id - track.end_frame > self.max_time_lost:
|
269 |
+
track.mark_removed()
|
270 |
+
removed_stracks.append(track)
|
271 |
+
|
272 |
+
# print('Ramained match {} s'.format(t4-t3))
|
273 |
+
|
274 |
+
self.tracked_stracks = [t for t in self.tracked_stracks if t.state == TrackState.Tracked]
|
275 |
+
self.tracked_stracks = joint_stracks(self.tracked_stracks, activated_starcks)
|
276 |
+
self.tracked_stracks = joint_stracks(self.tracked_stracks, refind_stracks)
|
277 |
+
self.lost_stracks = sub_stracks(self.lost_stracks, self.tracked_stracks)
|
278 |
+
self.lost_stracks.extend(lost_stracks)
|
279 |
+
self.lost_stracks = sub_stracks(self.lost_stracks, self.removed_stracks)
|
280 |
+
self.removed_stracks.extend(removed_stracks)
|
281 |
+
self.tracked_stracks, self.lost_stracks = remove_duplicate_stracks(self.tracked_stracks, self.lost_stracks)
|
282 |
+
# get scores of lost tracks
|
283 |
+
output_stracks = [track for track in self.tracked_stracks if track.is_activated]
|
284 |
+
|
285 |
+
return output_stracks
|
286 |
+
|
287 |
+
|
288 |
+
def joint_stracks(tlista, tlistb):
|
289 |
+
exists = {}
|
290 |
+
res = []
|
291 |
+
for t in tlista:
|
292 |
+
exists[t.track_id] = 1
|
293 |
+
res.append(t)
|
294 |
+
for t in tlistb:
|
295 |
+
tid = t.track_id
|
296 |
+
if not exists.get(tid, 0):
|
297 |
+
exists[tid] = 1
|
298 |
+
res.append(t)
|
299 |
+
return res
|
300 |
+
|
301 |
+
|
302 |
+
def sub_stracks(tlista, tlistb):
|
303 |
+
stracks = {}
|
304 |
+
for t in tlista:
|
305 |
+
stracks[t.track_id] = t
|
306 |
+
for t in tlistb:
|
307 |
+
tid = t.track_id
|
308 |
+
if stracks.get(tid, 0):
|
309 |
+
del stracks[tid]
|
310 |
+
return list(stracks.values())
|
311 |
+
|
312 |
+
|
313 |
+
def remove_duplicate_stracks(stracksa, stracksb):
|
314 |
+
pdist = matching.iou_distance(stracksa, stracksb)
|
315 |
+
pairs = np.where(pdist < 0.15)
|
316 |
+
dupa, dupb = list(), list()
|
317 |
+
for p, q in zip(*pairs):
|
318 |
+
timep = stracksa[p].frame_id - stracksa[p].start_frame
|
319 |
+
timeq = stracksb[q].frame_id - stracksb[q].start_frame
|
320 |
+
if timep > timeq:
|
321 |
+
dupb.append(q)
|
322 |
+
else:
|
323 |
+
dupa.append(p)
|
324 |
+
resa = [t for i, t in enumerate(stracksa) if not i in dupa]
|
325 |
+
resb = [t for i, t in enumerate(stracksb) if not i in dupb]
|
326 |
+
return resa, resb
|
byte_track/tracker/kalman_filter.py
ADDED
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# vim: expandtab:ts=4:sw=4
|
2 |
+
import numpy as np
|
3 |
+
import scipy.linalg
|
4 |
+
|
5 |
+
|
6 |
+
"""
|
7 |
+
Table for the 0.95 quantile of the chi-square distribution with N degrees of
|
8 |
+
freedom (contains values for N=1, ..., 9). Taken from MATLAB/Octave's chi2inv
|
9 |
+
function and used as Mahalanobis gating threshold.
|
10 |
+
"""
|
11 |
+
chi2inv95 = {
|
12 |
+
1: 3.8415,
|
13 |
+
2: 5.9915,
|
14 |
+
3: 7.8147,
|
15 |
+
4: 9.4877,
|
16 |
+
5: 11.070,
|
17 |
+
6: 12.592,
|
18 |
+
7: 14.067,
|
19 |
+
8: 15.507,
|
20 |
+
9: 16.919}
|
21 |
+
|
22 |
+
|
23 |
+
class KalmanFilter(object):
|
24 |
+
"""
|
25 |
+
A simple Kalman filter for tracking bounding boxes in image space.
|
26 |
+
|
27 |
+
The 8-dimensional state space
|
28 |
+
|
29 |
+
x, y, a, h, vx, vy, va, vh
|
30 |
+
|
31 |
+
contains the bounding box center position (x, y), aspect ratio a, height h,
|
32 |
+
and their respective velocities.
|
33 |
+
|
34 |
+
Object motion follows a constant velocity model. The bounding box location
|
35 |
+
(x, y, a, h) is taken as direct observation of the state space (linear
|
36 |
+
observation model).
|
37 |
+
|
38 |
+
"""
|
39 |
+
|
40 |
+
def __init__(self):
|
41 |
+
ndim, dt = 4, 1.
|
42 |
+
|
43 |
+
# Create Kalman filter model matrices.
|
44 |
+
self._motion_mat = np.eye(2 * ndim, 2 * ndim)
|
45 |
+
for i in range(ndim):
|
46 |
+
self._motion_mat[i, ndim + i] = dt
|
47 |
+
self._update_mat = np.eye(ndim, 2 * ndim)
|
48 |
+
|
49 |
+
# Motion and observation uncertainty are chosen relative to the current
|
50 |
+
# state estimate. These weights control the amount of uncertainty in
|
51 |
+
# the model. This is a bit hacky.
|
52 |
+
self._std_weight_position = 1. / 20
|
53 |
+
self._std_weight_velocity = 1. / 160
|
54 |
+
|
55 |
+
def initiate(self, measurement):
|
56 |
+
"""Create track from unassociated measurement.
|
57 |
+
|
58 |
+
Parameters
|
59 |
+
----------
|
60 |
+
measurement : ndarray
|
61 |
+
Bounding box coordinates (x, y, a, h) with center position (x, y),
|
62 |
+
aspect ratio a, and height h.
|
63 |
+
|
64 |
+
Returns
|
65 |
+
-------
|
66 |
+
(ndarray, ndarray)
|
67 |
+
Returns the mean vector (8 dimensional) and covariance matrix (8x8
|
68 |
+
dimensional) of the new track. Unobserved velocities are initialized
|
69 |
+
to 0 mean.
|
70 |
+
|
71 |
+
"""
|
72 |
+
mean_pos = measurement
|
73 |
+
mean_vel = np.zeros_like(mean_pos)
|
74 |
+
mean = np.r_[mean_pos, mean_vel]
|
75 |
+
|
76 |
+
std = [
|
77 |
+
2 * self._std_weight_position * measurement[3],
|
78 |
+
2 * self._std_weight_position * measurement[3],
|
79 |
+
1e-2,
|
80 |
+
2 * self._std_weight_position * measurement[3],
|
81 |
+
10 * self._std_weight_velocity * measurement[3],
|
82 |
+
10 * self._std_weight_velocity * measurement[3],
|
83 |
+
1e-5,
|
84 |
+
10 * self._std_weight_velocity * measurement[3]]
|
85 |
+
covariance = np.diag(np.square(std))
|
86 |
+
return mean, covariance
|
87 |
+
|
88 |
+
def predict(self, mean, covariance):
|
89 |
+
"""Run Kalman filter prediction step.
|
90 |
+
|
91 |
+
Parameters
|
92 |
+
----------
|
93 |
+
mean : ndarray
|
94 |
+
The 8 dimensional mean vector of the object state at the previous
|
95 |
+
time step.
|
96 |
+
covariance : ndarray
|
97 |
+
The 8x8 dimensional covariance matrix of the object state at the
|
98 |
+
previous time step.
|
99 |
+
|
100 |
+
Returns
|
101 |
+
-------
|
102 |
+
(ndarray, ndarray)
|
103 |
+
Returns the mean vector and covariance matrix of the predicted
|
104 |
+
state. Unobserved velocities are initialized to 0 mean.
|
105 |
+
|
106 |
+
"""
|
107 |
+
std_pos = [
|
108 |
+
self._std_weight_position * mean[3],
|
109 |
+
self._std_weight_position * mean[3],
|
110 |
+
1e-2,
|
111 |
+
self._std_weight_position * mean[3]]
|
112 |
+
std_vel = [
|
113 |
+
self._std_weight_velocity * mean[3],
|
114 |
+
self._std_weight_velocity * mean[3],
|
115 |
+
1e-5,
|
116 |
+
self._std_weight_velocity * mean[3]]
|
117 |
+
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))
|
118 |
+
|
119 |
+
#mean = np.dot(self._motion_mat, mean)
|
120 |
+
mean = np.dot(mean, self._motion_mat.T)
|
121 |
+
covariance = np.linalg.multi_dot((
|
122 |
+
self._motion_mat, covariance, self._motion_mat.T)) + motion_cov
|
123 |
+
|
124 |
+
return mean, covariance
|
125 |
+
|
126 |
+
def project(self, mean, covariance):
|
127 |
+
"""Project state distribution to measurement space.
|
128 |
+
|
129 |
+
Parameters
|
130 |
+
----------
|
131 |
+
mean : ndarray
|
132 |
+
The state's mean vector (8 dimensional array).
|
133 |
+
covariance : ndarray
|
134 |
+
The state's covariance matrix (8x8 dimensional).
|
135 |
+
|
136 |
+
Returns
|
137 |
+
-------
|
138 |
+
(ndarray, ndarray)
|
139 |
+
Returns the projected mean and covariance matrix of the given state
|
140 |
+
estimate.
|
141 |
+
|
142 |
+
"""
|
143 |
+
std = [
|
144 |
+
self._std_weight_position * mean[3],
|
145 |
+
self._std_weight_position * mean[3],
|
146 |
+
1e-1,
|
147 |
+
self._std_weight_position * mean[3]]
|
148 |
+
innovation_cov = np.diag(np.square(std))
|
149 |
+
|
150 |
+
mean = np.dot(self._update_mat, mean)
|
151 |
+
covariance = np.linalg.multi_dot((
|
152 |
+
self._update_mat, covariance, self._update_mat.T))
|
153 |
+
return mean, covariance + innovation_cov
|
154 |
+
|
155 |
+
def multi_predict(self, mean, covariance):
|
156 |
+
"""Run Kalman filter prediction step (Vectorized version).
|
157 |
+
Parameters
|
158 |
+
----------
|
159 |
+
mean : ndarray
|
160 |
+
The Nx8 dimensional mean matrix of the object states at the previous
|
161 |
+
time step.
|
162 |
+
covariance : ndarray
|
163 |
+
The Nx8x8 dimensional covariance matrics of the object states at the
|
164 |
+
previous time step.
|
165 |
+
Returns
|
166 |
+
-------
|
167 |
+
(ndarray, ndarray)
|
168 |
+
Returns the mean vector and covariance matrix of the predicted
|
169 |
+
state. Unobserved velocities are initialized to 0 mean.
|
170 |
+
"""
|
171 |
+
std_pos = [
|
172 |
+
self._std_weight_position * mean[:, 3],
|
173 |
+
self._std_weight_position * mean[:, 3],
|
174 |
+
1e-2 * np.ones_like(mean[:, 3]),
|
175 |
+
self._std_weight_position * mean[:, 3]]
|
176 |
+
std_vel = [
|
177 |
+
self._std_weight_velocity * mean[:, 3],
|
178 |
+
self._std_weight_velocity * mean[:, 3],
|
179 |
+
1e-5 * np.ones_like(mean[:, 3]),
|
180 |
+
self._std_weight_velocity * mean[:, 3]]
|
181 |
+
sqr = np.square(np.r_[std_pos, std_vel]).T
|
182 |
+
|
183 |
+
motion_cov = []
|
184 |
+
for i in range(len(mean)):
|
185 |
+
motion_cov.append(np.diag(sqr[i]))
|
186 |
+
motion_cov = np.asarray(motion_cov)
|
187 |
+
|
188 |
+
mean = np.dot(mean, self._motion_mat.T)
|
189 |
+
left = np.dot(self._motion_mat, covariance).transpose((1, 0, 2))
|
190 |
+
covariance = np.dot(left, self._motion_mat.T) + motion_cov
|
191 |
+
|
192 |
+
return mean, covariance
|
193 |
+
|
194 |
+
def update(self, mean, covariance, measurement):
|
195 |
+
"""Run Kalman filter correction step.
|
196 |
+
|
197 |
+
Parameters
|
198 |
+
----------
|
199 |
+
mean : ndarray
|
200 |
+
The predicted state's mean vector (8 dimensional).
|
201 |
+
covariance : ndarray
|
202 |
+
The state's covariance matrix (8x8 dimensional).
|
203 |
+
measurement : ndarray
|
204 |
+
The 4 dimensional measurement vector (x, y, a, h), where (x, y)
|
205 |
+
is the center position, a the aspect ratio, and h the height of the
|
206 |
+
bounding box.
|
207 |
+
|
208 |
+
Returns
|
209 |
+
-------
|
210 |
+
(ndarray, ndarray)
|
211 |
+
Returns the measurement-corrected state distribution.
|
212 |
+
|
213 |
+
"""
|
214 |
+
projected_mean, projected_cov = self.project(mean, covariance)
|
215 |
+
|
216 |
+
chol_factor, lower = scipy.linalg.cho_factor(
|
217 |
+
projected_cov, lower=True, check_finite=False)
|
218 |
+
kalman_gain = scipy.linalg.cho_solve(
|
219 |
+
(chol_factor, lower), np.dot(covariance, self._update_mat.T).T,
|
220 |
+
check_finite=False).T
|
221 |
+
innovation = measurement - projected_mean
|
222 |
+
|
223 |
+
new_mean = mean + np.dot(innovation, kalman_gain.T)
|
224 |
+
new_covariance = covariance - np.linalg.multi_dot((
|
225 |
+
kalman_gain, projected_cov, kalman_gain.T))
|
226 |
+
return new_mean, new_covariance
|
227 |
+
|
228 |
+
def gating_distance(self, mean, covariance, measurements,
|
229 |
+
only_position=False, metric='maha'):
|
230 |
+
"""Compute gating distance between state distribution and measurements.
|
231 |
+
A suitable distance threshold can be obtained from `chi2inv95`. If
|
232 |
+
`only_position` is False, the chi-square distribution has 4 degrees of
|
233 |
+
freedom, otherwise 2.
|
234 |
+
Parameters
|
235 |
+
----------
|
236 |
+
mean : ndarray
|
237 |
+
Mean vector over the state distribution (8 dimensional).
|
238 |
+
covariance : ndarray
|
239 |
+
Covariance of the state distribution (8x8 dimensional).
|
240 |
+
measurements : ndarray
|
241 |
+
An Nx4 dimensional matrix of N measurements, each in
|
242 |
+
format (x, y, a, h) where (x, y) is the bounding box center
|
243 |
+
position, a the aspect ratio, and h the height.
|
244 |
+
only_position : Optional[bool]
|
245 |
+
If True, distance computation is done with respect to the bounding
|
246 |
+
box center position only.
|
247 |
+
Returns
|
248 |
+
-------
|
249 |
+
ndarray
|
250 |
+
Returns an array of length N, where the i-th element contains the
|
251 |
+
squared Mahalanobis distance between (mean, covariance) and
|
252 |
+
`measurements[i]`.
|
253 |
+
"""
|
254 |
+
mean, covariance = self.project(mean, covariance)
|
255 |
+
if only_position:
|
256 |
+
mean, covariance = mean[:2], covariance[:2, :2]
|
257 |
+
measurements = measurements[:, :2]
|
258 |
+
|
259 |
+
d = measurements - mean
|
260 |
+
if metric == 'gaussian':
|
261 |
+
return np.sum(d * d, axis=1)
|
262 |
+
elif metric == 'maha':
|
263 |
+
cholesky_factor = np.linalg.cholesky(covariance)
|
264 |
+
z = scipy.linalg.solve_triangular(
|
265 |
+
cholesky_factor, d.T, lower=True, check_finite=False,
|
266 |
+
overwrite_b=True)
|
267 |
+
squared_maha = np.sum(z * z, axis=0)
|
268 |
+
return squared_maha
|
269 |
+
else:
|
270 |
+
raise ValueError('invalid distance metric')
|
byte_track/tracker/matching.py
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import scipy
|
3 |
+
import lap
|
4 |
+
from scipy.spatial.distance import cdist
|
5 |
+
from cython_bbox import bbox_overlaps as bbox_ious
|
6 |
+
import kalman_filter
|
7 |
+
|
8 |
+
def merge_matches(m1, m2, shape):
|
9 |
+
O,P,Q = shape
|
10 |
+
m1 = np.asarray(m1)
|
11 |
+
m2 = np.asarray(m2)
|
12 |
+
|
13 |
+
M1 = scipy.sparse.coo_matrix((np.ones(len(m1)), (m1[:, 0], m1[:, 1])), shape=(O, P))
|
14 |
+
M2 = scipy.sparse.coo_matrix((np.ones(len(m2)), (m2[:, 0], m2[:, 1])), shape=(P, Q))
|
15 |
+
|
16 |
+
mask = M1*M2
|
17 |
+
match = mask.nonzero()
|
18 |
+
match = list(zip(match[0], match[1]))
|
19 |
+
unmatched_O = tuple(set(range(O)) - set([i for i, j in match]))
|
20 |
+
unmatched_Q = tuple(set(range(Q)) - set([j for i, j in match]))
|
21 |
+
|
22 |
+
return match, unmatched_O, unmatched_Q
|
23 |
+
|
24 |
+
|
25 |
+
def _indices_to_matches(cost_matrix, indices, thresh):
|
26 |
+
matched_cost = cost_matrix[tuple(zip(*indices))]
|
27 |
+
matched_mask = (matched_cost <= thresh)
|
28 |
+
|
29 |
+
matches = indices[matched_mask]
|
30 |
+
unmatched_a = tuple(set(range(cost_matrix.shape[0])) - set(matches[:, 0]))
|
31 |
+
unmatched_b = tuple(set(range(cost_matrix.shape[1])) - set(matches[:, 1]))
|
32 |
+
|
33 |
+
return matches, unmatched_a, unmatched_b
|
34 |
+
|
35 |
+
|
36 |
+
def linear_assignment(cost_matrix, thresh):
|
37 |
+
if cost_matrix.size == 0:
|
38 |
+
return np.empty((0, 2), dtype=int), tuple(range(cost_matrix.shape[0])), tuple(range(cost_matrix.shape[1]))
|
39 |
+
matches, unmatched_a, unmatched_b = [], [], []
|
40 |
+
cost, x, y = lap.lapjv(cost_matrix, extend_cost=True, cost_limit=thresh)
|
41 |
+
for ix, mx in enumerate(x):
|
42 |
+
if mx >= 0:
|
43 |
+
matches.append([ix, mx])
|
44 |
+
unmatched_a = np.where(x < 0)[0]
|
45 |
+
unmatched_b = np.where(y < 0)[0]
|
46 |
+
matches = np.asarray(matches)
|
47 |
+
return matches, unmatched_a, unmatched_b
|
48 |
+
|
49 |
+
|
50 |
+
def ious(atlbrs, btlbrs):
|
51 |
+
"""
|
52 |
+
Compute cost based on IoU
|
53 |
+
:type atlbrs: list[tlbr] | np.ndarray
|
54 |
+
:type atlbrs: list[tlbr] | np.ndarray
|
55 |
+
|
56 |
+
:rtype ious np.ndarray
|
57 |
+
"""
|
58 |
+
ious = np.zeros((len(atlbrs), len(btlbrs)), dtype=np.float)
|
59 |
+
if ious.size == 0:
|
60 |
+
return ious
|
61 |
+
|
62 |
+
ious = bbox_ious(
|
63 |
+
np.ascontiguousarray(atlbrs, dtype=np.float),
|
64 |
+
np.ascontiguousarray(btlbrs, dtype=np.float)
|
65 |
+
)
|
66 |
+
|
67 |
+
return ious
|
68 |
+
|
69 |
+
|
70 |
+
def iou_distance(atracks, btracks):
|
71 |
+
"""
|
72 |
+
Compute cost based on IoU
|
73 |
+
:type atracks: list[STrack]
|
74 |
+
:type btracks: list[STrack]
|
75 |
+
|
76 |
+
:rtype cost_matrix np.ndarray
|
77 |
+
"""
|
78 |
+
|
79 |
+
if (len(atracks)>0 and isinstance(atracks[0], np.ndarray)) or (len(btracks) > 0 and isinstance(btracks[0], np.ndarray)):
|
80 |
+
atlbrs = atracks
|
81 |
+
btlbrs = btracks
|
82 |
+
else:
|
83 |
+
atlbrs = [track.tlbr for track in atracks]
|
84 |
+
btlbrs = [track.tlbr for track in btracks]
|
85 |
+
_ious = ious(atlbrs, btlbrs)
|
86 |
+
cost_matrix = 1 - _ious
|
87 |
+
|
88 |
+
return cost_matrix
|
89 |
+
|
90 |
+
def v_iou_distance(atracks, btracks):
|
91 |
+
"""
|
92 |
+
Compute cost based on IoU
|
93 |
+
:type atracks: list[STrack]
|
94 |
+
:type btracks: list[STrack]
|
95 |
+
|
96 |
+
:rtype cost_matrix np.ndarray
|
97 |
+
"""
|
98 |
+
|
99 |
+
if (len(atracks)>0 and isinstance(atracks[0], np.ndarray)) or (len(btracks) > 0 and isinstance(btracks[0], np.ndarray)):
|
100 |
+
atlbrs = atracks
|
101 |
+
btlbrs = btracks
|
102 |
+
else:
|
103 |
+
atlbrs = [track.tlwh_to_tlbr(track.pred_bbox) for track in atracks]
|
104 |
+
btlbrs = [track.tlwh_to_tlbr(track.pred_bbox) for track in btracks]
|
105 |
+
_ious = ious(atlbrs, btlbrs)
|
106 |
+
cost_matrix = 1 - _ious
|
107 |
+
|
108 |
+
return cost_matrix
|
109 |
+
|
110 |
+
def embedding_distance(tracks, detections, metric='cosine'):
|
111 |
+
"""
|
112 |
+
:param tracks: list[STrack]
|
113 |
+
:param detections: list[BaseTrack]
|
114 |
+
:param metric:
|
115 |
+
:return: cost_matrix np.ndarray
|
116 |
+
"""
|
117 |
+
|
118 |
+
cost_matrix = np.zeros((len(tracks), len(detections)), dtype=np.float)
|
119 |
+
if cost_matrix.size == 0:
|
120 |
+
return cost_matrix
|
121 |
+
det_features = np.asarray([track.curr_feat for track in detections], dtype=np.float)
|
122 |
+
#for i, track in enumerate(tracks):
|
123 |
+
#cost_matrix[i, :] = np.maximum(0.0, cdist(track.smooth_feat.reshape(1,-1), det_features, metric))
|
124 |
+
track_features = np.asarray([track.smooth_feat for track in tracks], dtype=np.float)
|
125 |
+
cost_matrix = np.maximum(0.0, cdist(track_features, det_features, metric)) # Nomalized features
|
126 |
+
return cost_matrix
|
127 |
+
|
128 |
+
|
129 |
+
def gate_cost_matrix(kf, cost_matrix, tracks, detections, only_position=False):
|
130 |
+
if cost_matrix.size == 0:
|
131 |
+
return cost_matrix
|
132 |
+
gating_dim = 2 if only_position else 4
|
133 |
+
gating_threshold = kalman_filter.chi2inv95[gating_dim]
|
134 |
+
measurements = np.asarray([det.to_xyah() for det in detections])
|
135 |
+
for row, track in enumerate(tracks):
|
136 |
+
gating_distance = kf.gating_distance(
|
137 |
+
track.mean, track.covariance, measurements, only_position)
|
138 |
+
cost_matrix[row, gating_distance > gating_threshold] = np.inf
|
139 |
+
return cost_matrix
|
140 |
+
|
141 |
+
|
142 |
+
def fuse_motion(kf, cost_matrix, tracks, detections, only_position=False, lambda_=0.98):
|
143 |
+
if cost_matrix.size == 0:
|
144 |
+
return cost_matrix
|
145 |
+
gating_dim = 2 if only_position else 4
|
146 |
+
gating_threshold = kalman_filter.chi2inv95[gating_dim]
|
147 |
+
measurements = np.asarray([det.to_xyah() for det in detections])
|
148 |
+
for row, track in enumerate(tracks):
|
149 |
+
gating_distance = kf.gating_distance(
|
150 |
+
track.mean, track.covariance, measurements, only_position, metric='maha')
|
151 |
+
cost_matrix[row, gating_distance > gating_threshold] = np.inf
|
152 |
+
cost_matrix[row] = lambda_ * cost_matrix[row] + (1 - lambda_) * gating_distance
|
153 |
+
return cost_matrix
|
154 |
+
|
155 |
+
|
156 |
+
def fuse_iou(cost_matrix, tracks, detections):
|
157 |
+
if cost_matrix.size == 0:
|
158 |
+
return cost_matrix
|
159 |
+
reid_sim = 1 - cost_matrix
|
160 |
+
iou_dist = iou_distance(tracks, detections)
|
161 |
+
iou_sim = 1 - iou_dist
|
162 |
+
fuse_sim = reid_sim * (1 + iou_sim) / 2
|
163 |
+
det_scores = np.array([det.score for det in detections])
|
164 |
+
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
|
165 |
+
#fuse_sim = fuse_sim * (1 + det_scores) / 2
|
166 |
+
fuse_cost = 1 - fuse_sim
|
167 |
+
return fuse_cost
|
168 |
+
|
169 |
+
|
170 |
+
def fuse_score(cost_matrix, detections):
|
171 |
+
if cost_matrix.size == 0:
|
172 |
+
return cost_matrix
|
173 |
+
iou_sim = 1 - cost_matrix
|
174 |
+
det_scores = np.array([det.score for det in detections])
|
175 |
+
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
|
176 |
+
fuse_sim = iou_sim * det_scores
|
177 |
+
fuse_cost = 1 - fuse_sim
|
178 |
+
return fuse_cost
|