写在前面:
在目标检测问题中,好几处地方使用了阈值这个限制指标,主要有:
- NMS操作之前用到的置信度阈值a;
- NMS进行时用到的IoU阈值b;
- 计算某类别AP时,统计TP,FP个数前,用到置信度阈值c;
- 计算某类别AP时,统计TP,FP个数时,用到IoU阈值d。
以下对这4个阈值作详细说明。
预备知识:
在正文开始之前,说一下置信度confidence这个概念吧,IoU的话比较简单,没什么混淆的,目标检测当中不同论文对其定义几乎一样。对于置信度,不同论文对其定义是不同的,一般用到比较多的是分类置信度,分类置信度confidence是介于0和1(或100%)之间的数字,它描述模型认为此预测边界框包含某类别目标的概率,一般多分类的话,最后一层输出接softmax函数,会输出n个前景和1个背景的概率分数,预测框属于哪一类看哪个的分数最高。YOLOv1当中对于置信度定义与上面不同。
还有新的一些论文,在预测边界框时还预测了预测框与GT的IoU,以此IoU作为置信度分数,这种称为定位置信度,比如这篇文章(这里)。以下我们的说明就以置信度分数指的是预测框中对象为某一类别的概率来进行说明。
1. NMS进行时用到置信度阈值a和IoU阈值b
1.1 NMS介绍
检测器预测完结果,一张图像中会有很多预测框,预测的结果间可能存在高冗余(即同一个目标可能被预测多个矩形框),进行NMS可以过滤掉与同一检测对象中置信度分数最高的预测框IoU超过阈值(即上面所说的b)的其他预测框,即同一Ground Truth的多个预测结果我们只取置信度分数最高的那个预测结果,其余的我们都不要。个人觉得这个不是很合理,NMS时,置信度分高但是位置不够准的框可能会把置信度分低但是位置很准的框去掉。这使得原本定位准确的边界框会在迭代回归的过程中偏离目标,和我们的最终目标是冲突的,当然不是所有文章都用分类置信度排序来进行NMS,比如旷世有一篇论文,IoU-Net,预测结果会预测边界框与GT的IoU置信度,以IoU置信度排序来取代分类置信度排序,进行NMS,主要应用在对位置精确度要求较高的场合(文章地址)。YOLO当中的置信度综合了预测的对不对和预测的准不准这2个性能,个人感觉更合理一些。
1.2 NMS过程
NMS的思路是:对于一张图片中的每一个预测框来说,模型为其每一个类别都预测了一个置信度分数(一般多分类,模型输出后接softmax,每一个类别都得到了一个置信度分数,包括背景类)我们取置信度最高的那一个类别作为预测框中对象所属的类别。1. 首先我们将置信度分数低于置信度阈值a的所有预测框去掉 。2. 然后在同一张图片上,我们按照类别(除开背景类,因为背景类不需要进行NMS),将所有预测框按照置信度从高到低排序,将置信度最高的框作为我们要保留的此类别的第1个预测框,3. 然后按照顺序计算剩下其他预测框与其的IoU,4. 去掉与其IoU大于IoU阈值b的预测框(其实代码实现里是将这些要去掉的预测框其置信度分数置为0),5. 第一次迭代结束,我们已经剔除了与第一个框重合度较高的框。
接着从剩下的预测框中取置信度分数最高的检测框作为我们要保留的第2个预测框,进行第2次迭代。反复下去,我们就过滤掉此类别与同一GT重叠度较高的预测框了,然后对下一个类别处理,直至处理完所有的类别。
1.3 代码
import numpy as np
def nms(dets, thresh):
"""Pure Python NMS baseline."""
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]
scores = dets[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1] # 置信度从高到低排序
keep = []
while order.size > 0:
i = order[0] # 此类别中置信度最高的预测框的索引
keep.append(i) # 将其作为保留下来的第1个预测框
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (areas[i] + areas[order[1:]] - inter) # 计算其余预测框与置信度最高的预测框的IoU
inds = np.where(ovr <= thresh)[0] # 记录下第1个与其Iou<阈值的预测框,也就是与其Iou<阈值的预测框中置信度最高的
order = order[inds + 1] # 将与保留下来的第1个预测框Iou<阈值的预测框中置信度分数最高的预测框作为第2个要保留的
return keep # 所有经过NMS后保留下来的框
说明一下,上述代码中的dets是经过置信度阈值过滤后的检测结果,所以此代码中没有这步操作,此外上面的dets也是一张图片中某一类所有的预测框。额外,插一下,好多企业,CV岗面试的时候,很喜欢出这个题目,手写NMS,另外也有一些其他更为简单的写法,这里放一下,我看到写的很好的例子。(NMS的多种代码实现)
2. 计算mAP时用到的置信度阈值c和IoU阈值d
在测试时,我们通过我们的检测器预测出许多预测框,通过NMS我们去除掉了对同一GT重叠度较高的一些框,NMS之后,我们需要评估我们的检测器精度怎么样,一般用的比较多的一个指标就是mAP,平均准确度均值。在计算mAP前,我们需要计算出我们测试集中每一类的AP值。
拿单张图片来说吧,首先遍历图片中ground truth对象,然后提取我们要计算的某类别的gt objects,之后读取我们通过检测器检测出的这种类别的检测框(其他类别的先不管),接着过滤掉置信度分数低于置信度阈值的框(即所说的置信度阈值c),将剩下的检测框按置信度分数从高到低排序,最先判断置信度分数最高的检测框与gt bbox的IoU是否大于IoU阈值(即·上面所说的IoU阈值d),若IoU大于设定的IoU阈值即判断为TP,将此gt_bbox标记为已检测(后续的同一个GT的多余检测框都视为FP,这就是为什么先要按照置信度分数从高到低排序,置信度分数最高的检测框最先去与IoU阈值比较,若大于IoU阈值,视为TP,后续的同一个gt对象的检测框都视为FP),IoU小于阈值的,直接规划到FP中去。
3. 总结
- NMS用到的IoU阈值,是拿除保留的预测框外的其余预测框跟同一类别中置信度最高的预测框IoU与其作比较。
- 计算mAP用到的IoU阈值,是拿预测框与GT的IoU与其作比较。
- NMS的置信度阈值主要是为了过滤掉一些背景预测框(一般来说one stage算法用的较多,因其没有two stage产生ROI,背景框较多)。
- 计算mAP的置信度阈值主要是用来选取TOP N(置信度分数从高到低排名的前N个检测)的样本来统计TP,FP,FN,计算AP。
4. 理解:
AP计算方法: 设置一组固定的置信度阈值,比如常用的41点和11点,然后按照上面的方法求TP,FP,然后分别求每个置信度阈值下的precision=TP/(TP+FP),得到一组precision,对这组precison取平均就是AP