1、COCO目标检测比赛中的模型评价指标介绍!_dulingwen的博客-CSDN博客
对于COCO格式的数据检测,我们主要分为不同的IoU阈值,不同的面积范围,单张图片的最大检测数量。在这些不同的参数下,会得到不同的AP与AR。
所以在这个类中,我们需要指定这些参数的数值范围,具体可看下面贴出的代码。
COCOeval类是COCO api中最复杂的类了,主要包括以下函数

创建COCOeval这个类的时候,我们需要传入两个COCO 类别的instance,一个是gt对应的COCO,一个是det对应的COCO,用来初始化det的anno和gt的anno,关于COCO的类别,那么关于COCO类,可以参考:
MSCOCO api详解 —— Keypoints - 知乎
COCO API-COCO模块在det中的应用_张学渣的博客-CSDN博客
__init__ 函数中需要注意:
self.params = Params(iouType=iouType) # parameters
它用了一个外部类,将评估过程中重要的参数隐式的初始化在COCOeval内你敢信?并且这个self.params 在后面的操作里,有多次的反复赋值、修改的操作。因此我们在使用不同格式的keypoints时,这里是也是唯一我们需要重载COCOeval的成员方法。
设置GT中的ignore flag,初始化空evaluation结果之类的一些常规操作,主要是一下操作
- self._gts = defaultdict(list) # gt for evaluation
- self._dts = defaultdict(list) # dt for evaluation
- for gt in gts:
- self._gts[gt['image_id'], gt['category_id']].append(gt)
- for dt in dts:
- self._dts[dt['image_id'], dt['category_id']].append(dt)
- self.evalImgs = defaultdict(list) # per-image per-category evaluation results
- self.eval = {} # accumulated evaluation results
根据image_id和cat_id计算这张图片里 cat_id 的所有GT、DT的iou矩阵,主要用于bbox和segmentation;
根据image_id和cat_id计算这张图片里所有GT、DT的Oks矩阵,也就是Sec 1.2.里OKS的计算源码出处。这里OKS矩阵的维度是 ![]()
根据image_id和cat_id,以及evaluate函数计算得到的iou矩阵,计算在给定的area rang和maxDet下不同iouThreshold下成功的匹配/不成功的匹配矩阵。
1、对于gt框,首先在所有框中挑出imgId, catId的gt框和det框,并根据面积范围的限制设置该gt框是否应该ignore,并把满足面积范围的gt框排在前面,还设置了该gt框的iscrowd标签,
2、对于dt框,把score更高的det框也排在前面,
3、最后从self.ious中挑出imgId, catId的ious矩阵,注意这个最后的ious阵也是根据是否满足面积范围排序了(gt根据是否满足面积范围排序了)
4、self.ious是有n*c个key的字典,n代表图片总数,c代表数据的类别数,每个key所对应value是一个二维矩阵,每一列代表某个gt框与所有det框的交并比。这个字典是之前已经计算好的。
这个方法传入固定的img_id,cat_id,aRng,maxDet,我们可以得到对应的img在特定类别,特定面积阈值,特定最大检测数下的检测结果。
把这个检测结果按照K,A,M的顺序堆叠,可以得到self.evalImgs这个list,这个list包含了所有图片在所有IoU阈值,面积阈值,最大检测数下的所有检测结果。
具体过程如下:
- gt = self._gts[imgId,catId]
- dt = self._dts[imgId,catId]
- gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
- gt = [gt[i] for i in gtind]
- dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
- dt = [dt[i] for i in dtind[0:maxDet]]
- iscrowd = [int(o['iscrowd']) for o in gt]
- # load computed ious
- ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
进行匹配,在给定的阈值下,各阈值互不影响:
m代表dt匹配的最好的gt的索引下标
对每一个dt,寻找与之最为匹配的gt,
返回的重要数据包括:
然后我们就要开始介绍COCOeval的评估三板斧了
这里evaluate主要做了以下几件事:
这里有一个需要理解好的概念,就是det ignore,可以理解为可以忽略的检测,只有一个检测不是需要忽略的检测的时候,它才会被计入precision和recall。那么什么样的det是可以被ignore的呢:

上图是coco中对两中比较特别的情况的判断方法。coco是将每一类当成二分类进行匹配的,其他类也算作background。即下面三条:
而在mmdet的计算混淆矩阵的方法中,并不是把每一类的gt和det拎出来再匹配,而且也没有去除重复的操作,即左边这种情况下,只要iou超过阈值,一个gt会和这两个det都匹配,导致最终的tp值大于了真实的gt数,如果gt和det类别不相同,也就增加了其他类的fp值。在右边的情况中也是没有去除重复,同样会增加两个tp,若类别不相同,那会增加其他类的fp。
先看懂这篇文章,否者这个函数很难理解:cocoapi如何计算map_「已注销」的博客-CSDN博客_coco map计算
cocoEval.evaluate() 只是每幅图的det和gt做了匹配,并将结果存在了self.evalImgs中。计算tp等指标需要cocoEval.accumulate()。
上面我们通过evaluate得到了每一个图片、每一个类别在 iouThreshold、area range、maxDet下的结果,那么在这一步我们需要将它们在整个数据集上汇总结果了。
最终返回的是所有图片在不同IoU阈值、不同AR、不同类别、不同面积阈值、不同最大检测数下的Ap与AR,以numpy数组的返回,即precision(T,R,K,A,M) recall(T,K,A,M)。
有一个地方需要注意:
在算平均精度的一般情况下,如果总共有100个样本,我们数量1开始计算精度和召回率以计算平均精度AP,通常的规律是:随着样本数量的增加,精度慢慢下降,召回率慢慢提高。这个循环解决的是上述规律出现反常的情况。如:检测前50个样本的精度为0.6,检测前51个样本的精度为0.7,则将检测前50个样本的精度置为0.7,这样得出来的数组是个单调递减数组
为什么要这样?
参考:AP,mAP计算详解(代码全解) - 知乎
它的图像描述的很明白,主要还是方便平均精度的计算
作者在这里把我们前面提到的参数Params作为一个函数的输入,似乎本意是希望能够更方便的在更多维度上统计结果,但是偏偏前面的evaluate又是在默认的参数上进行的,确实有一种还未完全实现功能。
针对上述accumulate获得的precision、recall矩阵,在不同的维度上进行统计,然后再呈现结果。
函数内部会根据传入的具体的IoU阈值,面积阈值,最大检测数的值返回上述precision和recall中对应维的检测结果,我们就也可以自定义形式返回我们想要的各种参数下的AP与AR啦。
这里没有什么特别的需要注意的。我们常看到的mAP也就是出自于此。
1、COCO API-深入解析cocoeval在det中的应用_张学渣的博客-CSDN博客