F. Yu, W. Li, Q. Li, Y. Liu, X. Shi, J. Yan. POI: Multiple Object Tracking with High Performance Detection and Appearance Feature. In BMTT, SenseTime Group Limited, 2016.
github:https://github.com/nwojke/deep_sort
按视频帧顺序处理,每一帧的处理流程如下:
1.读取当前帧目标检测框的位置及各检测框图像块的深度特征(此处在处理实际使用时需要自己来提取);
2.根据置信度对检测框进行过滤,即对置信度不足够高的检测框及特征予以删除;
3.对检测框进行非最大值抑制,消除一个目标身上多个框的情况;
4.预测:使用kalman滤波预测目标在当前帧的位置
5.更新:更新kalman追踪器参数及特征集,另外进行目标消失、新目标出现的判断
kalman滤波公式1和2:
x(k)=Ax(k−1)
p(k)=Ap(k−1)AT+Q,
其中,x(k−1) 为目标的状态信息(代码中的mean),为上一帧中目标的信息[center x,center y,aspect ration,height,0,0,0,0];p(k−1)为目标的估计误差(代码中的covariance);A为状态转移矩阵;Q为系统误差;
#mean
mean_pos = measurement
mean_vel = np.zeros_like(mean_pos)
mean = np.r_[mean_pos, mean_vel]
#covariance
self._std_weight_position = 1. / 20
self._std_weight_velocity = 1. / 160 (可调参数)
std = [
2 * self._std_weight_position * measurement[3],
2 * self._std_weight_position * measurement[3],
1e-2,
2 * self._std_weight_position * measurement[3],
10 * self._std_weight_velocity * measurement[3],
10 * self._std_weight_velocity * measurement[3],
1e-5,
10 * self._std_weight_velocity * measurement[3]]
covariance = np.diag(np.square(std))
#矩阵A
ndim, dt = 4, 1.
self._motion_mat = np.eye(2 * ndim, 2 * ndim)
for i in range(ndim):
self._motion_mat[i, ndim + i] = dt
std_pos = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-2,
self._std_weight_position * mean[3]]
std_vel = [
self._std_weight_velocity * mean[3],
self._std_weight_velocity * mean[3],
1e-5,
self._std_weight_velocity * mean[3]]
#矩阵Q
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))
#kalman滤波公式1
mean = np.dot(self._motion_mat, mean)
#kalman滤波公式2
covariance = np.linalg.multi_dot((
self._motion_mat, covariance, self._motion_mat.T)) + motion_cov
预测完之后,对每一个追踪器设置self.time_since_update += 1
一、 检测结果与追踪预测结果的匹配
1.区分已确认状态的追踪器和未确认状态的追踪器;
2. 已确认状态的追踪器进行级联匹配
2.1对同一消失时间的多个追踪器,计算当前帧新检测的每个目标的深度特征与各追踪器已保存的特征集之间的余弦距离矩阵。
假如当前帧有11个检测目标,已有10个追踪器,每个追踪器已保留前面6帧目标的深度特征,则计算得到的cost_matrix大小为1011,计算过程为首先对每一个追踪器的6个特征,计算与当前帧11个新检测目标特征之间的(1 - 余弦距离),得到611矩阵,对每个检测块求最小余弦距离,得到1*11矩阵,存入cost_matrix对应行,表示对当前追踪器而言与当前各检测块之间最小的余弦距离;
cost_matrix = self.metric.distance(features, targets)
#distance函数
cost_matrix = np.zeros((len(targets), len(features)))
for i, target in enumerate(targets):
cost_matrix[i, :] = self._metric(self.samples[target], features)
#_metric函数
distances = _cosine_distance(x, y)
return distances.min(axis=0)
return cost_matrix
2.2在计算特征的cost_matrix的基础上,计算kalman滤波预测位置与检测框之间的马氏距离
具体过程为,先将各检测框由[x,y,w,h]转化为[center x,center y, aspect ration,height],对每一个追踪器,也就是cost_matrix中的每一行,计算预测结果与检测结果之间的马氏距离,假设该帧有11个检测结果,那么马氏距离为1*11的矩阵,对cost_matrix中当前行,将马氏距离大于指定阈值的位置处置为1e+5。这样做实现了作者论文中所说的两种度量方式的gating,但是没有体现λ \lambdaλ参数的作用,另外使用cholesky分解计算马氏距离部分不是很理解。
2.3将cost_matrix中大于max_distance的元素置为cost_matrix > max_distance
2.4使用匈牙利算法以cost_matrix为输入进行指派
2.5指派完成后,分类未匹配的检测、未匹配的追踪、匹配对(cost_matrix中阈值大于max_distance的匹配对也送入未匹配检测和未匹配追踪中去)
3. 未级联匹配上追踪器和未确认状态的追踪与未级联匹配上的检测之间基于IOU进行匹配,具体实现是计算cost_matrix矩阵,矩阵的每一行表示一个追踪器和每一个检测结果之间的(1 - IOU)。对cost_matrix中大于max_distance的元素置为max_distance,然后使用匈牙利算法以cost_matrix矩阵作为输入进行指派,指派完成后依然统计matchs,unmatched_detections,unmatched_tracks;
二、匹配上的,去做参数更新
参数更新的过程就是计算kalman滤波的公式3,4,5。其中公式3中的R矩阵为
std = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-1,
self._std_weight_position * mean[3]]
innovation_cov = np.diag(np.square(std))
参数更新完成之后,特征插入追踪器特征集,对应参数进行重新初始化
self.features.append(detection.feature)
self.hits += 1
self.time_since_update = 0 #重置为0
#满足条件时确认追踪器
if self.state == TrackState.Tentative and self.hits >= self._n_init:
self.state = TrackState.Confirmed
三、未匹配的追踪器有可能要删除
未匹配的追踪器表示虽然预测到了新的位置,但是检测框匹配不上
#待定状态的追踪器直接删除
if self.state == TrackState.Tentative:
self.state = TrackState.Deleted
#已经时confirm状态的追踪器,虽然连续多帧对目标进行了预测,
#但中间过程中没有任何一帧能够实现与检测结果的关联,说明目标
#可能已经移除了画面,此时直接设置追踪器为待删除状态
elif self.time_since_update > self._max_age:
self.state = TrackState.Deleted
四、未匹配的检测,初始化为新的追踪器
没有匹配上的检测,说明是出现了新的待追踪目标,此时初始化一个新的kalman滤波器,再初始化一个新的追踪器
#根据初始检测位置初始化新的kalman滤波器的mean和covariance
mean, covariance = self.kf.initiate(detection.to_xyah())
#初始化一个新的tracker
self.tracks.append(Track(
mean, covariance, self._next_id, self.n_init, self.max_age,
detection.feature))
#Tracker的构造函数
self.mean = mean #初始的mean
self.covariance = covariance #初始的covariance
self.track_id = track_id #id
self.hits = 1
self.age = 1
self.time_since_update = 0 #初始值为0
self.state = TrackState.Tentative #初始为待定状态
self.features = []
if feature is not None:
self.features.append(feature) #特征入库
self._n_init = n_init
self._max_age = max_age
#总的目标id++
self._next_id += 1
五、删除待删除状态的追踪器
self.tracks = [t for t in self.tracks if not t.is_deleted()]
六、更新留下来的追踪器的特征集
#每个activate的追踪器保留最近的self.budget条特征
for feature, target in zip(features, targets):
self.samples.setdefault(target, []).append(feature)
if self.budget is not None:
self.samples[target] = self.samples[target][-self.budget:]
#以dict的形式插入总库
self.samples = {k: self.samples[k] for k in active_targets}
————————————————
版权声明:本文为CSDN博主「cdknight_happy」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cdknight_happy/article/details/79731981