Skip to content

Commit 259bf29

Browse files
authored
Add files via upload
1 parent e39fbb7 commit 259bf29

12 files changed

+1203
-0
lines changed

4. mtmc/config.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Tracking
2+
# iou_thr 0.25 is better than 0
3+
dist_thr = 0.837
4+
min_len = 5
5+
6+
# Path
7+
data_path = '../../dataset/AIC21_Track3/test/S06/'
8+
weight_path = '../2. feat_ext/outputs/resnext_8/resnext_17.t7'
9+
img_w = 320
10+
img_h = 320
11+
pad_color = (0, 0, 0)
12+
num_ide_class = 184

4. mtmc/mtmc.py

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import copy
2+
import pickle
3+
import numpy as np
4+
from utils import utils
5+
import scipy.optimize as opt
6+
7+
# Read mtsc results
8+
result_path = '../outputs/2. mtsc/mask_rcnn_0.2/det_small_mtsc_v2_res8_fut3_del3_post123'
9+
save_path = '../outputs/3. mtmc/mask_rcnn_0.2/det_small_mtsc_v2_res8_fut3_del3_post123_final'
10+
# result_path = '../outputs/2. mtsc/fairmot_affine_hsv_0.3/det_mtsc_v2_res8_fut3_del3_post123'
11+
# save_path = '../outputs/3. mtmc/fairmot_affine_hsv_0.3/det_mtsc_v2_res8_fut3_del3_post123'
12+
with open(result_path + '.pickle', 'rb') as f:
13+
mtsc_results = pickle.load(f)
14+
15+
16+
# Measure distance between trajectory
17+
def measure_distance(a_track, b_track):
18+
# Rearrange trajectories
19+
a_track_per_cam_no_str = {}
20+
for box in a_track:
21+
if type(box) is not str:
22+
if box[1] not in a_track_per_cam_no_str.keys():
23+
a_track_per_cam_no_str[box[1]] = []
24+
a_track_per_cam_no_str[box[1]].append(box)
25+
b_track_no_str = [box for box in b_track if type(box) is not str]
26+
27+
dist = []
28+
for cam in a_track_per_cam_no_str.keys():
29+
# Find best object scores
30+
a_track_obj_scores = [box[14] for box in a_track_per_cam_no_str[cam]]
31+
a_track_best_obj_scores = sorted(a_track_obj_scores)[-max(-5, round(len(a_track_obj_scores)*0.2)):]
32+
b_track_obj_scores = [box[14] for box in b_track_no_str]
33+
b_track_best_obj_scores = sorted(b_track_obj_scores)[-max(-5, round(len(b_track_obj_scores)*0.2)):]
34+
35+
for a_track_best_obj_score in a_track_best_obj_scores:
36+
# Get box and feature
37+
a_feat = a_track_per_cam_no_str[cam][a_track_obj_scores.index(a_track_best_obj_score)][15]
38+
for b_track_best_obj_score in b_track_best_obj_scores:
39+
# Get box and feature, Measure distance
40+
b_feat = b_track_no_str[b_track_obj_scores.index(b_track_best_obj_score)][15]
41+
dist.append(np.sqrt(np.sum((a_feat - b_feat) ** 2)))
42+
43+
return np.min(dist)
44+
45+
46+
# Generate pairwise distance matrix
47+
def gen_dist_mat(a_mtmc, b_mtsc):
48+
# Create empty matrix
49+
con_mat = np.zeros((len(a_mtmc), len(b_mtsc)))
50+
dist_mat = np.ones((len(a_mtmc), len(b_mtsc))) * 1000
51+
52+
# Overlap camera pairs (There are no overlapped cameras.)
53+
overlap_cam_pairs = []
54+
55+
a_1 = []
56+
for a_track in a_mtmc:
57+
if a_track[-1] == 'to_next_cam':
58+
a_1.append(copy.deepcopy(a_track))
59+
a_1_diff = []
60+
a_1 = sorted(a_1, key=lambda track: track[-2][2])
61+
for i in range(len(a_1) - 1):
62+
a_1_diff = a_1[i+1][-2][2] - a_1[i][-2][2]
63+
a_1_max_diff = np.max(a_1_diff) * 1.5
64+
65+
b_1 = []
66+
for b_track in b_mtsc:
67+
if b_track[-1] == 'to_previous_cam':
68+
b_1.append(copy.deepcopy(b_track))
69+
b_1_diff = []
70+
b_1 = sorted(b_1, key=lambda track: track[-2][2])
71+
for i in range(len(b_1) - 1):
72+
b_1_diff = b_1[i+1][-2][2] - b_1[i][-2][2]
73+
b_1_max_diff = np.max(b_1_diff) * 1.5
74+
75+
# Post process the distance matrix with the prior constraints
76+
for idx, a_track in enumerate(a_mtmc):
77+
# Get minimum frame number and maximum frame number
78+
a_f_min = np.min([box[2] for box in a_track if type(box) is not str])
79+
a_f_max = np.max([box[2] for box in a_track if type(box) is not str])
80+
81+
for jdx, b_track in enumerate(b_mtsc):
82+
# Get minimum frame number and maximum frame number
83+
b_f_min = np.min([box[2] for box in b_track if type(box) is not str])
84+
b_f_max = np.max([box[2] for box in b_track if type(box) is not str])
85+
86+
# Disconnect if connection not available
87+
if a_track[-1] == 'to_next_cam' and b_track[0] == 'from_previous_cam':
88+
min_f_num_diff = utils.get_min_f_num_diff(a_track, b_track, 1)
89+
if a_f_max + min_f_num_diff < b_f_min < a_f_max + min_f_num_diff + a_1_max_diff:
90+
dist_mat[idx, jdx] = measure_distance(a_track, b_track)
91+
con_mat[idx, jdx] = 1
92+
elif a_track[0] == 'from_next_cam' and b_track[-1] == 'to_previous_cam':
93+
min_f_num_diff = utils.get_min_f_num_diff(a_track, b_track, -1)
94+
if b_f_max + min_f_num_diff + b_1_max_diff > a_f_min > b_f_max + min_f_num_diff:
95+
dist_mat[idx, jdx] = measure_distance(a_track, b_track)
96+
con_mat[idx, jdx] = -1
97+
98+
# Post process dist mat
99+
for idx in range(dist_mat.shape[0]):
100+
for jdx in range(dist_mat.shape[1]):
101+
dist_mat[idx, jdx] = dist_mat[idx, jdx] if dist_mat[idx, jdx] <= 1.175 else 1000
102+
103+
return dist_mat, con_mat
104+
105+
106+
def hungarian():
107+
# Set merge order
108+
print('Start MTMC Hungarian\n')
109+
merge_order = ['c041', 'c042', 'c043', 'c044', 'c045', 'c046']
110+
111+
# Start mtmc
112+
a_mtmc, result = copy.deepcopy(mtsc_results['S06'][merge_order[0]]), []
113+
for c_idx in range(1, len(merge_order)):
114+
# Get current mtsc results
115+
print('S06_%s starts' % merge_order[c_idx])
116+
b_mtsc = copy.deepcopy(mtsc_results['S06'][merge_order[c_idx]])
117+
118+
# Generate distance matrix between trajectories
119+
print('Distance matrix pair: %d x %d' % (len(a_mtmc), len(b_mtsc)))
120+
dist_mat, con_mat = gen_dist_mat(a_mtmc, b_mtsc)
121+
print('Num connections: %d / %d\n' % (np.sum(con_mat != 0), len(a_mtmc) * len(b_mtsc)))
122+
123+
# Hungarian algorithm
124+
row_ind, col_ind = opt.linear_sum_assignment(dist_mat)
125+
row_ind, col_ind = list(row_ind), list(col_ind)
126+
127+
# Check distance between connections
128+
con_row_ind, con_col_ind = [], []
129+
for r_idx in range(len(row_ind)):
130+
if dist_mat[row_ind[r_idx], col_ind[r_idx]] < 1000:
131+
# Merge trajectories 'a' and 'b'
132+
if con_mat[row_ind[r_idx], col_ind[r_idx]] == 1:
133+
a_mtmc[row_ind[r_idx]] = copy.deepcopy(a_mtmc[row_ind[r_idx]]) \
134+
+ copy.deepcopy(b_mtsc[col_ind[r_idx]])
135+
elif con_mat[row_ind[r_idx], col_ind[r_idx]] == -1:
136+
a_mtmc[row_ind[r_idx]] = copy.deepcopy(b_mtsc[col_ind[r_idx]])\
137+
+ copy.deepcopy(a_mtmc[row_ind[r_idx]])
138+
139+
# Record
140+
con_row_ind.append(row_ind[r_idx])
141+
con_col_ind.append(col_ind[r_idx])
142+
143+
# Finish trajectories
144+
fin_idx = [r for r in range(len(a_mtmc)) if r not in con_row_ind]
145+
for idx, f_idx in enumerate(fin_idx):
146+
result.append(copy.deepcopy(a_mtmc.pop(f_idx - idx)))
147+
148+
# Starting trajectories
149+
for c in range(len(b_mtsc)):
150+
if c not in con_col_ind:
151+
a_mtmc.append(copy.deepcopy(b_mtsc[c]))
152+
153+
# Final merge
154+
result += copy.deepcopy(a_mtmc)
155+
156+
# # Post process (Do not post process Recall become too low)
157+
# result_post = []
158+
# for track in result:
159+
# cams = list(set([box[1] for box in track if type(box) is not str]))
160+
# if 2 <= len(cams):
161+
# result_post.append(track)
162+
163+
return result
164+
165+
166+
def map_obj_id(result):
167+
result_new_id = copy.deepcopy(result)
168+
for t_idx, track in enumerate(result):
169+
for b_idx, box in enumerate(track):
170+
if type(box) is not str:
171+
result_new_id[t_idx][b_idx][3] = t_idx
172+
print('Num ID: %d' % len(result_new_id))
173+
174+
return result_new_id
175+
176+
177+
def write_txt(result):
178+
# Open txt file, Write txt file, Close
179+
num_box = 0
180+
mtmc_txt = open(save_path + '.txt', 'w')
181+
for track in result:
182+
for box in track:
183+
if type(box) is not str:
184+
if 0.1 <= box[14]:
185+
# Decode
186+
left, top, w, h, img_w, img_h = box[4], box[5], box[6], box[7], box[8], box[9]
187+
188+
# Expand
189+
new_w, new_h = w * 1.2, h * 1.2
190+
# new_w, new_h = w, h
191+
192+
# Calculate new left and top
193+
c_x, c_y = left + w / 2, top + h / 2
194+
new_left, new_top = c_x - new_w / 2, c_y - new_h / 2
195+
new_right, new_bot = new_left + new_w, new_top + new_h
196+
197+
# Threshold by image size
198+
new_left, new_top = max(0, new_left), max(0, new_top)
199+
new_right, new_bot = min(img_w, new_right), min(img_h, new_bot)
200+
new_w, new_h = new_right - new_left, new_bot - new_top
201+
202+
# Write
203+
mtmc_txt.write('%d %d %d %d %d %d %d %d %d\n'
204+
% (int(box[1][1:]), box[3], box[2], new_left, new_top, new_w, new_h, 0, 0))
205+
num_box += 1
206+
mtmc_txt.close()
207+
print('Num Box: %d' % num_box)
208+
209+
210+
if __name__ == "__main__":
211+
result = hungarian()
212+
result = map_obj_id(result)
213+
write_txt(result)

