MOT指标个人理解及源码分析:
官网指标:
还有一篇讲的比较好的中文博客:博客
但是我对其中一些细节还是没有特别理解,就仔细看了论文以及MOT官方给出的计算评价指标的源码,在此加以总结和记录。
前言
检测对象:该帧中检测器检测到的目标
跟踪对象:当前所有的轨迹(由之前每一帧成功匹配的检测目标组成)
如图2是某一帧中的跟踪结果以及ground truth,这里虚线是我们的跟踪结果,实线带填充的是ground truth,因为是MOT数据集所以得出的结果都是带id号的,黑色的是ground truth中的id号,红色是跟踪结果的id号。
首先要清楚,MOTA统计的是最终的输出结果,也就是做多目标跟踪时我们每一帧都要对检测对象和跟踪对象来进行数据关联,获取到最终的检测跟踪对象,所以这里的TP,FP,FN都是针对的关联成功的最终结果。在我们输出的跟踪结果中,每一帧中都是带有编号的bbox如图2所示,在求FP,TP,FN这几个指标时,我们就想成是每一帧中没有编号的bbox和没有编号的ground truth做运算。同时这里参与运算一定都是每一帧中数据关联成功的检测跟踪对象!!!如图3就是将序号去掉的结果,虚线是输出的跟踪结果中的检测跟踪对象,实线有填充的是ground truth。
MOTA:
首先说一下混淆矩阵中的TP,FP,FN,TN,我个人是这样记的:拆成两部分第一部分是T/F,第二部分是P/N,第二部分表示我们预测的是P或者是N,第一部分表示:根据第一部分的预测我们预测的是对了还是错了。就好比在这里,我们认为这个结果是一个目标,实际上这里确实有ground truth那这就是一个TP
-
TP:数据关联成功的某个检测跟踪对象和某一个ground truth的IOU大于α,也就是我们的算法认为这个目标就是我们要跟踪的,且正好和groud truth一致。如图3中黑色圆圈圈住的两个目标
-
FP:数据关联成功的某个检测跟踪对象和任意ground truth的IOU小于α,也就是我们的算法认为这个目标是我们要跟踪的,但是实际上这里并没有ground truth。图3中的红色圆圈圈住的目标
-
FN:某个ground truth和任意一个数据关联成功的检测跟踪对象的IOU都小于α,也就是我们的算法并不认为这里有我们要跟踪的目标,但是实际上这里确实有一个目标。也就是图3中绿色圆圈圈住的目标。
-
IDSW:
文中说首先IDSW只针对TP,也就是跟踪出来认为这里有目标,实际上也确实有。简言之:一个gtID在最终结果中的变化次数。
图4 这里用图4进行解释,仿照图1画了一个图,更好的解释一下,这里黑色无填充圆圈代表某一个id的gt轨迹,蓝色填充圆圈和黄色填充圆圈代表输出结果中两个不同id的结果,黑色矩形框框住的都是TP,因为输出结果和gt匹配上了。在这幅图中IDSW为2,先从蓝色id变为黄色id,之后又变回了蓝色id,上文说一个TP的prID和之前的TP的prID值不同,即为一个IDSW,所以我理解这里应该为2。
问题:IDSW大,不一定跟踪效果就不好,比如
就像上图反映的,上下两组结果的TP,FP,FN都是相同的,但是下面的IDSW为4,上面的为2,MOTA计算结果反而上面更好,我认为下面明显更好。 -
TPs,FPs,FNs就是把结果中所有的相加即可。
IDF1
IDF1考虑的是轨迹级别的问题。论文3中的公式
以及解释,感觉理解不是特别透彻,这里我结合MOTchallenge官网提供的评测代码进行具体步骤分析。首先总结一下我看完代码后的个人理解:首先我们需要通过IOU来计算每一帧中输出结果和gt的相似度(用IOU计算的,和MODA中的一样),然后将大于阈值α的认为是关联上的检测跟踪对象。在最终结果中找到tracker和gt的关联关系,计算其中关联失败的对象数,计算以下指标。
- IDTP
- IDFP
- IDFN
具体代码分析:
data中的一些值
data[‘num_tracker_dets’]:跟踪结果中的检测结果
data[‘num_gt_dets’]:标注数据中没有标注检测结果的gt
data[‘num_tracker_ids’]:跟踪结果中的id个数
data[‘num_gt_ids’]:gt中的id个数
data[‘gt_ids’]:存的是gt每一帧中的所有id
data[‘tracker_ids’]:存的是跟踪结果每一帧中的所有id
data[‘similarity_scores’]:每一帧中,tracker和gt相似度的计算结果,列表中每一个对象是一帧的相似度计算结果——一个二维矩阵
def eval_sequence(self, data):
"""Calculates ID metrics for one sequence"""
# Initialise results
res = {}
for field in self.fields:#k:指标,v:值(当前是0)
res[field] = 0
# Return result quickly if tracker or gt sequence is empty
if data['num_tracker_dets'] == 0:
res['IDFN'] = data['num_gt_dets']
return res
if data['num_gt_dets'] == 0:
res['IDFP'] = data['num_tracker_dets']
return res
# Variables counting global association
potential_matches_count = np.zeros((data['num_gt_ids'], data['num_tracker_ids']))#建立一个尺寸为gt_id个数*输出结果id个数的数组,初始值为0,这个数组是为了记录一对gt_id和tracker_id的匹配上(IOU大于阈值)的个数
gt_id_count = np.zeros(data['num_gt_ids'])#这里是生成一个长度等于gt_id个数的一个一维数组,这个用来记录每个gt_id在视频中出现的帧数
tracker_id_count = np.zeros(data['num_tracker_ids'])
#这里是生成一个长度等于tracker_id个数的一个一维数组,这个用来记录每个tracker_id在视频中出现的帧数
import pdb
# First loop through each timestep and accumulate global track information.
for t, (gt_ids_t, tracker_ids_t) in enumerate(zip(data['gt_ids'], data['tracker_ids'])):#这里是一帧一帧遍历其中的id,gt_ids_t是gt中每一帧的所有id
# Count the potential matches between ids in each timestep
matches_mask = np.greater_equal(data['similarity_scores'][t], self.threshold)#data[similarity_scores][t]是第t帧中每一个track和gt的相似度,是一个二维矩阵
match_idx_gt, match_idx_tracker = np.nonzero(matches_mask)#这里返回的是非0的行号和列号也就是gt和tracker的编号,也就是相似度大于阈值的gt-tracker对。
potential_matches_count[gt_ids_t[match_idx_gt], tracker_ids_t[match_idx_tracker]] += 1#统计每一对的匹配次数
# Calculate the total number of dets for each gt_id and tracker_id.
gt_id_count[gt_ids_t] += 1#这里就是给出现过的id增加1,记录出现的次数
tracker_id_count[tracker_ids_t] += 1
pdb.set_trace()
# Calculate optimal assignment cost matrix for ID metrics
num_gt_ids = data['num_gt_ids']#gt中的id数
num_tracker_ids = data['num_tracker_ids']
#下面的操作第一遍看很难理解,但确实很巧妙!!!!如果其中某一步不太理解,可以先跟着我的注释顺一遍。
fp_mat = np.zeros((num_gt_ids + num_tracker_ids, num_gt_ids + num_tracker_ids))
fn_mat = np.zeros((num_gt_ids + num_tracker_ids, num_gt_ids + num_tracker_ids))#产生一个尺寸为:num_gt_ids + num_tracker_ids*num_gt_ids + num_tracker_ids,值全为0的矩阵
fp_mat[num_gt_ids:, :num_tracker_ids] = 1e10#把矩阵的左下部分改成1*10的10次方
fn_mat[:num_gt_ids, num_tracker_ids:] = 1e10#把矩阵的右上部分改成1*10的10次方
pdb.set_trace()
for gt_id in range(num_gt_ids):
fn_mat[gt_id, :num_tracker_ids] = gt_id_count[gt_id]
fn_mat[gt_id, num_tracker_ids + gt_id] = gt_id_count[gt_id]##这行代码的作用还不太明白,为什么要在后面形成一块对角阵???最后相加之后,无疑是最大的,根本不可能匹配上
#最后的fn_mat格式为每行中所有列数小于跟踪对象的都是gt数,大于的是一个对角矩阵,对角线为对应gt_id_counts,其他地方都是1*10的10次方
for tracker_id in range(num_tracker_ids):
fp_mat[:num_gt_ids, tracker_id] = tracker_id_count[tracker_id]
fp_mat[tracker_id + num_gt_ids, tracker_id] = tracker_id_count[tracker_id]
#最后fp格式为每一列,行数小于gt数的都是tracker数,大于的是一个对角阵
#fp,fn输出的时候都是左上角num_gt_ids*num_tracker_ids这一块有值
#下面两行代码的操作一模一样,这里我说一下我的看法,IDFN是针对gt的,就是我要去计算未匹配上的那些gt,IDFP同理是针对tracker的。这也就可以理解为什么上面fn要记录每一个gt的id数。下面减去了每一对gt-tracker匹配上的帧数,fn_mat[:num_gt_ids, :num_tracker_ids]中对应的就是没一对tracker-gt未匹配上的帧数,fp_mat同理。
fn_mat[:num_gt_ids, :num_tracker_ids] -= potential_matches_count
fp_mat[:num_gt_ids, :num_tracker_ids] -= potential_matches_count
pdb.set_trace()
#语言表达能力不太好,大家可以看一下我调试结果然后想象一下。
# Hungarian algorithm
match_rows, match_cols = linear_sum_assignment(fn_mat + fp_mat)#通过上面的解释,这里也不难理解,就是所有的tracker-gt对儿中,未匹配上(未匹配上的gt和未匹配上的tracker的和)最少的那些组合。这就完成了tracker-gt的分配问题。我认为就是找到重合度最高的tracker-gt对
#此时的矩阵左上是num_gt_ids*num_tracker_ids,右上是非对角元素为十的十次方的对角矩阵,左下同样,右下是全0,所以这里匹配上的就一定在左上,下半部分一定都是0,所以最后求和之后就相当于值考虑了左上角那部分。
# Accumulate basic statistics
res['IDFN'] = fn_mat[match_rows, match_cols].sum().astype(np.int)
res['IDFP'] = fp_mat[match_rows, match_cols].sum().astype(np.int)
res['IDTP'] = (gt_id_count.sum() - res['IDFN']).astype(np.int)
# Calculate final ID scores
res = self._compute_final_fields(res)
return res
目前我关注的比较多的就是MOTA以及IDF1这两个指标,最新的HOTA指标还没有仔细研究,以后如果用到了再更新。2021.8.10
参考文献:
- Luiten J, Osep A, Dendorfer P, et al. Hota: A higher order metric for evaluating multi-object tracking[J]. International journal of computer vision, 2021, 129(2): 548-578.
- Bernardin K, Stiefelhagen R. Evaluating multiple object tracking performance: the clear mot metrics[J]. EURASIP Journal on Image and Video Processing, 2008, 2008: 1-10.
- Ristani E , Solera F , Zou R S , et al. Performance Measures and a Data Set for Multi-Target, Multi-Camera Tracking[C]// European Conference on Computer Vision. Springer, Cham, 2016.
MOT官网评价指标源码:https://github.com/dendorferpatrick/MOTChallengeEvalKit