DeepSORT


源码解读:

一、 第三帧的检测结果与跟踪的预测结果相匹配

二、根据级联匹配的结果更新跟踪器的集合【frame 3】:

三、 更新Cosine矩阵【需要保存的特征】

四、 可视化及结果的保存


  论文地址:https://arxiv.org/pdf/1703.07402.pdf

  论文官方源码地址:https://github.com/nwojke/deep_sort

  MOT16 benchmark地址:https://motchallenge.net/data/MOT16/

  npy文件地址:https://storage.googleapis.com/drive-bulk-export-anonymous/20191204T070345Z/4133399871716478688/157f00aa-f838-48e5-b6a3-11179ffcc66c/1/bc2e206d-fd99-4e00-b01c-9ca030acc581?authuser

  上述最后两个文件的内容解压缩后:

  • MOT16 benchmark 解压为MOT16文件夹
  • npy文件解压后将其中的detections文件夹剪切,并放置在resources文件夹中。(新建resources)  

源码解读:

  源码的入口是deep_sort_app.py 程序,在该程序的入口包含args = parse_args()函数,说明需要在命令行运行时需要输入命令行参数。如下图所示:

  也正如作者github中关于运行的描述,在命令行或终端运行时需要输入如下指令。

1 python deep_sort_app.py 
2     --sequence_dir=./MOT16/test/MOT16-06 
3     --detection_file=./resources/detections/MOT16_POI_test/MOT16-06.npy 
4     --min_confidence=0.3 
5     --nn_budget=100 
6     --display=True

  如果想要在pycharm中运行,需要需要在pycharm中设置命令行参数。步骤如下:

  1. 选择Run - Edit Configurations...

  2. 在界面中的Parameters:中填写命令行参数。 如果不懂argparse命令行相关设置也不要紧,可以直接将github中给出的命令行参数去掉 "python deep_sort_app.py " 即可。

  以MOT16-14的数据集为切入点进行解读,之后不再赘述。  

  deep_sort_app.py主要由以下几个函数组成,其中核心程序在于def run()程序中的frame_callba ck()函数中,用于实现帧与帧之间跟踪器的处理。

 

*************************************Processing frame 00001******************************************

  1. 首先,获取当前帧数frame_idx的检测信息,并过滤掉置信度小于阈值的bbox。

  这里会将检测信息中包含的bbox坐标的信息、置信度、features存到一个检测类的对象中。并将多个bbox的对象存入列表中。[坐标info(array) 置信度(float) features(array) ]

  检测的坐标info中存储的是bbox[左下坐标的x,y,宽,高]

  在第一帧中,一共有18个b-box。

  

  2. 对这些b-box进行NMS操作

  为了便于操作,将各对象中包含的坐标信息和置信度读取成列表的形式。NMS的操作与yolo中有所不同。NMS操作如下:

  • 将box info转换为[x1, y1, x2, y2]的形式。 【x,y,w,h】 2 【x1, y1, x2, y2】
  • 计算bbox的面积。 需要注意的是,使用像素坐标点计算面积需要+1噢 (y2-y1+1)*(x2-x1+1)
  • 对置信度进行排序,返回索引的排序结果。
  • 进行NMS,每次取置信度最大的bbox与其余的bbox进行iou处理,通过相交面积 / 做iou的bbox的面积产生 overlap。
overlap = (w * h) / area[idxs[:last]] # 相交的面积/比较的框的面积
  • 去掉当前置信度最大的和overlap超过1的索引。【个人感觉此处的NMS并不会有什么作用,面积比超过或等于1的检测框一般在行人检测不存在】 

  3. 进行kalman的相关运算:

  主要包含两个部分,分别代表了Kalman滤波中的时间更新和状态更新。

  3.1 时间更新

  由于此时并没有tracker,不需要对状态进行估计。下述循环也就不存在,该函数直接跳出。

  3.2 状态更新

  首先,会进行匹配级联,返回匹配成功的matches,未匹配的跟踪器unmatched_tracks,未匹配的检测器unmatched_detections。

  由于第一帧还没有tracker,在匹配的过程中就不存在已确认和未确认tracker 。(论文中描述,新生成的跟踪器前三帧是试探期,只有在三帧内都可以匹配成功才会确认,否则将会被删除。

  自然,也就不会存在匹配成功的和未匹配的tracker, 只会有未匹配到的检测器18个(全都是未匹配的检测框)。

  利用检测框创建新的tracker:

# Update track set.

  由于此时匹配的跟踪器和未匹配的跟踪器均为[],并不会对匹配的跟踪器进行更新。只是对未匹配的检测框创建其对应的新tracker。

  detection.to_xyah 是将检测框的(左下坐标x1, y1, w, h)转化为(中心点坐标 x, y, 长宽比a, h)

  初始化kalman相关的变量。mean是一组8维的kalman滤波状态变量x。

  然后,为当前检测框 生成新的Track对象,并存储在tracks列表中:

  Tracker类的初始化如下图所示,可以看出,刚生成的Tracker对象是Tentative试探性的,同论文中所述一致,刚建立的tracker处于试探期,需要在后续三帧中都可以成功关联,该tracker才会确认;否则将会被删除。)另外,每一帧的特征feature也会进行存储,用于后续余弦距离的计算。

  第一帧根据18个detections生成了18个处于试探期的trackers。

  由于此时的trackers均处于试探期,没有确认,所以不会更新其马氏距离。

  在可视化时,会绘制检测框和跟踪框两种。

