Skip to content

Commit bfcd24d

Browse files
authored
Merge pull request #1 from bochinski/master
Pulled changes, V-IOU
2 parents fe53ac5 + 9929e44 commit bfcd24d

File tree

8 files changed

+787
-83
lines changed

8 files changed

+787
-83
lines changed

README.md

Lines changed: 183 additions & 55 deletions
Large diffs are not rendered by default.

cvpr19.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
3+
# set these variables according to your setup
4+
seq_dir=/path/to/cvpr19/train # base directory of the split (cvpr19/train, cvpr19/test etc.)
5+
results_dir=results/cvpr19 # output directory, will be created if not existing
6+
7+
8+
mkdir -p ${results_dir}
9+
10+
options="-v KCF2 -sl 0.3 -sh 0.8 -si 0.4 -tm 5 --ttl 20 -hr 0.3 -fmt motchallenge"
11+
for seq in $(ls $seq_dir); do
12+
echo $seq
13+
python demo.py -f ${seq_dir}/${seq}/img1/{:06d}.jpg -d ${seq_dir}/${seq}/det/det.txt \
14+
-o ${results_dir}/${seq}.txt ${options}
15+
done

demo.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,53 @@
77
# Written by Erik Bochinski
88
# ---------------------------------------------------------
99

10-
from time import time
1110
import argparse
1211

1312
from iou_tracker import track_iou
13+
from viou_tracker import track_viou
1414
from util import load_mot, save_to_csv
1515

1616

1717
def main(args):
18-
detections = load_mot(args.detection_path)
18+
formats = ['motchallenge', 'visdrone']
19+
assert args.format in formats, "format '{}' unknown supported formats are: {}".format(args.format, formats)
1920

20-
start = time()
21-
tracks = track_iou(detections, args.sigma_l, args.sigma_h, args.sigma_iou, args.t_min)
22-
end = time()
21+
with_classes = False
22+
if args.format == 'visdrone':
23+
with_classes = True
24+
detections = load_mot(args.detection_path, nms_overlap_thresh=args.nms, with_classes=with_classes)
2325

24-
num_frames = len(detections)
25-
print("finished at " + str(int(num_frames / (end - start))) + " fps!")
26+
if args.visual:
27+
tracks = track_viou(args.frames_path, detections, args.sigma_l, args.sigma_h, args.sigma_iou, args.t_min,
28+
args.ttl, args.visual, args.keep_upper_height_ratio)
29+
else:
30+
if with_classes:
31+
# track_viou can also be used without visual tracking, but note that the speed will be much slower compared
32+
# to track_iou. However, this way supports the optimal LAP solving and the handling of multiple object classes:
33+
tracks = track_viou(args.frames_path, detections, args.sigma_l, args.sigma_h, args.sigma_iou, args.t_min,
34+
args.ttl, 'NONE', args.keep_upper_height_ratio)
35+
else:
36+
tracks = track_iou(detections, args.sigma_l, args.sigma_h, args.sigma_iou, args.t_min)
2637

27-
save_to_csv(args.output_path, tracks)
38+
save_to_csv(args.output_path, tracks, fmt=args.format)
2839

2940

