FairMOT代码分析

1. 核心模型

从图中可以看出,有三个核心点:

  1. network进行了4次下采样和上采样分别计算特征图
  2. 输出结果的detection部分使用类似centerNet,heatmap用于预测中心点,center offset用于调整位置,boxsize用于预测框
  3. 直接将特征向量作为结果输出(接在reid embeddings之后)
    FairMOT代码分析_第1张图片

2. 跟踪器

使用JDE跟踪器,包含如下参数:
activated(激活状态), tracked(跟踪状态),mean(卡尔曼均值向量), covariance(卡尔曼协方差矩阵), smooth_feat(tracker的外观特征)

4种跟踪状态:
unconfirmed_stracks(activated = F, track_state=tracked ) 只出现一次的目标(检测器误检的目标)
activated_stracks(activate=T, track_state=tracked) 跟踪状态良好的tracker
lost_stracks(activate=T, track_state=lost)激活,但是跟踪状态丢失
refind_stracks(activated=T, track_state=lost->tracked)跟丢之后重新找回的目标

几个数据结构:
detection/track:每一帧的模型预测结果
strack:detection/track的组合。第一帧的activated=T,并赋予一个kalman_filter,其他为False

然后是具体流程:

  1. 第一帧:
    对每个目标初始化一个tracker,检测出n个目标,就用n个tracker,然后将n个tracker放入activated_stracks容器中。
# 从step 4开始,其中u_detection是未分配的track。
for inew in u_detection:            
# 对cosine/iou/uncofirmed_tracker都未匹配的detection重新初始化一个unconfimed_tracker
    track = detections[inew]
    track.activate(self.kalman_filter, self.frame_id)
    # 激活track,第一帧的activated=T,其他为False
    activated_stacks.append(track)
  1. 第二帧,对新检测目标进行外观+kalman距离匹配
    step1对第2帧进行目标检测得detections,
    然后再step2将[activated_strack,lost_stracks]融合成pool_stracks,
    将这个pool_stracks与detections根据feat计算外观矩阵,就是用feat计算cosine距离,
    然后用Strack.multi_predict函数,也就是卡尔曼算法预测pool_stracks的新的mean,covariance。
    然后用matching.embedding_distance计算pool_stracks和detection的外观距离,
    接着用matching.fuse_motion,加入运动模型,得到新的距离矩阵,并将大于阈值的矩阵赋值为inf。
    接下来调用matching.linear_assignment,利用匈牙利算法进行匹配,得到matches, u_track, u_detection三元组。
  • matches是能匹配的track和detection:1)pool_stracks的track_state=tracked,更新smooth_feat,卡尔曼状态更新mean,covariance(卡尔曼用),计入activated_stracks;2)pool_stracks的track_state=tracked,更新smooth_feat,卡尔曼状态更新mean,covariance(卡尔曼用),计入refind_stracks
    不能匹配的:提出不能匹配的,得到新的detections,r_tracked_stracks
strack_pool = joint_stracks(tracked_stracks, self.lost_stracks)
dists = matching.embedding_distance(strack_pool, detections)    # 计算新检测出来的目标和tracked_tracker之间的cosine距离
STrack.multi_predict(strack_pool)  # 卡尔曼预测
dists = matching.fuse_motion(self.kalman_filter, dists, strack_pool, detections)        # 利用卡尔曼计算detection和pool_stacker直接的距离代价
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=0.7)           # 匈牙利匹配 // 将跟踪框和检测框进行匹配 // u_track是未匹配的tracker的索引,

for itracked, idet in matches:      # matches:63*2 , 63:detections的维度,2:第一列为tracked_tracker索引,第二列为detection的索引
    track = strack_pool[itracked]
    det = detections[idet]
    if track.state == TrackState.Tracked:
        track.update(det, self.frame_id)       # 匹配的pool_tracker和detection,更新特征和卡尔曼状态
        activated_starcks.append(track)
    else:
        track.re_activate(det, self.frame_id, new_id=False)     # 如果是在lost中的,就重新激活
        refind_stracks.append(track)
  1. 剩下的,用IOU进行匹配
    对剩下的detections、r_tracked_stracks,使用matching.iou_distanc进行IOU匹配,然后对距离使用matching.linear_assignment再次进行匈牙利匹配
    能匹配的:1)pool_stracks的track_state=tracked,更新smooth_feat,卡尔曼状态更新mean,covariance(卡尔曼用),计入activated_stracks;pool_stracks的track_state==tracked,更新smooth_feat,卡尔曼状态更新mean,covariance(卡尔曼用),计入refind_stracks
    不能匹配的:r_tracked_stracks状态track_state改为lost,detections再遗留到下一步进行继续匹配