4. mtmc/nets/estimator.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import config
2+
from nets.resnext import *
3+
4+
5+
class Estimator(nn.Module):
6+
def __init__(self):
7+
super(Estimator, self).__init__()
8+
self.act = nn.ReLU(inplace=True)
9+
10+
# Construct backbone network (ResNext-50), Global average pooling, Dropout
11+
self.ext = resnext50_32x4d()
12+
self.avg_pool = nn.AdaptiveAvgPool2d(1)
13+
self.drop = nn.Dropout(0.5)
14+
15+
# BNNeck
16+
self.bnn = nn.BatchNorm2d(2048)
17+
nn.init.constant_(self.bnn.weight, 1)
18+
nn.init.constant_(self.bnn.bias, 0)
19+
self.bnn.bias.requires_grad_(False)
20+
21+
# IDE
22+
self.fc_ide = nn.Linear(2048, config.num_ide_class, bias=False)
23+
24+
def forward(self, patch):
25+
# Extract appearance feature
26+
feat_tri = self.avg_pool(self.ext(patch))
27+
28+
# BNNeck
29+
feat_infer = self.bnn(self.drop(feat_tri))
30+
31+
# IDE
32+
feat_ide = feat_infer.view(feat_infer.size(0), -1)
33+
ide = self.fc_ide(feat_ide)
34+
35+
return feat_tri, feat_infer, ide