3041
if __name__ == '__main__':
31-
32-
parser = argparse.ArgumentParser(description="IOU Tracker demo script")
42+
parser = argparse.ArgumentParser(description="IOU/V-IOU Tracker demo script")
43+
parser.add_argument('-v', '--visual', type=str, help="visual tracker for V-IOU. Currently supported are "
44+
"[BOOSTING, MIL, KCF, KCF2, TLD, MEDIANFLOW, GOTURN, NONE] "
45+
"see README.md for furthert details")
46+
parser.add_argument('-hr', '--keep_upper_height_ratio', type=float, default=1.0,
47+
help="Ratio of height of the object to track to the total height of the object "
48+
"for visual tracking. e.g. upper 30%%")
49+
parser.add_argument('-f', '--frames_path', type=str,
50+
help="sequence frames with format '/path/to/frames/frame_{:04d}.jpg' where '{:04d}' will "
51+
"be replaced with the frame id. (zero_padded to 4 digits, use {:05d} for 5 etc.)")
3352
parser.add_argument('-d', '--detection_path', type=str, required=True,
3453
help="full path to CSV file containing the detections")
3554
parser.add_argument('-o', '--output_path', type=str, required=True,
36-
help="output path to store the tracking results (MOT challenge devkit compatible format)")
55+
help="output path to store the tracking results "
56+
"(MOT challenge/Visdrone devkit compatible format)")
3757
parser.add_argument('-sl', '--sigma_l', type=float, default=0,
3858
help="low detection threshold")
3959
parser.add_argument('-sh', '--sigma_h', type=float, default=0.5,
@@ -42,6 +62,17 @@ def main(args):
4262
help="intersection-over-union threshold")
4363
parser.add_argument('-tm', '--t_min', type=float, default=2,
4464
help="minimum track length")
65+
parser.add_argument('-ttl', '--ttl', type=int, default=1,
66+
help="time to live parameter for v-iou")
67+
parser.add_argument('-nms', '--nms', type=float, default=None,
68+
help="nms for loading multi-class detections")
69+
parser.add_argument('-fmt', '--format', type=str, default='motchallenge',
70+
help='format of the detections [motchallenge, visdrone]')
4571

4672
args = parser.parse_args()
73+
assert not args.visual or args.visual and args.frames_path, "visual tracking requires video frames, " \
74+
"please specify via --frames_path"
75+
76+
assert 0.0 < args.keep_upper_height_ratio <= 1.0, "only values between 0 and 1 are allowed"
77+
assert args.nms is None or 0.0 <= args.nms <= 1.0, "only values between 0 and 1 are allowed"
4778
main(args)

run_tracker.m

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
function [stateInfo, speed] = run_tracker(curSequence, baselinedetections)
22
%% tracker configuration
3+
ttl = 0;
4+
tracker_type = '';
35

4-
%% Mask R-CNN (frcnn)
6+
%% v-iou tracker configurations
7+
% %% Mask R-CNN (frcnn)
58
sigma_l = 0;
6-
sigma_h = 0.95;
9+
sigma_h = 0.98;
710
sigma_iou = 0.6;
8-
t_min = 7;
11+
t_min = 13;
12+
ttl=6;
13+
tracker_type='KCF2';
14+
15+
% %% CompACT
16+
%sigma_l = 0;
17+
%sigma_h = 0.3;
18+
%sigma_iou = 0.5;
19+
%t_min = 3;
20+
%ttl=12;
21+
%tracker_type='KCF2';
22+
23+
%% iou tracker configurations
24+
% %% Mask R-CNN (frcnn)
25+
%sigma_l = 0;
26+
%sigma_h = 0.95;
27+
%sigma_iou = 0.6;
28+
%t_min = 7;
929

1030
% %% R-CNN
1131
% sigma_l = 0;
@@ -33,8 +53,11 @@
3353