detections = [detections[i] for i in u_detection]           # u_detection是未匹配的detection的索引
r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]
dists = matching.iou_distance(r_tracked_stracks, detections)
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=0.5)

for itracked, idet in matches:
    track = r_tracked_stracks[itracked]
    det = detections[idet]
    if track.state == TrackState.Tracked:
        track.update(det, self.frame_id)
        activated_starcks.append(track)
    else:
        track.re_activate(det, self.frame_id, new_id=False)   # 前面已经限定了是TrackState.Tracked,这里是不用运行到的。
        refind_stracks.append(track)

for it in u_track:
    track = r_tracked_stracks[it]
    if not track.state == TrackState.Lost:
        track.mark_lost()
        lost_stracks.append(track)      # 将和tracked_tracker iou未匹配的tracker的状态改为lost
  1. 上一步遗留的detection与unconfirmed_stracks进行IOU匹配
    计算IOU cost,然后进行匈牙利匹配
    能匹配:更新 unconfirmed_stracks,更新smooth_feat,卡尔曼状态更新mean,covariance(卡尔曼用),计入activated_stracks
    不能匹配:unconfirmed_stracks直接计入removed_stracks;不能匹配的detections,在遗留到下一步
detections = [detections[i] for i in u_detection]       # 将cosine/iou未匹配的detection和unconfirmed_tracker进行匹配
dists = matching.iou_distance(unconfirmed, detections)
matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
for itracked, idet in matches:
    unconfirmed[itracked].update(detections[idet], self.frame_id)
    activated_starcks.append(unconfirmed[itracked])
for it in u_unconfirmed:
    track = unconfirmed[it]
    track.mark_removed()
    removed_stracks.append(track)
  1. 上一步遗留的detections,在step4初始化成unconfirmed_stracks中的tracker
for inew in u_detection: # cosine/iou/uncofirmed_tracker都未匹配的detection重新初始化一个unconfimed_tracker
    track = detections[inew]
    if track.score < self.det_thresh:
        continue
    track.activate(self.kalman_filter, self.frame_id)       # 激活track,第一帧的activated=T,其他为False
    activated_starcks.append(track)
  1. 对15帧连续track_state=lost的tracker,进行删除(这里改成仅用外观向量匹配)
for track in self.lost_stracks:
    if self.frame_id - track.end_frame > self.max_time_lost:        # 消失15帧之后
        track.mark_removed()
        removed_stracks.append(track)

3. 代码结构

主目录下的requirements.txt需要安装:pip install -r requirements.txt
需要自己新建一个文件夹models:下载all_hrnet_v2_w18.pth、hrnetv2_w18_imagenet_pretrained.pth两个预训练模型

3.1 Tracker

basetracker基本数据结构,注意其中location用于跨镜跟踪。features是一个数组,用于存储所有的特征向量:
FairMOT代码分析_第2张图片

接下来是STrack类,继承BaseTrack类,注意这里有一个buffer_size参数,表示最大允许存储的特征向量个数:
FairMOT代码分析_第3张图片
然后是最核心的JDETracker:
FairMOT代码分析_第4张图片
其中的update函数用到了第二节的代码。

3.2 Networks

3.2.1 DCNv2

使用DCNv2_new作为backbone

cd src/lib/models/networks/DCNv2_new 
sh make.sh

我们来看下里面的内容,其实就是pip install DCNv2,参见https://github.com/charlesshang/DCNv2
FairMOT代码分析_第5张图片
具体来说,原来只需要学习9个参数,现在要学习18个参数(每个点还有一个偏移量要学习)
FairMOT代码分析_第6张图片

3.* Demo

两种模式,我们测试的时候使用的方法是,把追踪结果写到result.txt中,然后再进行渲染。
核心是调用了track.py中的eval_seq函数。

你可能感兴趣的:(FairMOT代码分析)