博主最近也有在接触目标检测相关的相关研究,其中有一个环节博主卡了很久,那就是AP的计算过程。相信大家都看到过很多关于AP的介绍,但是都很空泛,而且大家的答案都是千篇一律,我们只能看个大概,但是一到看代码或者是要自己写代码的时候具体怎么操作就不会了。博主在学习的时候看到一篇英文的关于AP的介绍十分的详细,最最最关键的是它还有例子。这里放上原文的链接:https://github.com/rafaelpadilla/Object-Detection-Metrics 然后博主在这里结合自己的理解给大家通俗易懂的讲讲AP到底怎么算不是想其他资料一样讲写套话,看的大家云里雾里的。
一、IOU
简单一点说呢就是我们要求两个框的IOU就是用两个框的重合部分除以连个框的并集,可以看上面的第二个公式就很明了了,计算的时候要注意两个框的并集的面积等于二者的面积之和减去二者的交集的面积。
二、True Positive, False Positive, False Negative and True Negative
True Positive (TP):一个正确的定位结果,就是你预测的框和我们的groudtruth之间的IOU是可以大于我们规定的阈值的,我们一般取这个阈值都是0.5
False Positive (FP):就是一个错误的结果,就是你预测的这个框和groundtruth的IOU是小于阈值的
False Negative (FN): 就是我们本来这里有一个物体的,所以我们这个地方应该有个框,但是你没预测出来,那么这个groundtruth对于你的模型来讲就是一个FN
True Negative (TN):这个就是我们这个groundtru找到了一个和它的IOU大于阈值的预测框,那么对于这个ground就是认为被成功的检测出来了。
三、Precision和Recall
首先是precision就是用TP除以总的detection的数目(你预测处来的框的数目),注意我们在计算AP的时候要首先要计算precision。在我们实际计算的时候是一个序列,如果我们一共有N个detections那么它就是一个长度维N的序列,我们计算Precision的时候是针对某一个类别的,首先确定一个类别,然后对这个类别的所有的检测结果按他们得到的分数也就是confidence进行从大大小排序,然后我们每次取一个结果出来不管这个detection是TP还是FP,all detections都要加1也就是在实际计算的过程中上述的公式中的all detections是一个递增变化的数,每次取一个框就加1,然后每次取一个框出来如果这个框是TP那么TP的数目就加1,然后TP和all detections比值作为当前的precision,这样取完所有的框就有了N个precision组成的一个序列。这个地方如果没有看太懂没有关系,下面我们会举一个简单的例子来解释具体的操作的。然后我们来看下什么是recall.
与precision对应的recall也是一个长度维N的序列,其中N是我们得到detections的数目,我们没取出来一个框如果这个框是TP,那么TP的数目就加1,然后我们用当前的TP数目来除以all ground truths来得到当前的recall,这里要注意all ground truths就是我们这数据集中所有的这个类别的目标的数目,对于一个特定的数据集的一个特定的类别这是一个定值。所以我么把所有的框都取完之后我们就得到两个长度为N的序列,一个是precision一个是recall。这两个数据分别表示了我们的定位效果的两个方面,precision表示的是我们的定位的准确率,就是我们得到了这么多框到底有多少个是好的,比如我们一共有5个目标你给出了2000个框,好吧这2000个框中都包含了这5个目标,但是其他的1995个框都是没用的,这样无疑是很蠢的,因为只要我们取的框足够多我们总是可以把这些东西找的出来的,但这样就没有意义了。那么recall怎么解释呢,recall是表征我们到底找到了多少个目标物的量,比如我们一共有200个目标,我们最后给出的detections是20个而且这20个里面竟然有19个都是TP那么无疑我们的precision是很高的有95%,但是我们发现我们还有181个目标没有找到,也就是我们的recall是很低的,这肯定不行啊,比如我么要在雷达里面找导弹,一共12个导弹你就找到3个,好了够你死好几回了。所以我们可以看到无论是precision还是recall在目标检测中都是很重要的,但是我们发现我们多取一些框那么recall就可能比较的高,但是这样就可能会使得precision降低,所以在模型如果不是特别特别精确的情景下,这两个量是相互矛盾的,所以我们对于不同的场景可能就必须要选择到底要照顾那一边。那么对于普适的情况下我们就要综合考虑二者的取一个平衡,也就有了我们的AP这个指标。好吧下面来介绍AP的计算过程。
四、AP的计算
AP的计算有两套标准,一套是07年的标准,一套是后面的标准我们一般现在使用的都是新的标准。
第一种是根据recall将我们的得到的那个序列分成11段
就是首先我们取出来所有的recall在0到0.1这个区间内的所有的recall以及对应的那些precision,然后我们从这些precision中找到最大值作为这个区间内precision的代表,然后我们在剩下的10个区间内也分别找到最大的precision,最后把这11个数求均值就作为我们的AP。
好了我们可以看到我们上面只是取了11个recall的区间,这样做其实也可以但是总是感觉太粗糙了些,所以后面大家就提出了一种新的AP的计算方法,我们可以把所有的recall取值都取一遍,仔细观察下我们会发现其实在recall序列中有些位置都是相等的,因为只有我们取到了一个TP的时候recall的数值才会发生变化。
好了我们结合上面的公式来看看到底怎么算,首先我们取到了第n种recal的取值,然后我们我们在往后看直到发现出现了不同的recall我们然后我们在这个区间里找到最大的precision,然后用这个最大的precision和这两个recall相乘作为这段区间的AP然后我们遍历所有的区间然后把每段的AP加起来就得到了最后的AP,好吧,如果还是没有看懂我们看个例子,看完这个例子你一定就懂了。
首先我们假设这个数据集中的某个类别一共有7张图像,然后这些图像中绿色的框是ground truths然后红色的框是我们得到的detections。
然后我们首先我们按分数来给这下框排个序就像下面这样然后我们要看下这些框到底是TP还是FP,这写怎么判断我们上都有解释过,但是这里有一个问题就是对于同一个ground truth我们可能同时有多个框都和它的IOU都大于阈值,那么我么把它们都当做TP吗?肯定不是啊,这个时候我们就从这些框里面选一个和我们的ground truth重合率最大的一个框作为TP剩下的作为FP,注意这里是重合率大的留下而不是confidence大的留下。其实我感觉这样也不太合理重合率大的作为TP没问题但是小的作为FP就不合理了,应该直接跳过这个框就好了不是TP也不是FP。好吧反正就是这么算。
然后得到了每个框是TP还是FP之后我们来计算 precision和recal序列,我们先看下结果好吧
然后怎么算我们已经说过了现在我们在简单提下看最前面的两个框。取到第一个框的时候我们的all detections就是1正好我我们这个框就是TP,所以我们这里的precision就是1咯,然后在取一个框,但是这个框是FP,所有我们的TP数目还是1,但是我们的all detections就变成2了所以我们的precision这个时候变成了0.5,然后我们看下recall我们一共有15个groundtrus然后我们的recall只有在我们取到了一个TP之后才会变化对吧,好了这个很明显了我不想写了。
恩然后有了recall和precision序列了然后我们怎么搞:
首先是11点法:这个就不解释了前面有解释过结合着我们上面的那个表大家肯定看得懂的。
然后是我们现在常用的算法:就是每个recall区间做相应的计算,即每个recall的区间内我们只取这个区间内precision的最大值然后和这个区间的长度做乘积,所以最后体现出来就是一系列的矩形的面积,还是以上面的那个例子为例,我们一共有recall一共变化了7次,我们就有7个recall区间要做计算,然后实际我们计算的时候人为的要把这个曲线变化成单调递减的,也就是对现有的precision序列要做一些处理,后来有些同学留言说在AP的计算这块有疑问,我最初写的时候也确实有写问题,有些地方也没有交代清楚,所以现在在这里重新结合代码写了一下。我们先放上代码:
def voc_ap(rec, prec, use_07_metric=False):
""" ap = voc_ap(rec, prec, [use_07_metric])
Compute VOC AP given precision and recall.
If use_07_metric is true, uses the
VOC 07 11 point method (default:False).
"""
if use_07_metric:
# 11 point metric
ap = 0.
for t in np.arange(0., 1.1, 0.1):
if np.sum(rec >= t) == 0:
p = 0
else:
p = np.max(prec[rec >= t])
ap = ap + p / 11.
else:
# correct AP calculation
# first append sentinel values at the end
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))
print(mpre)
# compute the precision envelope
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
# to calculate area under PR curve, look for points
# where X axis (recall) changes value
i = np.where(mrec[1:] != mrec[:-1])[0]
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
print(mpre)
return ap
这个代码在任何一个目标检测的代码里应该都可以找到的,下面我们看下这个代码做了哪些事情:首先我们输入的序列是我们得到的:
rec:[0.0666,0.0666,0.1333,0.1333,0.1333,0.1333,0.1333,0.1333,0.1333,0.2,0.2,0.2666,0.3333 ,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4666,0.4666],
然后是我们的pre:[1,0.5,0.6666,0.5,0.4,0.3333,0.2857,0.25,0.2222,0.3,0.2727,0.3333,0.3846,0.4285,0.4,0.375,0.3529, 0.3333,0.3157,0.3,0.2857,0.2727,0.3043,0.2916]。
代码里首先是判断是不是用11点的方法,我们这里不用所以跳转到下面的代码:
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))
那么这一步呢就是相当于把我们的recall开放的区间给补上了,补成了闭合的区间。mpre也是做了对应的补偿。
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
那这一步呢就是在做我们上面说到的人为地把这个pre-rec曲线变成单调递减的,为什么这么做呢,我也不太清楚。我们这里可以看到他就是在做一个比较,就是后一项要是比前一项大的话那么就把这个大的值赋值给前一项。做完这一步后的pre就变成了:[1. 1. 0.6666 0.6666 0.5 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4 0.375 0.3529 0.3333 0.3157 0.3043 0.3043 0.3043 0.3043 0.2916 0. ]我们发现与之前的[0. 1. 0.5 0.6666 0.5 0.4 0.3333 0.2857 0.25 0.2222 0.3 0.2727 0.3333 0.3846 0.4285 0.4 0.375 0.3529 0.3333 0.3157 0.3 0.2857 0.2727 0.3043 0.2916 0. ]对比那些在0.4285之前出现的但是比0.4285小的数值都被替换成了0.4285.,相当于强行把这个曲线给填的鼓了起来。
做完这个操作后,我们就要按rec区间去计算ap了,准确来说是用rec的区间长度乘以这个区间上的最大的pre(注意这里是处理后的pre)。代码实现起来就是:
i = np.where(mrec[1:] != mrec[:-1])[0]#获取rec区间变化的点
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])#(mrec[i + 1] - mrec[i])这里得到rec区间的
#长度,mpre[i + 1]这里是这个区间上对应的pre数值(处理后的)
我们这里分别输出一下:
print((mrec[i + 1] - mrec[i]))
print(mpre[i + 1])
结果是:[0.0666 0.0667 0.0667 0.0666 0.0667 0.0667 0.0666 0.5334]和[1. 0.6666 0.4285 0.4285 0.4285 0.4285 0.3043 0. ]我们看一下首先是0.0666他代表第一个rec0.0666到0的区间长度,然后呢这个区间上出现的最大的pre是1,第二项0.0667代表0.0666到0.1333的区间长度是0.0667,在rec取0.1333这段区间上最大额pre是0.4285(变化后的),后面的以此类推这块还有疑问的建议结合这上面的那个表和pre处理后的序列看一下哈,。为啥pre输出的最后一项是0呢,是因为这段区间实际上recall是没有达到过的我们最大的rec也就只到了0.4666就结束了。
那么对于这个例子就是通过上面的就可以算出来了,其实还是蛮直观的。恩到这里我们就把怎么算AP讲完了。
那么mAP又是啥呢,其实m就是mean均值的意思我们AP不是算个一个类别的吗,mAP就是把所有的类别的AP都算出来然后求个均值就可以了,然后至于代码github上面或者你看faster-RCNN这些都有现成的,恩希望大家看完这个就能理解AP咋算的啦,有不懂的地方好好看看这个例子肯定是可以看懂的。
————————————————
版权声明:本文为CSDN博主「修木」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35916487/article/details/89076570