其中,检测框绘制颜色为0,0,255。由于此时是BGR的色域,所以检测框为红色框。 跟踪框会为每一个id设置不同的颜色。但需要当前的tracker处于确认的状态。 所以在前三帧中,只会绘制检测框,即仅有红色框,如下图所示。

  第一帧除了创建了Tracker,没有什么有含金量的内容。步入第二帧。

*************************************Processing frame 00002******************************************

  第二帧中,关于检测结果的提取和NMS操作与第一帧相同,不再赘述。经过NMS后剩余的检测框detections有12个:

  此时,由于第一帧构建了18个trackers,会利用kalman滤波进行估计,得到18个跟踪预测结果。【即时间更新,tracker.predict()】。而且预测完成后会对每一个tracker的time_since_update+=1。 该操作与SORT中相同,用于衡量其每一次是否关联成功,后续在匹配中会用到。

其中,kalman滤波的时间更新,self._std_weight_position为初始化的1/20(0.05),self. _std_weight_velocity为初始化的1/160(0.00625),mean[3]是上一状态估计值的长宽比。其中可以对应到kalman fitter的时间更新第一步和时间更新第二步的操作。【mean 和 covariance的计算】

  

  目前,有18个trackers, 12个detections,将会涉及到对tracker进行update的相关操作。【tracker.update(detections)

  • 首先要进行检测结果和跟踪预测结果的匹配级联(Matching Cascade)

  步骤如下:

  part 1 : 将现有的trackers划分为confirmed_tracks和unconfirmed_tracks两种:

  此时18个trackers均是unconfirmed_tracks。confirmed_trackers为空列表。

  part 2:针对confirmed_trakers使用外观特征进行级联匹配

  但由于在第二帧,还没有confirmed_trackers,所以该步操作并不会进行。返回的匹配结果均为未匹配的检测框。

  part 3:unconfirmed tracks与还没有匹配上的检测结果通过IOU计算的方式进行关联

  unmatched_tracks_a并不参与IOU关联的运算,仅用于后续unmatched_tracks的构成。

  iou_track_candidates共有18个(源自第一帧建立的track);

  unmatched_detections共有12个 (源自第二帧的检测结果)。

  首先,计算这些框两两之间的iou值,并通过1-iou得到matrix_cost。

  然后,再将cost_matrix中的值大于0.7的,全部置为0.70001。而这些值表示检测框与trackers的IOU较小

   P.S. 该矩阵为(18,12)的矩阵

  将cost_matrix矩阵作为匈牙利算法的输入,得到匹配的结果:

  从匹配结果上可以看出,第0列是iou_track_candidates的索引(18个), 第1列是unmatched_ detections(12个)的索引。

  再对匹配的结果进行进一步的处理: (进一步的关联处理

  Ⅰ. 将仍然没有匹配的检测结果和trackers进行统计。

  Ⅱ. 将cost_matrix>0.7的过滤器与追踪器之间的关联去掉。【cost_matrix>0.7,说明二者的IOU小于0.3,即二者之间的关联不大】

  Ⅲ. 记录匹配成功的track id 和 detection id

  Ⅳ. 最终返回处理后的结果

  part 4 part 2中处理的结果与part 3 中处理的结果进行整合,得到最终的匹配结果和未匹配结果。

  • 匹配级联完成之后,更新多目标跟踪器trackers集合

  part 1 针对match的,根据检测的结果更新track的参数。 【Kalman 状态更新过程】

   主要包含以下步骤:

  Ⅰ. 与SORT一样,更新Kalman滤波中的一系列运动变量、关联次数以及重置time_since_update.

  Ⅱ. 将当前帧的features保存到该跟踪器中。

  Ⅲ.如果已经连续关联3帧,将该track的状态由tentative改为confirmed。由于此时还没有成功关联3帧,各tracker还是1 tentative的状态。

  此时,每个track中将包含2帧中目标的features

  part 2: 针对unmatched tracks:

  对于未匹配的跟踪器,处理手段主要包含以下两种情况:

  Ⅰ. 当tracker处于tentative(试探期)时, 直接将该tracker和其id删除;

  Ⅱ. 当tracker处于确认状态,并且self.time_since_update大于max_age(源码中设置的是30),也就是说,如果已确认的tracker 30帧以上未关联成功,就将该tracker即其id删除。

  此时,[9, 10, 11, 15, 16, 17]这些id的tracker将会被删除。

  part 3: 针对unmatched_detections:

  对于未匹配的检测框,要为其创建新的tracker【处于试探期的】。

   因为当前帧没有unmatched_detection,所以该操作没有进行。 【该操作与第一帧根据detections 生成tracker的操作相同】

  • 更新已确认的距离度量矩阵

  由于此时的trackers仍然都处于试探期,所以并不会进行更新。

  同样,该帧的trackers都未得到确认,在显示时仅会显示检测框的红色,并且在结果保存时,并不会写到txt中。

  

*************************************Processing frame 00003******************************************

  第三帧中,有14个检测框,12个未确认的tracker(处于tentative状态)。

  首先,要对12个未确认的tracker进行Kalman状态估计。然后对每一个tracker的time_since_ update += 1。【更新完成后匹配成功的会置0,不成功的将会进行删除,或者死缓的操作。】

  【tracker.predict()

  重点还是放在12个未确认的tracker和14个detections如何匹配,以及tracker如何更新上!【tracker.update()

一、 第三帧的检测结果与跟踪的预测结果相匹配

  step 1: 将现有的trackers划分为已确认和未确认两组。

  step 2:首先对已经确认的trackers进行匹配级联。

  由于此时没有已经确认的trackers,所以该步依然不会有实质性的操作。返回结果中,匹配的tracker,未匹配的tracker均为空列表[],未匹配的检测框detections为当前的检测结果。

  

  step 3: unconfirmed tracks与还没有匹配上的检测结果通过IOU计算的方式进行关联

  unmatched_tracks_a并不参与IOU关联的运算,仅用于后续unmatched_tracks的构成。

  此时,unconfirmed_tracks包含12个,未匹配的检测器包含14个。对它们之间进行IOU关联运算。

  IOU关联运算,首先计算iou_track_candidates与unmatched_detections两两之间的cost_matrix矩阵。cost_matrix矩阵的计算通过iou计算,再通过1-iou。

  然后,将cost_matrix矩阵中>0.7的值全部赋值为0.70001。

  再将处理后的cost_matrix输入到匈牙利算法中进行匹配,得到线性的匹配结果indices。左侧为跟踪器的id,右侧为检测器的id。

  step 4:对匹配结果进一步处理,获取最终的匹配,未匹配【检测、跟踪】的结果。

  首先,对所有的检测结果id 在线性匹配结果indices中遍历,将没有出现的检测结果记录到未匹配检测unmatched_detections中。

  其次,对所有跟踪结果id 在线性匹配结果indices中遍历,将没有出现的检测结果记录到未匹配跟踪器unmatched_tracker中。

  最后,对cost_matrix中值大于0.7 对应的跟踪id和检测id加入到未匹配的行列中。【因为它们之间的iou太小】 将cost_matrix中值小于0.7对应的跟踪id和检测id结果对加入到匹配的结果中。并返回结果。

  这些计算都是基于cost_matrix和匈牙利算法计算的结果indices。

  step 5:step2 step4的结果进行整合

  第三帧级联匹配的结果:

  成功匹配的结果有11个,未匹配的检测结果有3个,未匹配的跟踪器有1个

二、根据级联匹配的结果更新跟踪器的集合【frame 3】:

  1. 状态的变更(试探 确认 删除 1 2 3 )

  2. 新跟踪器的创建

  step 1:针对matches,根据检测结果更新track参数。(11个)

  A. 根据检测结果,更新Kalman参数

  主要包括以下步骤:

  a. 根据检测的结果更新Kalman状态参数。 主要是计算状态变量x(mean)和Pk(covariance);

  b. 将当前帧标定的特征保存到当前的跟踪器中;

  c. 更新当前track的状态是否更新。 是否从试探期转化到确认期。(3帧 存在)

  此时,有11个track已经经历了3帧,状态将会被修改为确认状态Confirmed,在代码中以2进行保存。

  step 2:针对unmatched_tracks (删1个)

  对于没有成功匹配的跟踪器,主要由两种情况:

  A. 没有匹配的跟踪器是确认状态Confirmed: 30帧的死缓,如果30帧都没有成功关联,则修改其状态为Delete,在代码中以3进行保存。

  B. 没有匹配的跟踪器是试探状态Tentative: 修改其状态为Delete,在代码中以3进行保存。

  状态表:

  在第三帧中,一中最后的结果,将会有1个跟踪器被删除

  step 3: 针对unmatched_detections: (建3个)

  需要创建3个新的跟踪器(试探期)。  

三、 更新Cosine矩阵【需要保存的特征】

  此时,已经包含状态为Confirmed的tracks,便需要对distance metric进行更新。tracks即active_targets中:

  A. 对于已经确认的跟踪器,提取器特征集

  B. 计算Cosine metric

  最多会保存100条特征集合。   

四、 可视化及结果的保存

  由于此时已经包含了状态为Confirmed的跟踪器,所以在显示的过程中不会仅显示红色的检测框。并且这些已经确认的跟踪器也会保存在txt中。

  可以看出,第3帧与前两帧不同的是在更新时,产生了Confirmed状态的跟踪器。增添了Cosine矩阵的计算。同时,在显示时,也增添了 追踪的结果。

*************************************Processing frame 00004******************************************

  由于第3帧中,产生了状态为Confirmed trackers,所以在第4帧中,将会增添已确认trackers的级联匹配操作。

  关于前3帧相同的操作,这里将会进行简述,只对新增的对Confirmed状态的trackers的级联匹配进行详述。

  第4帧中,一共有14个检测框。

  根据现有的trackers,对每一个trackers进行kalman跟踪结果预测,也就是时间更新的状态估计。预测完之后,对每一个trackers的self.time_since_update += 1。 其中,有11个是Confirmed 的状态,有3个是tentative状态。

    

  进入14的detections与14个trk的匹配过程。【update】

  1. 将现有的跟踪器划分为已确认和未确认两组;

  此时,已经存在confirmed_tracks。

    

  2. 对confirmed_tracks与detections进行级联匹配。

  前3帧中,由于此时没有confirmed_tracks,所以不进行该过程的实际操作。第4帧中,存在confirmed_tracks,需要对其进行级联匹配,这也是第四帧中新增的一部分。

  A. 首先获取处于确认状态的trackers的indices和 检测框的indices,将检测框的indices命名为unmatched_detections:

  B. 对已经确认的tracker进行级联匹配

  级联匹配是什么呢?为什么会有级联匹配呢?

  由于已经确认的tracker存在可能没有匹配到的状态,即处于死缓的tracker。所以需要对那些死缓期间的trackers,需要对几帧没有匹配到的tracker依次进行级联

  所以cascade_depth为30,与死缓的帧数一致。当cascade_depth为0时,即为非死缓的tracker。

  track_indices_1列表的作用就是统计不同level的tracker。【正常状态的和不同死缓的】,对他们进行关联操作,可以看出,正常状态-1帧未关联-2帧未关联- .... - 29帧未关联,优先级递减。

  关联操作与检测框与未确认的跟踪器的关联操作是一致的。

  结果如下:

  可以看出,在有已确认的trackers时,基本上已经把detections的结果大多数都关联了。留给没有确认的trackers的detections不多了。这种优胜劣汰的机制也与人的感知十分的神似~

  其中,min_cost_matching函数中,包含distance_metric的传入,其调用函数为gated_metric():

  其中,包含余弦距离(外观特征)和马氏距离(运动信息)的计算。

  余弦距离:

  self._metric()函数中调用下述函数实现余弦距离的计算:

  

  3. 对未确认的trackers进行IOU匹配。

  匹配结果如下:

  4. 获得最终的匹配情况

  5. 进行跟踪器集合的更新

     A. 状态的转换

     B. 新跟踪器的生成

  6. 更新现有已确认的跟踪器的x和Pk

  7.进行可视化和保存

    

  

原文地址:https://www.cnblogs.com/monologuesmw/p/13534798.html