本篇文章主要梳理deepsort跟踪的基本流程,不会具体讲解所有细节,但是基本的环节都不会遗漏。
基本概念
匈牙利算法
匈牙利算法也可以称之为最小权重匹配,给一个矩阵,每一行至多与一列匹配,每一列也至多与一行匹配,当所有行或者列都匹配结束后,使匹配的结果之和最小。
>>> cost = np.array([[4, 1, 3], [2, 0, 5], [3, 2, 2]])
>>> from scipy.optimize import linear_sum_assignment
>>> row_ind, col_ind = linear_sum_assignment(cost)
>>> col_ind
array([1, 0, 2])
>>> cost[row_ind, col_ind].sum()
5
卡尔曼滤波
待完善。。。
成员
1.tracks:指正在跟踪的目标Tentative,Confirmed ,Deleted
tracks=[track1,track2,track3,,,]
每个track属性:
self.mean = mean #[x,y,a,h,ax,ay,aa,ah]
self.covariance = covariance
self.track_id = track_id
elf.hits = 1 #
self.time_since_update = 0 #上一次update距离当前帧的帧数,每次匹配成功后该值会自动清零,为匹配成功则自动加一。
self.state = TrackState.Tentative #一共有Tentative,Confirmed ,Deleted 三个状态,
self.features = [] # 目标特征
self._n_init = n_init # 连续成功匹配n_init次后,状态由Tentative转到Confirmed
self._max_age = max_age # 跟丢后保留的帧数
self._n_init的设计是为了防止误报,这个值可以是三,也可以是1。
每个track的状态变化条件
#匹配成功时
if self.state == TrackState.Tentative and self.hits >= self._n_init:
self.state = TrackState.Confirmed
#匹配失败时
if self.state == TrackState.Tentative:
self.state = TrackState.Deleted
elif self.time_since_update > self._max_age:
self.state = TrackState.Deleted
Confirmed状态其实包括了正常跟踪和短暂跟丢两个状态。
2.detections
detections:指当前检测到的目标
属性:
self.tlwh = np.asarray(tlwh, dtype=np.float)
self.confidence = float(confidence)
self.feature = np.asarray(feature, dtype=np.float32)
3.tracker
tracker是指用于匹配tracks和detections的方法的概述。
主要包括predict和update两个方法,predict要用在update之前。
predict的作用是通过卡尔曼滤波预测每一个track在当前帧的状态。
self.mean, self.covariance = kf.predict(self.mean, self.covariance)
update是tracker的重要操作,根据tracks在当前帧的状态和detections的状态进行匹配,并根据匹配结果去更新tracks,使tracks中的track的状态和数量进行相应的变化。
基本流程
1.预测每个track在当前帧的状态。
tracker.predict()
每个track对应的变化是
self.mean, self.covariance = kf.predict(self.mean, self.covariance)
self.age += 1
self.time_since_update += 1
2.匹配
tracker.update(detections)
这一步是跟踪的关键环节
这一步会输出三个结果,matches, unmatched_tracks, unmatched_detections。
matches, unmatched_tracks, unmatched_detections = self._match(detections)
2.1 特征匹配
过程是这样的:
将处于Confirmed这个状态的track的feature和detections的feature进行特征匹配,计算余弦距离,计算之后再根据每个track和每个detection的距离进行修正,若某个track和某个detection的距离很远,那么他们之间的余弦距离就重置为一个较大的值。然后再将余弦距离大于阈值(0.2)的位置设置为0.2+1e6。处理之后在经过匈牙利算法得到匹配结果。匈牙利匹配的结果中,如果它们之间的余弦距离小于阈值那么算是成功匹配,否则就是匹配失败。
匹配的过程并不是一次性匹配完成,而是matching_cascade。根据time_since_update的值的大小进行匹配,先将time_since_update值最小的tracks与detections的特征进行匹配,也就是tracks中的每个track的上一次状态更新(成功匹配才会更新状态)距离当前帧越近就越优先匹配。这里是与fairmot有一些不同,fairmot将跟踪状态和短暂跟丢(lost)的tracks同时与detections进行特征匹配。
这一步结束后,如果某个track没有和某个detection匹配成功,而且它的time_since_update==1,那么它将进入下一步通过IOU与没有匹配成功的detections进一步匹配。对于time_since_update>1的track没有资格进行IOU匹配。(fairmot中处于lost状态的track也不会进行iou的匹配)
# fairmot代码
r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]
2.2 IOU匹配
该匹配就是将特征匹配失败且time_since_update==1的track与处于Tentative的track一起与特征匹配失败的detections进行匹配,计算它们之间的IOU值,得到一个IOU矩阵,同样根据匈牙利算法和阈值确定匹配结果。
3.更新
整体的更新
for track_idx, detection_idx in matches:
self.tracks[track_idx].update(self.kf, detections[detection_idx])
for track_idx in unmatched_tracks:
self.tracks[track_idx].mark_missed()
for detection_idx in unmatched_detections:
self._initiate_track(detections[detection_idx])
self.tracks = [t for t in self.tracks if not t.is_deleted()]
每个track的更新,其中特征的更新是把特征加到特征列表里。
def update(self, kf, detection):
self.mean, self.covariance = kf.update(
self.mean, self.covariance, detection.to_xyah())
self.features.append(detection.feature)
self.hits += 1
self.time_since_update = 0
if self.state == TrackState.Tentative and self.hits >= self._n_init:
self.state = TrackState.Confirmed
由于track把历史的特征存在了列表里,进行特征匹配时每个特征都要计算,最后计算均值。