Skip to content

Commit 0afb410

Browse files
committed
add hybrid sort and fix errors of OC sort
1 parent 995813c commit 0afb410

File tree

10 files changed

+794
-96
lines changed

10 files changed

+794
-96
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ git checkout v2 # change to v2 branch !!
1919

2020
</div>
2121

22+
## 🗺️ Latest News
23+
24+
- ***2024.10.24*** Add Hybrid SORT and fix some errors and bugs of OC-SORT.
25+
2226
## ❤️ Introduction
2327

2428
This repo is a toolbox that implements the **tracking-by-detection paradigm multi-object tracker**. The detector supports:
@@ -38,6 +42,7 @@ and the tracker supports:
3842
- Strong SORT ([IEEE TMM 2023](https://arxiv.org/pdf/2202.13514))
3943
- Sparse Track ([arxiv 2306](https://arxiv.org/pdf/2306.05238))
4044
- UCMC Track ([AAAI 2024](http://arxiv.org/abs/2312.08952))
45+
- Hybrid SORT([AAAI 2024](https://ojs.aaai.org/index.php/AAAI/article/view/28471))
4146

4247
and the reid model supports:
4348

@@ -51,10 +56,6 @@ The highlights are:
5156

5257
![gif](figure/demo.gif)
5358

54-
## 🗺️ Roadmap
55-
56-
- [ x ] Add UCMC Track
57-
- [] Add more ReID modules.
5859

5960
## 🔨 Installation
6061

@@ -178,7 +179,7 @@ For example:
178179

179180
- ByteTrack: `python tracker/track.py --dataset uavdt --detector yolov8 --tracker bytetrack --kalman_format byte --detector_model_path weights/yolov8l_UAVDT_60epochs_20230509.pt`
180181

181-
- OCSort: `python tracker/track.py --dataset uavdt --detector yolov8 --tracker ocsort --kalman_format ocsort --detector_model_path weights/yolov8l_UAVDT_60epochs_20230509.pt`
182+
- OCSort: `python tracker/track.py --dataset mot17 --detector yolox --tracker ocsort --kalman_format ocsort --detector_model_path weights/bytetrack_m_mot17.pth.tar`
182183

183184
- C-BIoU Track: `python tracker/track.py --dataset uavdt --detector yolov8 --tracker c_bioutrack --kalman_format bot --detector_model_path weights/yolov8l_UAVDT_60epochs_20230509.pt`
184185

@@ -190,6 +191,8 @@ For example:
190191

191192
- UCMC Track: `python tracker/track.py --dataset mot17 --detector yolox --tracker ucmctrack --kalman_format ucmc --detector_model_path weights/bytetrack_m_mot17.pth.tar --camera_parameter_folder ./tracker/cam_param_files`
192193

194+
- Hybrid SORT: `python tracker/track.py --dataset mot17 --detector yolox --tracker hybridsort --kalman_format hybridsort --detector_model_path weights/bytetrack_m_mot17.pth.tar --save_images`
195+
193196
> **Important notes for UCMC Track:**
194197
>
195198
> 1. Camera parameters. The UCMC Track need the intrinsic and extrinsic parameter of camera. Please organize like the format of `tracker/cam_param_files/uavdt/M0101.txt`. One video sequence corresponds to one txt file. If you do not have the labelled parameters, you can refer to the estimating toolbox in original repo ([https://github.com/corfyi/UCMCTrack](https://github.com/corfyi/UCMCTrack)).

README_CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ git checkout v2 # change to v2 branch !!
1313

1414
🙌 ***如果您有任何关于添加跟踪器的建议***,请在Issues部分留言并附上论文标题或链接!欢迎大家一起来让这个repo变得更好
1515

16+
## 🗺️ 最近更新
17+
18+
- ***2024.10.24*** 增加了 Hybrid SORT 并且修复了OC-SORT的一些bug和错误。
1619

1720

1821
## ❤️ 介绍
@@ -34,6 +37,7 @@ git checkout v2 # change to v2 branch !!
3437
- Strong SORT ([IEEE TMM 2023](https://arxiv.org/pdf/2202.13514))
3538
- Sparse Track ([arxiv 2306](https://arxiv.org/pdf/2306.05238))
3639
- UCMC Track ([AAAI 2024](http://arxiv.org/abs/2312.08952))
40+
- Hybrid SORT([AAAI 2024](https://ojs.aaai.org/index.php/AAAI/article/view/28471))
3741

3842
REID模型支持:
3943

@@ -182,6 +186,8 @@ python tracker/track.py --dataset ${dataset name, related with the yaml file} --
182186

183187
- UCMC Track: `python tracker/track.py --dataset mot17 --detector yolox --tracker ucmctrack --kalman_format ucmc --detector_model_path weights/bytetrack_m_mot17.pth.tar --camera_parameter_folder ./tracker/cam_param_files`
184188

189+
- Hybrid SORT: `python tracker/track.py --dataset mot17 --detector yolox --tracker hybridsort --kalman_format hybridsort --detector_model_path weights/bytetrack_m_mot17.pth.tar --save_images`
190+
185191
>**UCMC Track的重要提示:**
186192
>
187193
> 1. 相机参数. UCMC Track需要相机的内参和外参. 请按照`tracker/cam_ram_files/uavdt/M0101.txt`的格式组织. 一个视频序列对应一个txt文件. 如果您没有标记的参数, 可以参考原始仓库中的估算工具箱([https://github.com/corfyi/UCMCTrack](https://github.com/corfyi/UCMCTrack)).

tracker/track.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from trackers.strongsort_tracker import StrongSortTracker
3030
from trackers.sparse_tracker import SparseTracker
3131
from trackers.ucmc_tracker import UCMCTracker
32+
from trackers.hybridsort_tracker import HybridSortTracker
3233

3334
# YOLOX modules
3435
try:
@@ -73,6 +74,7 @@
7374
'strongsort': StrongSortTracker,
7475
'sparsetrack': SparseTracker,
7576
'ucmctrack': UCMCTracker,
77+
'hybridsort': HybridSortTracker
7678
}
7779

7880
def get_args():

tracker/trackers/basetrack.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,7 @@ def tlwh(self):
5656
"""Get current position in bounding box format `(top left x, top left y,
5757
width, height)`.
5858
"""
59-
if self.mean is None:
60-
return self._tlwh.copy()
61-
ret = self.mean[:4].copy()
62-
ret[:2] -= ret[2:] / 2
63-
return ret
59+
raise NotImplementedError # implented in class Tracklet(BaseTrack)
6460

6561
@property
6662
def tlbr(self):
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
"""
2+
Hybrid Sort
3+
"""
4+
5+
import numpy as np
6+
from collections import deque
7+
from .basetrack import BaseTrack, TrackState
8+
from .tracklet import Tracklet, Tracklet_w_velocity_four_corner
9+
from .matching import *
10+
11+
from cython_bbox import bbox_overlaps as bbox_ious
12+
13+
class HybridSortTracker(object):
14+
def __init__(self, args, frame_rate=30):
15+
self.tracked_tracklets = [] # type: list[Tracklet]
16+
self.lost_tracklets = [] # type: list[Tracklet]
17+
self.removed_tracklets = [] # type: list[Tracklet]
18+
19+
self.frame_id = 0
20+
self.args = args
21+
22+
self.det_thresh = args.conf_thresh + 0.1
23+
self.buffer_size = int(frame_rate / 30.0 * args.track_buffer)
24+
self.max_time_lost = self.buffer_size
25+
26+
self.motion = args.kalman_format
27+
28+
self.delta_t = 3
29+
30+
@staticmethod
31+
def k_previous_obs(observations, cur_age, k):
32+
if len(observations) == 0:
33+
return [-1, -1, -1, -1, -1]
34+
for i in range(k):
35+
dt = k - i
36+
if cur_age - dt in observations:
37+
return observations[cur_age-dt]
38+
max_age = max(observations.keys())
39+
return observations[max_age]
40+
41+
def update(self, output_results, img, ori_img):
42+
"""
43+
output_results: processed detections (scale to original size) tlbr format
44+
"""
45+
46+
self.frame_id += 1
47+
activated_tracklets = []
48+
refind_tracklets = []
49+
lost_tracklets = []
50+
removed_tracklets = []
51+
52+
scores = output_results[:, 4]
53+
bboxes = output_results[:, :4]
54+
categories = output_results[:, -1]
55+
56+
remain_inds = scores > self.args.conf_thresh
57+
inds_low = scores > 0.1
58+
inds_high = scores < self.args.conf_thresh
59+
60+
inds_second = np.logical_and(inds_low, inds_high)
61+
dets_second = bboxes[inds_second]
62+
dets = bboxes[remain_inds]
63+
64+
cates = categories[remain_inds]
65+
cates_second = categories[inds_second]
66+
67+
scores_keep = scores[remain_inds]
68+
scores_second = scores[inds_second]
69+
70+
if len(dets) > 0:
71+
'''Detections'''
72+
detections = [Tracklet_w_velocity_four_corner(tlwh, s, cate, motion=self.motion) for
73+
(tlwh, s, cate) in zip(dets, scores_keep, cates)]
74+
else:
75+
detections = []
76+
77+
''' Add newly detected tracklets to tracked_tracklets'''
78+
unconfirmed = []
79+
tracked_tracklets = [] # type: list[Tracklet]
80+
for track in self.tracked_tracklets:
81+
if not track.is_activated:
82+
unconfirmed.append(track)
83+
else:
84+
tracked_tracklets.append(track)
85+
86+
''' Step 2: First association, Weak Cues (four corner confidence and score)'''
87+
tracklet_pool = joint_tracklets(tracked_tracklets, self.lost_tracklets)
88+
89+
velocities = np.array(
90+
[trk.get_velocity() for trk in tracklet_pool]) # (N, 4, 2)
91+
92+
# last observation, obervation-centric
93+
# last_boxes = np.array([trk.last_observation for trk in tracklet_pool])
94+
95+
# historical observations
96+
k_observations = np.array(
97+
[self.k_previous_obs(trk.observations, trk.age, self.delta_t) for trk in tracklet_pool])
98+
99+
100+
# Predict the current location with Kalman
101+
for tracklet in tracklet_pool:
102+
tracklet.predict()
103+
104+
# weak cues cost matrix (hmiou + four corner velocity) and assignment
105+
matches, u_track, u_detection = association_weak_cues(
106+
tracklets=tracklet_pool, detections=detections, velocities=velocities,
107+
previous_obs=k_observations, vdc_weight=0.05)
108+
109+
for itracked, idet in matches:
110+
track = tracklet_pool[itracked]
111+
det = detections[idet]
112+
if track.state == TrackState.Tracked:
113+
track.update(detections[idet], self.frame_id)
114+
activated_tracklets.append(track)
115+
else:
116+
track.re_activate(det, self.frame_id, new_id=False)
117+
refind_tracklets.append(track)
118+
119+
''' Step 3: Second association, with low score detection boxes'''
120+
# association the untrack to the low score detections
121+
if len(dets_second) > 0:
122+
'''Detections'''
123+
detections_second = [Tracklet_w_velocity_four_corner(tlwh, s, cate, motion=self.motion) for
124+
(tlwh, s, cate) in zip(dets_second, scores_second, cates_second)]
125+
else:
126+
detections_second = []
127+
r_tracked_tracklets = [tracklet_pool[i] for i in u_track if tracklet_pool[i].state == TrackState.Tracked]
128+
129+
dists = hm_iou_distance(r_tracked_tracklets, detections_second) - score_distance(r_tracked_tracklets, detections_second)
130+
131+
matches, u_track, u_detection_second = linear_assignment(dists, thresh=0.5)
132+
for itracked, idet in matches:
133+
track = r_tracked_tracklets[itracked]
134+
det = detections_second[idet]
135+
if track.state == TrackState.Tracked:
136+
track.update(det, self.frame_id)
137+
activated_tracklets.append(track)
138+
else:
139+
track.re_activate(det, self.frame_id, new_id=False)
140+
refind_tracklets.append(track)
141+
142+
143+
'''Step 4: Third association, match high-conf remain detections with last observation of tracks'''
144+
r_tracked_tracklets = [r_tracked_tracklets[i] for i in u_track] # remain tracklets from last step
145+
r_detections = [detections[i] for i in u_detection] # high-conf remain detections
146+
147+
dists = hm_iou_distance(atracks=[t.last_observation[: 4] for t in r_tracked_tracklets], # parse bbox directly
148+
btracks=[d.tlbr for d in r_detections])
149+
150+
matches, u_track, u_detection = linear_assignment(dists, thresh=0.5)
151+
152+
for itracked, idet in matches:
153+
track = r_tracked_tracklets[itracked]
154+
det = r_detections[idet]
155+
if track.state == TrackState.Tracked:
156+
track.update(det, self.frame_id)
157+
activated_tracklets.append(track)
158+
else:
159+
track.re_activate(det, self.frame_id, new_id=False)
160+
refind_tracklets.append(track)
161+
162+
# for tracks still failed, mark lost
163+
for it in u_track:
164+
track = r_tracked_tracklets[it]
165+
if not track.state == TrackState.Lost:
166+
track.mark_lost()
167+
lost_tracklets.append(track)
168+
169+
170+
'''Deal with unconfirmed tracks, usually tracks with only one beginning frame'''
171+
detections = [r_detections[i] for i in u_detection]
172+
dists = iou_distance(unconfirmed, detections)
173+
174+
matches, u_unconfirmed, u_detection = linear_assignment(dists, thresh=0.7)
175+
176+
for itracked, idet in matches:
177+
unconfirmed[itracked].update(detections[idet], self.frame_id)
178+
activated_tracklets.append(unconfirmed[itracked])
179+
for it in u_unconfirmed:
180+
track = unconfirmed[it]
181+
track.mark_removed()
182+
removed_tracklets.append(track)
183+
184+
""" Step 4: Init new tracklets"""
185+
for inew in u_detection:
186+
track = detections[inew]
187+
if track.score < self.det_thresh:
188+
continue
189+
track.activate(self.frame_id)
190+
activated_tracklets.append(track)
191+
192+
""" Step 5: Update state"""
193+
for track in self.lost_tracklets:
194+
if self.frame_id - track.end_frame > self.max_time_lost:
195+
track.mark_removed()
196+
removed_tracklets.append(track)
197+
198+
self.tracked_tracklets = [t for t in self.tracked_tracklets if t.state == TrackState.Tracked]
199+
self.tracked_tracklets = joint_tracklets(self.tracked_tracklets, activated_tracklets)
200+
self.tracked_tracklets = joint_tracklets(self.tracked_tracklets, refind_tracklets)
201+
self.lost_tracklets = sub_tracklets(self.lost_tracklets, self.tracked_tracklets)
202+
self.lost_tracklets.extend(lost_tracklets)
203+
self.lost_tracklets = sub_tracklets(self.lost_tracklets, self.removed_tracklets)
204+
self.removed_tracklets.extend(removed_tracklets)
205+
self.tracked_tracklets, self.lost_tracklets = remove_duplicate_tracklets(self.tracked_tracklets, self.lost_tracklets)
206+
# get scores of lost tracks
207+
output_tracklets = [track for track in self.tracked_tracklets if track.is_activated]
208+
209+
return output_tracklets
210+
211+
212+
213+
214+
def joint_tracklets(tlista, tlistb):
215+
exists = {}
216+
res = []
217+
for t in tlista:
218+
exists[t.track_id] = 1
219+
res.append(t)
220+
for t in tlistb:
221+
tid = t.track_id
222+
if not exists.get(tid, 0):
223+
exists[tid] = 1
224+
res.append(t)
225+
return res
226+
227+
228+
def sub_tracklets(tlista, tlistb):
229+
tracklets = {}
230+
for t in tlista:
231+
tracklets[t.track_id] = t
232+
for t in tlistb:
233+
tid = t.track_id
234+
if tracklets.get(tid, 0):
235+
del tracklets[tid]
236+
return list(tracklets.values())
237+
238+
239+
def remove_duplicate_tracklets(trackletsa, trackletsb):
240+
pdist = iou_distance(trackletsa, trackletsb)
241+
pairs = np.where(pdist < 0.15)
242+
dupa, dupb = list(), list()
243+
for p, q in zip(*pairs):
244+
timep = trackletsa[p].frame_id - trackletsa[p].start_frame
245+
timeq = trackletsb[q].frame_id - trackletsb[q].start_frame
246+
if timep > timeq:
247+
dupb.append(q)
248+
else:
249+
dupa.append(p)
250+
resa = [t for i, t in enumerate(trackletsa) if not i in dupa]
251+
resb = [t for i, t in enumerate(trackletsb) if not i in dupb]
252+
return resa, resb

0 commit comments

Comments
 (0)