3454
%% running tracking algorithm
3555
try
36-
ret = py.iou_tracker.track_iou_matlab_wrapper(py.numpy.array(baselinedetections(:).'), sigma_l, sigma_h, sigma_iou, t_min);
37-
56+
if strcmp(tracker_type, '')
57+
ret = py.iou_tracker.track_iou_matlab_wrapper(py.numpy.array(baselinedetections(:).'), sigma_l, sigma_h, sigma_iou, t_min);
58+
else
59+
ret = py.viou_tracker.track_viou_matlab_wrapper(curSequence.imgFolder, py.numpy.array(baselinedetections(:).'), sigma_l, sigma_h, sigma_iou, t_min, ttl, tracker_type);
60+
end
3861
catch exception
3962
disp('error while calling the python tracking module: ')
4063
disp(' ')

util.py

Lines changed: 147 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,33 @@
99
import csv
1010

1111

12-
def load_mot(detections):
12+
visdrone_classes = {'car': 4, 'bus': 9, 'truck': 6, 'pedestrian': 1, 'van': 5}
13+
14+
15+
def load_mot(detections, nms_overlap_thresh=None, with_classes=True, nms_per_class=False):
1316
"""
1417
Loads detections stored in a mot-challenge like formatted CSV or numpy array (fieldNames = ['frame', 'id', 'x', 'y',
1518
'w', 'h', 'score']).
1619
1720
Args:
18-
detections
21+
detections (str, numpy.ndarray): path to csv file containing the detections or numpy array containing them.
22+
nms_overlap_thresh (float, optional): perform non-maximum suppression on the input detections with this thrshold.
23+
no nms is performed if this parameter is not specified.
24+
with_classes (bool, optional): indicates if the detections have classes or not. set to false for motchallange.
25+
nms_per_class (bool, optional): perform non-maximum suppression for each class separately
1926
2027
Returns:
2128
list: list containing the detections for each frame.
2229
"""
30+
if nms_overlap_thresh:
31+
assert with_classes, "currently only works with classes available"
2332

2433
data = []
2534
if type(detections) is str:
2635
raw = np.genfromtxt(detections, delimiter=',', dtype=np.float32)
36+
if np.isnan(raw).all():
37+
raw = np.genfromtxt(detections, delimiter=' ', dtype=np.float32)
38+
2739
else:
2840
# assume it is an array
2941
assert isinstance(detections, np.ndarray), "only numpy arrays or *.csv paths are supported as detections."
@@ -34,16 +46,127 @@ def load_mot(detections):
3446
idx = raw[:, 0] == i
3547
bbox = raw[idx, 2:6]
3648
bbox[:, 2:4] += bbox[:, 0:2] # x1, y1, w, h -> x1, y1, x2, y2
49+
bbox -= 1 # correct 1,1 matlab offset
3750
scores = raw[idx, 6]
51+
52+
if with_classes:
53+
classes = raw[idx, 7]
54+
55+
bbox_filtered = None
56+
scores_filtered = None
57+
classes_filtered = None
58+
for coi in visdrone_classes:
59+
cids = classes==visdrone_classes[coi]
60+
if nms_per_class and nms_overlap_thresh:
61+
bbox_tmp, scores_tmp = nms(bbox[cids], scores[cids], nms_overlap_thresh)
62+
else:
63+
bbox_tmp, scores_tmp = bbox[cids], scores[cids]
64+
65+
if bbox_filtered is None:
66+
bbox_filtered = bbox_tmp
67+
scores_filtered = scores_tmp
68+
classes_filtered = [coi]*bbox_filtered.shape[0]
69+
elif len(bbox_tmp) > 0:
70+
bbox_filtered = np.vstack((bbox_filtered, bbox_tmp))
71+
scores_filtered = np.hstack((scores_filtered, scores_tmp))
72+
classes_filtered += [coi] * bbox_tmp.shape[0]
73+
74+
if bbox_filtered is not None:
75+
bbox = bbox_filtered
76+
scores = scores_filtered
77+
classes = classes_filtered
78+
79+
if nms_per_class is False and nms_overlap_thresh:
80+
bbox, scores, classes = nms(bbox, scores, nms_overlap_thresh, np.array(classes))
81+
82+
else:
83+
classes = ['pedestrian']*bbox.shape[0]
84+
3885
dets = []
39-
for bb, s in zip(bbox, scores):
40-
dets.append({'bbox': (bb[0], bb[1], bb[2], bb[3]), 'score': s})
86+
for bb, s, c in zip(bbox, scores, classes):
87+
dets.append({'bbox': (bb[0], bb[1], bb[2], bb[3]), 'score': s, 'class': c})
4188
data.append(dets)
4289

4390
return data
4491

4592

46-
def save_to_csv(out_path, tracks):
93+
def nms(boxes, scores, overlapThresh, classes=None):
94+
"""
95+
perform non-maximum suppression. based on Malisiewicz et al.
96+
Args:
97+
boxes (numpy.ndarray): boxes to process
98+
scores (numpy.ndarray): corresponding scores for each box
99+
overlapThresh (float): overlap threshold for boxes to merge
100+
classes (numpy.ndarray, optional): class ids for each box.
101+
102+
Returns:
103+
(tuple): tuple containing:
104+
105+
boxes (list): nms boxes
106+
scores (list): nms scores
107+
classes (list, optional): nms classes if specified
108+
"""
109+
# # if there are no boxes, return an empty list
110+
# if len(boxes) == 0:
111+
# return [], [], [] if classes else [], []
112+
113+
# if the bounding boxes integers, convert them to floats --
114+
# this is important since we'll be doing a bunch of divisions
115+
if boxes.dtype.kind == "i":
116+
boxes = boxes.astype("float")
117+
118+
if scores.dtype.kind == "i":
119+
scores = scores.astype("float")
120+
121+
# initialize the list of picked indexes
122+
pick = []
123+
124+
# grab the coordinates of the bounding boxes
125+
x1 = boxes[:, 0]
126+
y1 = boxes[:, 1]
127+
x2 = boxes[:, 2]
128+
y2 = boxes[:, 3]
129+
#score = boxes[:, 4]
130+
# compute the area of the bounding boxes and sort the bounding
131+
# boxes by the bottom-right y-coordinate of the bounding box
132+
area = (x2 - x1 + 1) * (y2 - y1 + 1)
133+
idxs = np.argsort(scores)
134+
135+
# keep looping while some indexes still remain in the indexes
136+
# list
137+
while len(idxs) > 0:
138+
# grab the last index in the indexes list and add the
139+
# index value to the list of picked indexes
140+
last = len(idxs) - 1
141+
i = idxs[last]
142+
pick.append(i)
143+
144+
# find the largest (x, y) coordinates for the start of
145+
# the bounding box and the smallest (x, y) coordinates
146+
# for the end of the bounding box
147+
xx1 = np.maximum(x1[i], x1[idxs[:last]])
148+
yy1 = np.maximum(y1[i], y1[idxs[:last]])
149+
xx2 = np.minimum(x2[i], x2[idxs[:last]])
150+
yy2 = np.minimum(y2[i], y2[idxs[:last]])
151+
152+
# compute the width and height of the bounding box
153+
w = np.maximum(0, xx2 - xx1 + 1)
154+
h = np.maximum(0, yy2 - yy1 + 1)
155+
156+
# compute the ratio of overlap
157+
overlap = (w * h) / area[idxs[:last]]
158+
159+
# delete all indexes from the index list that have
160+
idxs = np.delete(idxs, np.concatenate(([last],
161+
np.where(overlap > overlapThresh)[0])))
162+
163+
if classes is not None:
164+
return boxes[pick], scores[pick], classes[pick]
165+
else:
166+
return boxes[pick], scores[pick]
167+
168+
169+
def save_to_csv(out_path, tracks, fmt='motchallenge'):
47170
"""
48171
Saves tracks to a CSV file.
49172
@@ -53,22 +176,34 @@ def save_to_csv(out_path, tracks):
53176
"""
54177

55178
with open(out_path, "w") as ofile:
56-
field_names = ['frame', 'id', 'x', 'y', 'w', 'h', 'score', 'wx', 'wy', 'wz']
179+
if fmt == 'motchallenge':
180+
field_names = ['frame', 'id', 'x', 'y', 'w', 'h', 'score', 'wx', 'wy', 'wz']
181+
elif fmt == 'visdrone':
182+
field_names = ['frame', 'id', 'x', 'y', 'w', 'h', 'score', 'object_category', 'truncation', 'occlusion']
183+
else:
184+
raise ValueError("unknown format type '{}'".format(fmt))
57185

58186
odict = csv.DictWriter(ofile, field_names)
59187
id_ = 1
60188
for track in tracks:
61189
for i, bbox in enumerate(track['bboxes']):
62190
row = {'id': id_,
63191
'frame': track['start_frame'] + i,
64-
'x': bbox[0],
65-
'y': bbox[1],
192+
'x': bbox[0]+1,
193+
'y': bbox[1]+1,
66194
'w': bbox[2] - bbox[0],
67195
'h': bbox[3] - bbox[1],
68-
'score': track['max_score'],
69-
'wx': -1,
70-
'wy': -1,
71-
'wz': -1}
196+
'score': track['max_score']}
197+
if fmt == 'motchallenge':
198+
row['wx'] = -1
199+
row['wy'] = -1
200+
row['wz'] = -1
201+
elif fmt == 'visdrone':
202+
row['object_category'] = visdrone_classes[track['class']]
203+
row['truncation'] = -1
204+
row['occlusion'] = -1
205+
else:
206+
raise ValueError("unknown format type '{}'".format(fmt))
72207

73208
odict.writerow(row)
74209
id_ += 1

0 commit comments

Comments
 (0)