supervisely 6.73.417__py3-none-any.whl → 6.73.419__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- supervisely/api/entity_annotation/figure_api.py +89 -45
- supervisely/nn/inference/inference.py +61 -45
- supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
- supervisely/nn/inference/object_detection/object_detection.py +1 -0
- supervisely/nn/inference/session.py +4 -4
- supervisely/nn/model/model_api.py +31 -20
- supervisely/nn/model/prediction.py +11 -0
- supervisely/nn/model/prediction_session.py +33 -6
- supervisely/nn/tracker/__init__.py +1 -2
- supervisely/nn/tracker/base_tracker.py +44 -0
- supervisely/nn/tracker/botsort/__init__.py +1 -0
- supervisely/nn/tracker/botsort/botsort_config.yaml +31 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
- supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
- supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
- supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
- supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
- supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
- supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
- supervisely/nn/tracker/botsort_tracker.py +259 -0
- supervisely/project/project.py +1 -1
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/METADATA +5 -3
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/RECORD +29 -42
- supervisely/nn/tracker/bot_sort/__init__.py +0 -21
- supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
- supervisely/nn/tracker/bot_sort/matching.py +0 -127
- supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
- supervisely/nn/tracker/deep_sort/__init__.py +0 -6
- supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
- supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
- supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
- supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
- supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
- supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
- supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
- supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
- supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
- supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
- supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
- supervisely/nn/tracker/tracker.py +0 -285
- supervisely/nn/tracker/utils/kalman_filter.py +0 -492
- supervisely/nn/tracking/__init__.py +0 -1
- supervisely/nn/tracking/boxmot.py +0 -114
- supervisely/nn/tracking/tracking.py +0 -24
- /supervisely/nn/tracker/{utils → botsort/osnet_reid}/__init__.py +0 -0
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/LICENSE +0 -0
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/WHEEL +0 -0
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.417.dist-info → supervisely-6.73.419.dist-info}/top_level.txt +0 -0
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import lap # pylint: disable=import-error
|
|
2
|
-
import numpy as np
|
|
3
|
-
from cython_bbox import bbox_overlaps as bbox_ious # pylint: disable=import-error
|
|
4
|
-
from scipy.spatial.distance import cdist
|
|
5
|
-
|
|
6
|
-
from supervisely.nn.tracker.utils import kalman_filter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def linear_assignment(cost_matrix, thresh):
|
|
10
|
-
if cost_matrix.size == 0:
|
|
11
|
-
return (
|
|
12
|
-
np.empty((0, 2), dtype=int),
|
|
13
|
-
tuple(range(cost_matrix.shape[0])),
|
|
14
|
-
tuple(range(cost_matrix.shape[1])),
|
|
15
|
-
)
|
|
16
|
-
matches, unmatched_a, unmatched_b = [], [], []
|
|
17
|
-
cost, x, y = lap.lapjv(cost_matrix, extend_cost=True, cost_limit=thresh)
|
|
18
|
-
for ix, mx in enumerate(x):
|
|
19
|
-
if mx >= 0:
|
|
20
|
-
matches.append([ix, mx])
|
|
21
|
-
unmatched_a = np.where(x < 0)[0]
|
|
22
|
-
unmatched_b = np.where(y < 0)[0]
|
|
23
|
-
matches = np.asarray(matches)
|
|
24
|
-
return matches, unmatched_a, unmatched_b
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def ious(atlbrs, btlbrs):
|
|
28
|
-
"""
|
|
29
|
-
Compute cost based on IoU
|
|
30
|
-
:type atlbrs: list[tlbr] | np.ndarray
|
|
31
|
-
:type atlbrs: list[tlbr] | np.ndarray
|
|
32
|
-
|
|
33
|
-
:rtype ious np.ndarray
|
|
34
|
-
"""
|
|
35
|
-
ious = np.zeros((len(atlbrs), len(btlbrs)), dtype=float)
|
|
36
|
-
if ious.size == 0:
|
|
37
|
-
return ious
|
|
38
|
-
|
|
39
|
-
ious = bbox_ious(
|
|
40
|
-
np.ascontiguousarray(atlbrs, dtype=float), np.ascontiguousarray(btlbrs, dtype=float)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
return ious
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def iou_distance(atracks, btracks):
|
|
47
|
-
"""
|
|
48
|
-
Compute cost based on IoU
|
|
49
|
-
:type atracks: list[STrack]
|
|
50
|
-
:type btracks: list[STrack]
|
|
51
|
-
|
|
52
|
-
:rtype cost_matrix np.ndarray
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
if (len(atracks) > 0 and isinstance(atracks[0], np.ndarray)) or (
|
|
56
|
-
len(btracks) > 0 and isinstance(btracks[0], np.ndarray)
|
|
57
|
-
):
|
|
58
|
-
atlbrs = atracks
|
|
59
|
-
btlbrs = btracks
|
|
60
|
-
else:
|
|
61
|
-
atlbrs = [track.tlbr for track in atracks]
|
|
62
|
-
btlbrs = [track.tlbr for track in btracks]
|
|
63
|
-
_ious = ious(atlbrs, btlbrs)
|
|
64
|
-
cost_matrix = 1 - _ious
|
|
65
|
-
|
|
66
|
-
return cost_matrix
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def embedding_distance(tracks, detections, metric="cosine"):
|
|
70
|
-
"""
|
|
71
|
-
:param tracks: list[STrack]
|
|
72
|
-
:param detections: list[BaseTrack]
|
|
73
|
-
:param metric:
|
|
74
|
-
:return: cost_matrix np.ndarray
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
cost_matrix = np.zeros((len(tracks), len(detections)), dtype=float)
|
|
78
|
-
if cost_matrix.size == 0:
|
|
79
|
-
return cost_matrix
|
|
80
|
-
det_features = np.asarray([track.curr_feat for track in detections], dtype=float)
|
|
81
|
-
track_features = np.asarray([track.smooth_feat for track in tracks], dtype=float)
|
|
82
|
-
|
|
83
|
-
cost_matrix = np.maximum(
|
|
84
|
-
0.0, cdist(track_features, det_features, metric)
|
|
85
|
-
) # / 2.0 # Nomalized features
|
|
86
|
-
return cost_matrix
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def fuse_motion(kf, cost_matrix, tracks, detections, only_position=False, lambda_=0.98):
|
|
90
|
-
if cost_matrix.size == 0:
|
|
91
|
-
return cost_matrix
|
|
92
|
-
gating_dim = 2 if only_position else 4
|
|
93
|
-
gating_threshold = kalman_filter.chi2inv95[gating_dim]
|
|
94
|
-
# measurements = np.asarray([det.to_xyah() for det in detections])
|
|
95
|
-
measurements = np.asarray([det.to_xywh() for det in detections])
|
|
96
|
-
for row, track in enumerate(tracks):
|
|
97
|
-
gating_distance = kf.gating_distance(
|
|
98
|
-
track.mean, track.covariance, measurements, only_position, metric="maha"
|
|
99
|
-
)
|
|
100
|
-
cost_matrix[row, gating_distance > gating_threshold] = np.inf
|
|
101
|
-
cost_matrix[row] = lambda_ * cost_matrix[row] + (1 - lambda_) * gating_distance
|
|
102
|
-
return cost_matrix
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def fuse_iou(cost_matrix, tracks, detections):
|
|
106
|
-
if cost_matrix.size == 0:
|
|
107
|
-
return cost_matrix
|
|
108
|
-
reid_sim = 1 - cost_matrix
|
|
109
|
-
iou_dist = iou_distance(tracks, detections)
|
|
110
|
-
iou_sim = 1 - iou_dist
|
|
111
|
-
fuse_sim = reid_sim * (1 + iou_sim) / 2
|
|
112
|
-
det_scores = np.array([det.score for det in detections])
|
|
113
|
-
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
|
|
114
|
-
# fuse_sim = fuse_sim * (1 + det_scores) / 2
|
|
115
|
-
fuse_cost = 1 - fuse_sim
|
|
116
|
-
return fuse_cost
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def fuse_score(cost_matrix, detections):
|
|
120
|
-
if cost_matrix.size == 0:
|
|
121
|
-
return cost_matrix
|
|
122
|
-
iou_sim = 1 - cost_matrix
|
|
123
|
-
det_scores = np.array([det.score for det in detections])
|
|
124
|
-
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
|
|
125
|
-
fuse_sim = iou_sim * det_scores
|
|
126
|
-
fuse_cost = 1 - fuse_sim
|
|
127
|
-
return fuse_cost
|
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Dict, List, Tuple, Union
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
import requests
|
|
8
|
-
|
|
9
|
-
from supervisely import Annotation, Label
|
|
10
|
-
from supervisely.nn.tracker.tracker import BaseDetection as Detection
|
|
11
|
-
from supervisely.nn.tracker.tracker import BaseTrack, BaseTracker
|
|
12
|
-
from supervisely.sly_logger import logger
|
|
13
|
-
|
|
14
|
-
from . import matching
|
|
15
|
-
from .bot_sort import (
|
|
16
|
-
BoTSORT,
|
|
17
|
-
STrack,
|
|
18
|
-
TrackState,
|
|
19
|
-
joint_stracks,
|
|
20
|
-
remove_duplicate_stracks,
|
|
21
|
-
sub_stracks,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class Track(BaseTrack, STrack):
|
|
26
|
-
def __init__(self, tlwh, confidence: float, feature=None, sly_label: Label = None):
|
|
27
|
-
self.track_id = None
|
|
28
|
-
STrack.__init__(self, tlwh, confidence, sly_label.obj_class.name, feature)
|
|
29
|
-
self.sly_label = sly_label
|
|
30
|
-
|
|
31
|
-
def get_sly_label(self):
|
|
32
|
-
return self.sly_label
|
|
33
|
-
|
|
34
|
-
def update(self, new: Track, frame_id):
|
|
35
|
-
STrack.update(self, new, frame_id)
|
|
36
|
-
self.sly_label = new.get_sly_label()
|
|
37
|
-
|
|
38
|
-
def re_activate(self, new: Track, frame_id, new_id=False):
|
|
39
|
-
STrack.re_activate(self, new, frame_id, new_id)
|
|
40
|
-
self.sly_label = new.get_sly_label()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class _BoTSORT(BoTSORT):
|
|
44
|
-
def __init__(self, args):
|
|
45
|
-
super().__init__(args)
|
|
46
|
-
|
|
47
|
-
# for type hinting
|
|
48
|
-
self.tracked_stracks = [] # type: list[Track]
|
|
49
|
-
self.lost_stracks = [] # type: list[Track]
|
|
50
|
-
self.removed_stracks = [] # type: list[Track]
|
|
51
|
-
|
|
52
|
-
# for pylint
|
|
53
|
-
self.frame_id = 0
|
|
54
|
-
|
|
55
|
-
def _init_track(
|
|
56
|
-
self, dets: np.ndarray, scores: np.ndarray, labels: List[Label], features: np.ndarray
|
|
57
|
-
):
|
|
58
|
-
if len(dets) == 0:
|
|
59
|
-
return []
|
|
60
|
-
if self.args.with_reid:
|
|
61
|
-
return [
|
|
62
|
-
Track(Track.tlbr_to_tlwh(tlbr), s, f, l)
|
|
63
|
-
for (tlbr, s, f, l) in zip(dets, scores, features, labels)
|
|
64
|
-
]
|
|
65
|
-
return [
|
|
66
|
-
Track(Track.tlbr_to_tlwh(tlbr), s, None, l)
|
|
67
|
-
for (tlbr, s, l) in zip(dets, scores, labels)
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
def get_dists(self, tracks: List[Track], detections: List[Track]):
|
|
71
|
-
# Associate with high score detection boxes
|
|
72
|
-
ious_dists = matching.iou_distance(tracks, detections)
|
|
73
|
-
|
|
74
|
-
# if not self.args.mot20:
|
|
75
|
-
ious_dists = matching.fuse_score(ious_dists, detections)
|
|
76
|
-
|
|
77
|
-
# Remove detections with different shapes
|
|
78
|
-
for track_i, track in enumerate(tracks):
|
|
79
|
-
for det_i, det in enumerate(detections):
|
|
80
|
-
if track.get_sly_label().geometry.name() != det.get_sly_label().geometry.name():
|
|
81
|
-
ious_dists[track_i, det_i] = 1.0
|
|
82
|
-
|
|
83
|
-
if not self.args.with_reid:
|
|
84
|
-
return ious_dists
|
|
85
|
-
|
|
86
|
-
ious_dists_mask = ious_dists > self.args.proximity_thresh
|
|
87
|
-
emb_dists = matching.embedding_distance(tracks, detections) / 2.0
|
|
88
|
-
# raw_emb_dists = emb_dists.copy()
|
|
89
|
-
emb_dists[emb_dists > self.args.appearance_thresh] = 1.0
|
|
90
|
-
emb_dists[ious_dists_mask] = 1.0
|
|
91
|
-
dists = np.minimum(ious_dists, emb_dists)
|
|
92
|
-
|
|
93
|
-
# Popular ReID method (JDE / FairMOT)
|
|
94
|
-
# raw_emb_dists = matching.embedding_distance(strack_pool, detections)
|
|
95
|
-
# dists = matching.fuse_motion(self.kalman_filter, raw_emb_dists, strack_pool, detections)
|
|
96
|
-
# emb_dists = dists
|
|
97
|
-
|
|
98
|
-
# IoU making ReID
|
|
99
|
-
# dists = matching.embedding_distance(strack_pool, detections)
|
|
100
|
-
# dists[ious_dists_mask] = 1.0
|
|
101
|
-
return dists
|
|
102
|
-
|
|
103
|
-
def update(self, detections_: List[Detection], img) -> List[Track]:
|
|
104
|
-
self.frame_id += 1
|
|
105
|
-
activated_starcks = []
|
|
106
|
-
refind_stracks = []
|
|
107
|
-
lost_stracks = []
|
|
108
|
-
removed_stracks = []
|
|
109
|
-
|
|
110
|
-
if len(detections_):
|
|
111
|
-
scores = np.array([det.confidence for det in detections_])
|
|
112
|
-
bboxes = np.array([det.tlbr()[:4] for det in detections_])
|
|
113
|
-
labels = np.array([det.sly_label for det in detections_])
|
|
114
|
-
features = np.array([det.feature for det in detections_])
|
|
115
|
-
|
|
116
|
-
# Remove bad detections
|
|
117
|
-
lowest_inds = scores > self.args.track_low_thresh
|
|
118
|
-
bboxes = bboxes[lowest_inds]
|
|
119
|
-
scores = scores[lowest_inds]
|
|
120
|
-
labels = labels[lowest_inds]
|
|
121
|
-
features = features[lowest_inds]
|
|
122
|
-
|
|
123
|
-
# Find high threshold detections
|
|
124
|
-
remain_inds = scores > self.args.track_high_thresh
|
|
125
|
-
dets = bboxes[remain_inds]
|
|
126
|
-
scores_keep = scores[remain_inds]
|
|
127
|
-
labels_keep = labels[remain_inds]
|
|
128
|
-
features_keep = features[remain_inds]
|
|
129
|
-
else:
|
|
130
|
-
bboxes = []
|
|
131
|
-
scores = []
|
|
132
|
-
labels = []
|
|
133
|
-
dets = []
|
|
134
|
-
scores_keep = []
|
|
135
|
-
labels_keep = []
|
|
136
|
-
features_keep = []
|
|
137
|
-
|
|
138
|
-
"""Extract embeddings """
|
|
139
|
-
if self.args.with_reid:
|
|
140
|
-
features_keep = self.encoder.inference(img, dets)
|
|
141
|
-
|
|
142
|
-
"""Detections"""
|
|
143
|
-
detections = self._init_track(dets, scores_keep, labels_keep, features_keep)
|
|
144
|
-
|
|
145
|
-
""" Add newly detected tracklets to tracked_stracks"""
|
|
146
|
-
unconfirmed = []
|
|
147
|
-
tracked_stracks = [] # type: list[STrack]
|
|
148
|
-
for track in self.tracked_stracks:
|
|
149
|
-
if not track.is_activated:
|
|
150
|
-
unconfirmed.append(track)
|
|
151
|
-
else:
|
|
152
|
-
tracked_stracks.append(track)
|
|
153
|
-
|
|
154
|
-
""" Step 2: First association, with high score detection boxes"""
|
|
155
|
-
strack_pool: List[Track] = joint_stracks(tracked_stracks, self.lost_stracks)
|
|
156
|
-
|
|
157
|
-
# Predict the current location with KF
|
|
158
|
-
STrack.multi_predict(strack_pool)
|
|
159
|
-
|
|
160
|
-
# Fix camera motion
|
|
161
|
-
warp = self.gmc.apply(img, dets)
|
|
162
|
-
STrack.multi_gmc(strack_pool, warp)
|
|
163
|
-
STrack.multi_gmc(unconfirmed, warp)
|
|
164
|
-
|
|
165
|
-
dists = self.get_dists(strack_pool, detections)
|
|
166
|
-
|
|
167
|
-
matches, u_track, u_detection = matching.linear_assignment(
|
|
168
|
-
dists, thresh=self.args.match_thresh
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
for itracked, idet in matches:
|
|
172
|
-
track: Track = strack_pool[itracked]
|
|
173
|
-
det: Track = detections[idet]
|
|
174
|
-
if track.state == TrackState.Tracked:
|
|
175
|
-
track.update(detections[idet], self.frame_id)
|
|
176
|
-
activated_starcks.append(track)
|
|
177
|
-
else:
|
|
178
|
-
track.re_activate(det, self.frame_id, new_id=False)
|
|
179
|
-
refind_stracks.append(track)
|
|
180
|
-
|
|
181
|
-
""" Step 3: Second association, with low score detection boxes"""
|
|
182
|
-
if len(scores):
|
|
183
|
-
inds_second = scores < self.args.track_high_thresh
|
|
184
|
-
dets_second = bboxes[inds_second]
|
|
185
|
-
scores_second = scores[inds_second]
|
|
186
|
-
labels_second = labels[inds_second]
|
|
187
|
-
features_second = features[inds_second]
|
|
188
|
-
else:
|
|
189
|
-
dets_second = []
|
|
190
|
-
scores_second = []
|
|
191
|
-
labels_second = []
|
|
192
|
-
features_second = []
|
|
193
|
-
|
|
194
|
-
# association the untrack to the low score detections
|
|
195
|
-
detections_second = self._init_track(
|
|
196
|
-
dets_second, scores_second, labels_second, features_second
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
r_tracked_stracks = [
|
|
200
|
-
strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked
|
|
201
|
-
]
|
|
202
|
-
dists = matching.iou_distance(r_tracked_stracks, detections_second)
|
|
203
|
-
matches, u_track, u_detection_second = matching.linear_assignment(dists, thresh=0.5)
|
|
204
|
-
for itracked, idet in matches:
|
|
205
|
-
track = r_tracked_stracks[itracked]
|
|
206
|
-
det = detections_second[idet]
|
|
207
|
-
if track.state == TrackState.Tracked:
|
|
208
|
-
track.update(det, self.frame_id)
|
|
209
|
-
activated_starcks.append(track)
|
|
210
|
-
else:
|
|
211
|
-
track.re_activate(det, self.frame_id, new_id=False)
|
|
212
|
-
refind_stracks.append(track)
|
|
213
|
-
|
|
214
|
-
for it in u_track:
|
|
215
|
-
track = r_tracked_stracks[it]
|
|
216
|
-
if not track.state == TrackState.Lost:
|
|
217
|
-
track.mark_lost()
|
|
218
|
-
lost_stracks.append(track)
|
|
219
|
-
|
|
220
|
-
"""Deal with unconfirmed tracks, usually tracks with only one beginning frame"""
|
|
221
|
-
detections = [detections[i] for i in u_detection]
|
|
222
|
-
dists = self.get_dists(unconfirmed, detections)
|
|
223
|
-
matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
|
|
224
|
-
for itracked, idet in matches:
|
|
225
|
-
unconfirmed[itracked].update(detections[idet], self.frame_id)
|
|
226
|
-
activated_starcks.append(unconfirmed[itracked])
|
|
227
|
-
for it in u_unconfirmed:
|
|
228
|
-
track = unconfirmed[it]
|
|
229
|
-
track.mark_removed()
|
|
230
|
-
removed_stracks.append(track)
|
|
231
|
-
|
|
232
|
-
""" Step 4: Init new stracks"""
|
|
233
|
-
for inew in u_detection:
|
|
234
|
-
track = detections[inew]
|
|
235
|
-
if track.score < self.new_track_thresh:
|
|
236
|
-
continue
|
|
237
|
-
|
|
238
|
-
track.activate(self.kalman_filter, self.frame_id)
|
|
239
|
-
activated_starcks.append(track)
|
|
240
|
-
|
|
241
|
-
""" Step 5: Update state"""
|
|
242
|
-
for track in self.lost_stracks:
|
|
243
|
-
if self.frame_id - track.end_frame > self.max_time_lost:
|
|
244
|
-
track.mark_removed()
|
|
245
|
-
removed_stracks.append(track)
|
|
246
|
-
|
|
247
|
-
""" Merge """
|
|
248
|
-
self.tracked_stracks = [t for t in self.tracked_stracks if t.state == TrackState.Tracked]
|
|
249
|
-
self.tracked_stracks = joint_stracks(self.tracked_stracks, activated_starcks)
|
|
250
|
-
self.tracked_stracks = joint_stracks(self.tracked_stracks, refind_stracks)
|
|
251
|
-
self.lost_stracks = sub_stracks(self.lost_stracks, self.tracked_stracks)
|
|
252
|
-
self.lost_stracks.extend(lost_stracks)
|
|
253
|
-
self.lost_stracks = sub_stracks(self.lost_stracks, self.removed_stracks)
|
|
254
|
-
self.removed_stracks.extend(removed_stracks)
|
|
255
|
-
self.tracked_stracks, self.lost_stracks = remove_duplicate_stracks(
|
|
256
|
-
self.tracked_stracks, self.lost_stracks
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
# output_stracks = [track for track in self.tracked_stracks if track.is_activated]
|
|
260
|
-
output_stracks = [track for track in self.tracked_stracks]
|
|
261
|
-
|
|
262
|
-
return output_stracks
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
class BoTTracker(BaseTracker):
|
|
266
|
-
def __init__(self, settings=None):
|
|
267
|
-
if settings is None:
|
|
268
|
-
settings = {}
|
|
269
|
-
super().__init__(settings=settings)
|
|
270
|
-
self.tracker = _BoTSORT(self.args)
|
|
271
|
-
|
|
272
|
-
if self.args.with_reid:
|
|
273
|
-
if not Path(self.args.fast_reid_weights).exists():
|
|
274
|
-
logger.info("Downloading ReID weights...")
|
|
275
|
-
|
|
276
|
-
with requests.get(self.args.fast_reid_weights_url, stream=True) as r:
|
|
277
|
-
r.raise_for_status()
|
|
278
|
-
with open(self.args.fast_reid_weights, "wb") as f:
|
|
279
|
-
for chunk in r.iter_content(chunk_size=8192):
|
|
280
|
-
f.write(chunk)
|
|
281
|
-
|
|
282
|
-
def default_settings(self):
|
|
283
|
-
return {
|
|
284
|
-
"name": None,
|
|
285
|
-
"ablation": None,
|
|
286
|
-
# Tracking
|
|
287
|
-
"track_high_thresh": 0.6,
|
|
288
|
-
"track_low_thresh": 0.1,
|
|
289
|
-
"new_track_thresh": 0.7,
|
|
290
|
-
"track_buffer": 30,
|
|
291
|
-
"match_thresh": 0.5,
|
|
292
|
-
"min_box_area": 10,
|
|
293
|
-
"fuse_score": False,
|
|
294
|
-
# CMC
|
|
295
|
-
"cmc_method": "sparseOptFlow",
|
|
296
|
-
"gmc_config": None,
|
|
297
|
-
# ReID
|
|
298
|
-
"with_reid": False,
|
|
299
|
-
"fast_reid_config": f"{Path(__file__).parent}/fast_reid/configs/MOT17/sbs_S50.yml",
|
|
300
|
-
"fast_reid_weights": f"pretrained/yolo7x.pt",
|
|
301
|
-
"fast_reid_weights_url": r"https://github.com/WongKinYiu/yolov7/releases/download/v0.1/yolov7.pt",
|
|
302
|
-
"proximity_thresh": 0.5,
|
|
303
|
-
"appearance_thresh": 0.25,
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
def track(
|
|
307
|
-
self,
|
|
308
|
-
source: Union[List[np.ndarray], List[str], str],
|
|
309
|
-
frame_to_annotation: Dict[int, Annotation],
|
|
310
|
-
frame_shape: Tuple[int, int],
|
|
311
|
-
pbar_cb=None,
|
|
312
|
-
) -> Annotation:
|
|
313
|
-
"""
|
|
314
|
-
Track objects in the video using BoTSort algorithm.
|
|
315
|
-
|
|
316
|
-
:param source: List of images, paths to images or path to the video file.
|
|
317
|
-
:type source: List[np.ndarray] | List[str] | str
|
|
318
|
-
:param frame_to_annotation: Dictionary with frame index as key and Annotation as value.
|
|
319
|
-
:type frame_to_annotation: Dict[int, Annotation]
|
|
320
|
-
:param frame_shape: Size of the frame (height, width).
|
|
321
|
-
:type frame_shape: Tuple[int, int]
|
|
322
|
-
:param pbar_cb: Callback to update progress bar.
|
|
323
|
-
:type pbar_cb: Callable, optional
|
|
324
|
-
|
|
325
|
-
:return: Video annotation with tracked objects.
|
|
326
|
-
:rtype: VideoAnnotation
|
|
327
|
-
|
|
328
|
-
:raises ValueError: If number of images and annotations are not the same.
|
|
329
|
-
|
|
330
|
-
:Usage example:
|
|
331
|
-
|
|
332
|
-
.. code-block:: python
|
|
333
|
-
|
|
334
|
-
import supervisely as sly
|
|
335
|
-
from supervisely.nn.tracker import BoTTracker
|
|
336
|
-
|
|
337
|
-
api = sly.Api()
|
|
338
|
-
|
|
339
|
-
project_id = 12345
|
|
340
|
-
video_id = 12345678
|
|
341
|
-
video_path = "video.mp4"
|
|
342
|
-
|
|
343
|
-
# Download video and get video info
|
|
344
|
-
video_info = api.video.get_info_by_id(video_id)
|
|
345
|
-
frame_shape = (video_info.frame_height, video_info.frame_width)
|
|
346
|
-
api.video.download_path(id=video_id, path=video_path)
|
|
347
|
-
|
|
348
|
-
# Run inference app to get detections
|
|
349
|
-
task_id = 12345 # detection app task id
|
|
350
|
-
session = sly.nn.inference.Session(api, task_id)
|
|
351
|
-
annotations = session.inference_video_id(video_id, 0, video_info.frames_count)
|
|
352
|
-
frame_to_annotation = {i: ann for i, ann in enumerate(annotations)}
|
|
353
|
-
|
|
354
|
-
# Run tracker
|
|
355
|
-
tracker = BoTTracker()
|
|
356
|
-
video_ann = tracker.track(video_path, frame_to_annotation, frame_shape)
|
|
357
|
-
|
|
358
|
-
# Upload result
|
|
359
|
-
model_meta = session.get_model_meta()
|
|
360
|
-
project_meta = sly.ProjectMeta.from_json(api.project.get_meta(project_id))
|
|
361
|
-
project_meta = project_meta.merge(model_meta)
|
|
362
|
-
api.project.update_meta(project_id, project_meta)
|
|
363
|
-
api.video.annotation.append(video_id, video_ann)
|
|
364
|
-
|
|
365
|
-
"""
|
|
366
|
-
if not isinstance(source, str):
|
|
367
|
-
if len(source) != len(frame_to_annotation):
|
|
368
|
-
raise ValueError("Number of images and annotations should be the same")
|
|
369
|
-
|
|
370
|
-
tracks_data = {}
|
|
371
|
-
logger.info("Starting BoTSort tracking...")
|
|
372
|
-
for frame_index, img in enumerate(self.frames_generator(source)):
|
|
373
|
-
self.update(img, frame_to_annotation[frame_index], frame_index, tracks_data=tracks_data)
|
|
374
|
-
|
|
375
|
-
if pbar_cb is not None:
|
|
376
|
-
pbar_cb()
|
|
377
|
-
|
|
378
|
-
return self.get_annotation(
|
|
379
|
-
tracks_data=tracks_data,
|
|
380
|
-
frame_shape=frame_shape,
|
|
381
|
-
frames_count=len(frame_to_annotation),
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
def update(
|
|
385
|
-
self, img, annotation: Annotation, frame_index, tracks_data: Dict[int, List[Dict]] = None
|
|
386
|
-
):
|
|
387
|
-
pred, sly_labels = self.convert_annotation(annotation)
|
|
388
|
-
|
|
389
|
-
detections = [Detection(p[:4], p[4], None, label) for p, label in zip(pred, sly_labels)]
|
|
390
|
-
|
|
391
|
-
self.tracker.update(detections, img)
|
|
392
|
-
|
|
393
|
-
if tracks_data is None:
|
|
394
|
-
tracks_data = {}
|
|
395
|
-
self.update_track_data(
|
|
396
|
-
tracks_data=tracks_data,
|
|
397
|
-
tracks=self.tracker.tracked_stracks,
|
|
398
|
-
frame_index=frame_index,
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
return tracks_data
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# vim: expandtab:ts=4:sw=4
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# vim: expandtab:ts=4:sw=4
|
|
2
|
-
import numpy as np
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Detection(object):
|
|
6
|
-
"""
|
|
7
|
-
This class represents a bounding box detection in a single image.
|
|
8
|
-
|
|
9
|
-
Parameters
|
|
10
|
-
----------
|
|
11
|
-
tlwh : array_like
|
|
12
|
-
Bounding box in format `(x, y, w, h)`.
|
|
13
|
-
confidence : float
|
|
14
|
-
Detector confidence score.
|
|
15
|
-
feature : array_like
|
|
16
|
-
A feature vector that describes the object contained in this image.
|
|
17
|
-
|
|
18
|
-
Attributes
|
|
19
|
-
----------
|
|
20
|
-
tlwh : ndarray
|
|
21
|
-
Bounding box in format `(top left x, top left y, width, height)`.
|
|
22
|
-
confidence : ndarray
|
|
23
|
-
Detector confidence score.
|
|
24
|
-
feature : ndarray | NoneType
|
|
25
|
-
A feature vector that describes the object contained in this image.
|
|
26
|
-
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(self, tlwh, confidence, feature):
|
|
30
|
-
self.tlwh = np.asarray(tlwh, dtype=float)
|
|
31
|
-
self.confidence = float(confidence)
|
|
32
|
-
self.feature = np.asarray(feature, dtype=np.float32)
|
|
33
|
-
|
|
34
|
-
def to_tlbr(self):
|
|
35
|
-
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
|
|
36
|
-
`(top left, bottom right)`.
|
|
37
|
-
"""
|
|
38
|
-
ret = self.tlwh.copy()
|
|
39
|
-
ret[2:] += ret[:2]
|
|
40
|
-
return ret
|
|
41
|
-
|
|
42
|
-
def to_xyah(self):
|
|
43
|
-
"""Convert bounding box to format `(center x, center y, aspect ratio,
|
|
44
|
-
height)`, where the aspect ratio is `width / height`.
|
|
45
|
-
"""
|
|
46
|
-
ret = self.tlwh.copy()
|
|
47
|
-
ret[:2] += ret[2:] / 2
|
|
48
|
-
ret[2] /= ret[3]
|
|
49
|
-
return ret
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# vim: expandtab:ts=4:sw=4
|
|
2
|
-
from __future__ import absolute_import
|
|
3
|
-
import numpy as np
|
|
4
|
-
from . import linear_assignment
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def iou(bbox, candidates):
|
|
8
|
-
"""Computer intersection over union.
|
|
9
|
-
|
|
10
|
-
Parameters
|
|
11
|
-
----------
|
|
12
|
-
bbox : ndarray
|
|
13
|
-
A bounding box in format `(top left x, top left y, width, height)`.
|
|
14
|
-
candidates : ndarray
|
|
15
|
-
A matrix of candidate bounding boxes (one per row) in the same format
|
|
16
|
-
as `bbox`.
|
|
17
|
-
|
|
18
|
-
Returns
|
|
19
|
-
-------
|
|
20
|
-
ndarray
|
|
21
|
-
The intersection over union in [0, 1] between the `bbox` and each
|
|
22
|
-
candidate. A higher score means a larger fraction of the `bbox` is
|
|
23
|
-
occluded by the candidate.
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
bbox_tl, bbox_br = bbox[:2], bbox[:2] + bbox[2:]
|
|
27
|
-
candidates_tl = candidates[:, :2]
|
|
28
|
-
candidates_br = candidates[:, :2] + candidates[:, 2:]
|
|
29
|
-
|
|
30
|
-
tl = np.c_[np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis],
|
|
31
|
-
np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis]]
|
|
32
|
-
br = np.c_[np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis],
|
|
33
|
-
np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis]]
|
|
34
|
-
wh = np.maximum(0., br - tl)
|
|
35
|
-
|
|
36
|
-
area_intersection = wh.prod(axis=1)
|
|
37
|
-
area_bbox = bbox[2:].prod()
|
|
38
|
-
area_candidates = candidates[:, 2:].prod(axis=1)
|
|
39
|
-
return area_intersection / (area_bbox + area_candidates - area_intersection)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def iou_cost(tracks, detections, track_indices=None,
|
|
43
|
-
detection_indices=None):
|
|
44
|
-
"""An intersection over union distance metric.
|
|
45
|
-
|
|
46
|
-
Parameters
|
|
47
|
-
----------
|
|
48
|
-
tracks : List[deep_sort.track.Track]
|
|
49
|
-
A list of tracks.
|
|
50
|
-
detections : List[deep_sort.detection.Detection]
|
|
51
|
-
A list of detections.
|
|
52
|
-
track_indices : Optional[List[int]]
|
|
53
|
-
A list of indices to tracks that should be matched. Defaults to
|
|
54
|
-
all `tracks`.
|
|
55
|
-
detection_indices : Optional[List[int]]
|
|
56
|
-
A list of indices to detections that should be matched. Defaults
|
|
57
|
-
to all `detections`.
|
|
58
|
-
|
|
59
|
-
Returns
|
|
60
|
-
-------
|
|
61
|
-
ndarray
|
|
62
|
-
Returns a cost matrix of shape
|
|
63
|
-
len(track_indices), len(detection_indices) where entry (i, j) is
|
|
64
|
-
`1 - iou(tracks[track_indices[i]], detections[detection_indices[j]])`.
|
|
65
|
-
|
|
66
|
-
"""
|
|
67
|
-
if track_indices is None:
|
|
68
|
-
track_indices = np.arange(len(tracks))
|
|
69
|
-
if detection_indices is None:
|
|
70
|
-
detection_indices = np.arange(len(detections))
|
|
71
|
-
|
|
72
|
-
cost_matrix = np.zeros((len(track_indices), len(detection_indices)))
|
|
73
|
-
for row, track_idx in enumerate(track_indices):
|
|
74
|
-
if tracks[track_idx].time_since_update > 1:
|
|
75
|
-
cost_matrix[row, :] = linear_assignment.INFTY_COST
|
|
76
|
-
continue
|
|
77
|
-
|
|
78
|
-
bbox = tracks[track_idx].to_tlwh()
|
|
79
|
-
candidates = np.asarray([detections[i].tlwh for i in detection_indices])
|
|
80
|
-
cost_matrix[row, :] = 1. - iou(bbox, candidates)
|
|
81
|
-
return cost_matrix
|