主要参考:
书籍《深度学习之PyTorch物体检测实战》
对于具体的某个物体来讲,我们可以从预测框与真实框的贴合程度来判
断检测的质量,通常使用IoU来量化贴合程度。
IoU的计算方式如上图所示,使用两个边框的交集与并集的比值,就可以得到IoU,公式如式(1-1)所示。显而易见,IoU的取值区间是[0,1],IoU值越大,表明两个框重合越好。
伪代码:
# 需要根据自己的代码进行修改
def iou(boxA, boxB):
# 计算重合部分的上、下、左、右4个边的值,注意最大最小函数的使用
left_max = max(boxA[0], boxB[0])
top_max = max(boxA[1], boxB[1])
right_min = min(boxA[2], boxB[2])
bottom_min = min(boxA[3], boxB[3])
# 计算重合部分的面积
inter =max(0,(right_min-left_max))* max(0,(bottom_min-top_max)
Sa = (boxA[2]-boxA[0])*(boxA[3]-boxA[1])
Sb = (boxB[2]-boxB[0])*(boxB[3]-boxB[1])
# 计算所有区域的面积并计算iou,如果是Python 2,则要增加浮点化操作
union = Sa+Sb-inter
iou = inter/union
return iou
对于IoU而言,通常会选取一个阈值,如0.5,来确定预测框是 正确的还是错误的
。
IoU>0.5 ————有效的检测
IoU<=0.5 ————无效的匹配
如下图中有两个杯子的标签,模型产生 了两个预测框。
由于图像中存在背景与物体
两种标签,预测框也分为正确与错误
,因此在评测时会产生以下4种样本。
正确检测框
预测框正确地与标签框匹配了,误检框
将背景预测成了物体,框与图中所有标签的IoU都不会超过0.5。漏检框
本来需要模型检测出的物体,模型 没有检测出。正确背景
本身是背景,模型也没有检测出来,通常不需要考虑
。mAP用来评价一 个模型的好坏,这里的AP指的是一个类别的检测精度,mAP则是多个类别的平均精度。
每个样本(图片)的预测值与标签值
1. 首先将所有的预测框按照得分从高到低进行排序(因为得分越高的边框其对于真实物体的概率往往越大)
2. 然后从高到低遍历预测框
对于遍历中的每一个预测框,计算其(预测框 Dets)与该图中同一类别的所有真实框(标签框GTs)的IoU
并选取拥有最大IoU的GT作为当前预测框的匹配对象。
如果该IoU小于阈值,则将当前的预测框标记为误检框FP
如果该IoU大于阈值,还要看对应的标签框GT是否被访问过。
如果已经被访问过(即前面已经有得分更高的预测框与该标签框对应了),即使现在的IoU大于阈值,也会被标记为FP。
如果没有被访问过,则将当前预测框Det标记为正确检测框TP,并将该GT标记为访问过,以防止后面还有预测框与其对应。
3. 在遍历完所有的预测框后,我们会得到每一个预测框的属性,即TP或FP。
召回率(Recall,R)
即当前一共检测出的标签框与所有标签框的比值
准确率(Precision,P)
即当前遍历过的预测框中,属于正确预测边框的比值
F1分数(F1 Score)
通常是统计学中用来衡量二分类模型精确度的一种指标。它同时兼顾了分类模型的准确率和召回率。
F1分数可以看作是模型精确率和召回率的一种调和平均(可以理解为P和R的调和平均数),它的最大值是1,最小值是0。
P-R曲线
遍历到每一个预测框时,都可以生成一个对应的P与R,这两个值可以组成一个点(R,P),将所有的点绘制成曲线,即形成了P-R曲线
然而,即使有了P-R曲线,评价模型仍然不直观,如果直接取曲线上的点,在哪里选取都不合适,因为召回率高的时候准确率会很低,准确率高的时候往往召回率很低。
AP代表了曲线的面积
,综合考量了不同召回率下的准确率,不会对P与R有任何偏好。每个类别的AP是相互独立
的,将每个类别的AP进行平均,即可得到mAP
。
严格意义上讲,还需要对曲线进行一定的修正,再进行AP计算。除了求面积的方式,还可 以使用11个不同召回率对应的准确率求平均的方式求AP。
伪代码:
假设当前经过标签数据与预测数据的加载,我们得到了下面两个变量:
for c in classes:
# 通过类别作为关键字,得到每个类别的预测、标签及总标签数
dects = det_boxes[c]
gt_class = gt_boxes[c]
npos = num_pos[c]
# 利用得分作为关键字,对预测框按照得分从高到低排序
dects = sorted(dects, key=lambda conf: conf[5], reverse=True)
# 设置两个与预测边框长度相同的列表,标记是True Positive还是False Positive
TP = np.zeros(len(dects))
FP = np.zeros(len(dects))
# 对某一个类别的预测框进行遍历
for d in range(len(dects)):
# 将IoU默认置为最低
iouMax = sys.float_info.min
# 遍历与预测框同一图像中的同一类别的标签,计算IoU
if dects[d][-1] in gt_class:
for j in range(len(gt_class[dects[d][-1]])):
iou = Evaluator.iou(dects[d][:4], gt_class[dects[d][-1]][j][:4])
if iou > iouMax:
iouMax = iou
jmax = j # 记录与预测有最大IoU的标签
# 如果最大IoU大于阈值,并且没有被匹配过,则赋予TP
if iouMax >= cfg['iouThreshold']:
if gt_class[dects[d][-1]][jmax][4] == 0:
TP[d] = 1
gt_class[dects[d][-1]][jmax][4] = 1 # 标记为匹配过
# 如果被匹配过,赋予FP
else:
FP[d] = 1
# 如果最大IoU没有超过阈值,赋予FP
else:
FP[d] = 1
# 如果对应图像中没有该类别的标签,赋予FP
else:
FP[d] = 1
# 利用NumPy的cumsum()函数,计算累计的FP与TP
acc_FP = np.cumsum(FP)
acc_TP = np.cumsum(TP)
rec = acc_TP / npos # 得到每个点的Recall
prec = np.divide(acc_TP, (acc_FP + acc_TP)) # 得到每个点的Precision
# 利用Recall与Precision进一步计算得到AP
[ap, mpre, mrec, ii] = Evaluator.CalculateAveragePrecision(rec, prec)