4. mtmc/nets/resnext.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import torch.nn as nn
2+
from torch.hub import load_state_dict_from_url
3+
4+
5+
def conv1x1(in_planes, out_planes, stride=1):
6+
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
7+
8+
9+
def conv3x3(in_planes, out_planes, stride=1, groups=1):
10+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, groups=groups, bias=False)
11+
12+
13+
class Bottleneck(nn.Module):
14+
expansion = 4
15+
16+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, base_width=64):
17+
super(Bottleneck, self).__init__()
18+
19+
# Set parameters
20+
self.stride = stride
21+
width = int(planes * (base_width / 64.)) * groups
22+
23+
# Convolutions
24+
self.conv1 = conv1x1(inplanes, width)
25+
self.bn1 = nn.BatchNorm2d(width)
26+
self.conv2 = conv3x3(width, width, stride, groups)
27+
self.bn2 = nn.BatchNorm2d(width)
28+
self.conv3 = conv1x1(width, planes * self.expansion)
29+
self.bn3 = nn.BatchNorm2d(planes * self.expansion)
30+
self.relu = nn.ReLU(inplace=True)
31+
32+
# Others
33+
self.downsample = downsample
34+
35+
def forward(self, x):
36+
# Convolutions
37+
out = self.conv1(x)
38+
out = self.bn1(out)
39+
out = self.relu(out)
40+
out = self.conv2(out)
41+
out = self.bn2(out)
42+
out = self.relu(out)
43+
out = self.conv3(out)
44+
out = self.bn3(out)
45+
46+
# Skip connection, Final activation
47+
identity = self.downsample(x) if self.downsample is not None else x
48+
out = self.relu(out + identity)
49+
50+
return out
51+
52+
53+
class ResNet(nn.Module):
54+
def __init__(self, block, layers, groups=1, width_per_group=64):
55+
super(ResNet, self).__init__()
56+
57+
self.dilation = 1
58+
self.inplanes = 64
59+
60+
self.groups = groups
61+
self.base_width = width_per_group
62+
self.relu = nn.ReLU(inplace=True)
63+
64+
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
65+
self.bn1 = nn.BatchNorm2d(self.inplanes)
66+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
67+
68+
self.layer1 = self._make_layer(block, 64, layers[0])
69+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
70+
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
71+
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
72+
73+
for m in self.modules():
74+
if isinstance(m, nn.Conv2d):
75+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
76+
elif isinstance(m, nn.BatchNorm2d):
77+
nn.init.constant_(m.weight, 1)
78+
nn.init.constant_(m.bias, 0)
79+
80+
# Zero-initialize the last BN in each residual branch,
81+
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
82+
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
83+
for m in self.modules():
84+
if isinstance(m, Bottleneck):
85+
nn.init.constant_(m.bn3.weight, 0)
86+
87+
def _make_layer(self, block, planes, blocks, stride=1):
88+
downsample = None
89+
90+
if stride != 1 or self.inplanes != planes * block.expansion:
91+
downsample = nn.Sequential(
92+
conv1x1(self.inplanes, planes * block.expansion, stride),
93+
nn.BatchNorm2d(planes * block.expansion),
94+
)
95+
96+
# First layer
97+
layers = [block(self.inplanes, planes, stride, downsample, self.groups, self.base_width)]
98+
99+
# Other layers
100+
self.inplanes = planes * block.expansion
101+
for _ in range(1, blocks):
102+
layers.append(block(self.inplanes, planes, groups=self.groups, base_width=self.base_width))
103+
104+
return nn.Sequential(*layers)
105+
106+
def forward(self, x):
107+
x = self.maxpool(self.relu(self.bn1(self.conv1(x))))
108+
x = self.layer1(x)
109+
x = self.layer2(x)
110+
x = self.layer3(x)
111+
x = self.layer4(x)
112+
113+
return x
114+
115+
116+
def resnext50_32x4d(**kwargs):
117+
kwargs['groups'] = 32
118+
kwargs['width_per_group'] = 4
119+
120+
# Model
121+
model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
122+
123+
# Get state dictionaries
124+
pretrained = load_state_dict_from_url('https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth')
125+
pretrained.pop('fc.weight')
126+
pretrained.pop('fc.bias')
127+
128+
# Update and load
129+
model_state_dict = model.state_dict()
130+
model_state_dict.update(pretrained)
131+
model.load_state_dict(model_state_dict)
132+
133+
return model

0 commit comments

Comments
 (0)