参考:

这篇文章写得很好,我在阅读学习的时候对其中我觉得有些不太具体的地方加了一些自己的理解。可以结合着一起看。

  1. 局部变量:
  1. activated_starcks:所有is_activated=True的Strack对象
  2. refind_stracks:该次操作中原本状态为lost之后又匹配上的Stracks对象
  3. lost_stracks:该次操作中原本状态为Tracked后来又丢失的对象
  4. removed_stracks:丢失状态超过阈值之后的那些Strack对象
  1. 全局变量:self.tracked_stracks:目前所有状态为Tracked那些跟踪对象
  2. 按照文中所说,第一帧直接跳到初始化。
for inew in u_detection:            # 对cosine/iou/uncofirmed_tracker都未匹配的detection重新初始化一个unconfimed_tracker
    track = detections[inew]
    if track.score < self.det_thresh:#conf_thres
        continue
    track.activate(self.kalman_filter, self.frame_id)       # 激活track,第一帧的activated=T,其他为False
    activated_starcks.append(track)#加入状态为activate的列表中

这里需要特别注意的是if后面的操作,这里的track.score就是设置的参数conf_thres是在跑代码的时候设置的,这里我理解就是检测器检测出来的目标得分小于这个阈值的话就不进行下面两行的操作。重点就要看一下下面两行:

track.activate(self.kalman_filter, self.frame_id)  
activated_starcks.append(track)#加入状态为activate的列表中
def activate(self, kalman_filter, frame_id):
    """Start a new tracklet"""
    self.kalman_filter = kalman_filter
    self.track_id = self.next_id()
    self.mean, self.covariance = self.kalman_filter.initiate(self.tlwh_to_xyah(self._tlwh))

    self.tracklet_len = 0
    self.state = TrackState.Tracked
    if frame_id == 1:###这里是针对第一帧特殊处理了。
        self.is_activated = True
    #self.is_activated = True
    self.frame_id = frame_id
    self.start_frame = frame_id

注意第9,10行代码,这个地方是对第一帧进行了特殊处理,如果是第一帧检测到的对象就把他的is_activated设为True,在该类初始化的时候,默认的参数都是is_activated=False。所以之后的每一帧中,如果有这种未匹配上的检测框,他的is_activated都是False这样在后面的输出时如果是第一次检测到且没有与任何之前的轨迹匹配上,是不会进行输出的(但是会被加入到activated_starcks列表中,而且在后面也会被加入到self.tracked_stracks中,但是在最后输出的时候不会被输出),默认它是误检,除非下一帧再一次匹配上了才会把他设置为True。输出代码如下

output_stracks = [track for track in self.tracked_stracks if track.is_activated]

补充

for track in self.tracked_stracks:
    if not track.is_activated:
        unconfirmed.append(track)
    else:
        tracked_stracks.append(track)

看这个代码,例如我们上一帧中新检测出来了几个结果,但是还未与任何对象匹配上,那就很可能是误检

也就是说第一帧的时候除了检测得分低于阈值的其他的is_activated都为True。

因此第一帧输出的结果就是检测结果中得分大于阈值的那些物体

其实代码后面的这些判断得分的都没啥用,应为在检测结果出来之后有一个筛选过程

dets = self.post_process(dets, meta)
dets = self.merge_outputs([dets])[1]

remain_inds = dets[:, 4] > self.opt.conf_thres#根据得分进行过滤
dets = dets[remain_inds]

这里就已经把低分的框删去了,这里面用的这几个阈值都是同一个参数,所以后面的都跟分数没影响,都不需要考虑……

此时再进行第二帧的时候就好理解了,这里的self.tracked_stracks就是所有状态为Tracked的对象:

for track in self.tracked_stracks:#这里个人认为是上一帧确认的对象
    if not track.is_activated:#得分小于阈值的将会被置为false,也就是不确定的
        unconfirmed.append(track)
    else:
        tracked_stracks.append(track)#is_activated为False的对象

首先在self.tracked_stracks中找到is_activated为True的,存入tracked_stracks中,为False的存入unconfirmed。

  1. 然后把tracked_stracks中的对象和lost_tracks中的对象结合起来,也就是把跟丢的但是没超过丢失最大阈值的那些对象也进行匹配。
  2. 再把未匹配上的对象进行IOU匹配,如果跟踪对象还未匹配上,就把这些track状态设为lost(该帧的检测对象不受影响)
  3. 然后把剩下的未匹配上的检测对象与unconfirmed跟踪对象进行IOU匹配,这里的unconfirmed就是is_activated为Flase的那些对象,在第二帧中没有这种对象,但是可以想一下例如现在在第十帧,那么第九帧中会有一些检测对象由于没有成功匹配所以is_activated为False,这里就是在匹配这些上一帧第一次出现且上一帧未成功匹配的那些检测对象。如果该帧那些跟踪对象还未匹配上,就认为是误检了。

与deepsort算法对比:

  1. deepsort中使用的是整个轨迹加权之后的特征。例如一个轨迹出现在1-10帧中,在deepsort中会将所有帧的特征存入一个容器,也就是该容器中有10个特征对象,匹配的时候取每个检测对象和一段轨迹中代价最小的那个最为代价矩阵中的值,但是FairMOT这里使用的JDETracker使用的是一个加权后的特征。
def update_features(self, feat):
        feat /= np.linalg.norm(feat)
        self.curr_feat = feat#当下特征,也就是上一次匹配上之后的特征
        if self.smooth_feat is None:
            self.smooth_feat = feat
        else:
            self.smooth_feat = self.alpha * self.smooth_feat + (1 - self.alpha) * feat
        self.features.append(feat)#存储所有特征?
        self.smooth_feat /= np.linalg.norm(self.smooth_feat)#下面分母就是向量的二范数
  1. deepsort中在进行特征匹配的时候是对跟踪对象距离上一次更新的远近作为匹配顺序,首先会取所有上一帧刚更新过的跟踪对象与检测对象进行匹配,然后再取上上一帧更新过的跟踪对象进行匹配,以此类推。由于FairMOT中每个跟踪对象都是一个特征。
  2. deepsort中对初始化对象需要其连续三帧(可以自己设定)都能匹配到检测对象才将其转为Confirmed。FairMOT中是对第一帧单独处理,之后的初始化的对象需要在下一帧可以匹配得上才行,否则认为是误检。

如有错误还请指正!