1.目标检测
目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标,并确定它们的类别和位置。
目标检测的位置信息一般由两种格式(以图片左上角为原点(0,0)):
1、极坐标表示:(xmin, ymin, xmax, ymax)
- xmin,ymin:x,y坐标的最小值
- xmin,ymin:x,y坐标的最大值
2、中心点坐标:(x_center, y_center, w, h)
- x_center, y_center:目标检测框的中心点坐标
- w,h:目标检测框的宽、高
那么按中心坐标如下:
2.常用的评价指标
2.1 Iou
在目标检测算法中,IoU(intersection over union,交并比)是目标检测算法中用来评价2个矩形框之间相似度的指标:
IoU = 两个矩形框相交的面积 / 两个矩形框相并的面积
通过一个例子看下在目标检测中的应用:
其中上图蓝色框框为检测结果,红色框框为真实标注。
一般我们会设置应该阈值,一旦IoU大于阈值,我们就认为检测到目标物体。
代码实现:
import numpy as np
def Iou(box1,box2,wh=False):
# 判断是否按中心坐标
if wh == False:
# 使用极坐标
xmin1,ymin1,xmax1,ymax1 = box1
xmin2,ymin2,xmax2,ymax2 = box2
else:
# 使用中心坐标
xmin1,ymin1 = int(box1[0]-box1[2]/2),int(box1[1]-box1[3]/2)
xmax1,ymax1 = int(box1[0]+box1[2]/2),int(box1[1]+box1[3]/2)
xmin2,ymin2 = int(box2[0]-box2[2]/2),int(box2[1]-box2[3]/2)
xmax2,ymax2 = int(box2[0]+box2[2]/2),int(box2[1]+box2[3]/2)
# 获取交集的左上和右下的坐标
xx1 = np.max([xmin1,xmin2])
yy1 = np.max([ymin1,ymin2])
xx2 = np.min([xmax1,xmax2])
yy2 = np.min([ymax1,ymax2])
# 计算两个矩形的面积
area1 = (xmax1-xmin1) * (ymax1-ymin1)
area2 = (xmax2-xmin2) * (ymax2-ymin2)
# 计算交集的面积
inter_area = (np.max([0.0,xx2-xx1]))*(np.max([0.0,yy2-yy1]))
# 计算Iou
iou = inter_area/(area2+area1-inter_area+1e-6)
return iou
2.2 mAP
目标检测问题中的每个图片都可能包含一些不同类别的物体,需要评估模型的物体分类和定位性能。因此,用于图像分类问题的标准指标precision不能直接应用于此。 在目标检测中,mAP是主要的衡量指标。
mAP是多个分类任务的AP的平均值,而AP(average precision)是PR曲线下的面积,所以在介绍mAP之前我们要先得到PR曲线。
TP、FP、FN、TN
- TP:Iou大于阈值的检测框数量
- FP:Iou小于阈值的检测框数量,或者是检测到同一个GT(Ground Truth,真实框)的多余检测框的数量
- FN:没有被检测到的GT的数量
- TN:在mAP没有用到,无实际意义
查准率、查全率
- 查准率:TP/(TP+FP)
- 查全率:TP/(TP+NP)
查全率(Recall)做横坐标,查准率(Precision)做纵坐标
AP 是计算某一类 P-R 曲线下的面积,mAP 则是计算所有类别 P-R 曲线下面积的平均值。
使用例子帮助理解
假设我们有 7 张图片(Images1-Image7),这些图片有 15 个目标(绿色的框,GT 的数量,上文提及的 all ground truths)以及 24 个预测边框(红色的框,A-Y 编号表示,并且有一个置信度值):
根据上图以及说明,我们可以列出以下表格,其中 Images 代表图片的编号,Detections 代表预测边框的编号,Confidences 代表预测边框的置信度,TP or FP 代表预测的边框是标记为 TP 还是 FP(认为预测边框与 GT 的 IOU 值大于等于 0.3 就标记为 TP;若一个 GT 有多个预测边框,则认为 IOU 最大且大于等于 0.3 的预测框标记为 TP,其他的标记为 FP,即一个 GT 只能有一个预测框标记为 TP),这里的 0.3 是随机取的一个值。
因为此时的到的只是一组固定P、R,不能绘制P-R 曲线,以此我们引入累加的概念。根据置信度从大到小排序所有的预测框,然后就可以计算 Precision 和 Recall 的值,见下表。
- 每一行的Precision = ACCTP/(ACCTP+ACCFP):比如标号为10的Precision=3/(7+3)=0.3
- 每一行的Recall = ACCTP/(GT):比如标号为10的Recall=3/15=0.2
以此类推,按Confidences算出所有预测框的Precision和Recall
然后就可以绘制出 P-R 曲线
得到 P-R 曲线就可以计算 AP(P-R 曲线下的面积),要计算 P-R 下方的面积,有两种方法:
在VOC2010以前,只需要选取当Recall >= 0, 0.1, 0.2, ..., 1共11个点时的Precision最大值,然后AP就是这11个Precision的平均值,取 11 个点 [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] 的插值所得
当Recall=0.2时,>=0.2的Precision最大值为0.4,故为0.4,>0.5以后Recall都为0
这时我们得到一个类别的AP结果:
在VOC2010及以后,需要针对每一个不同的Recall值(包括0和1),选取其大于等于这些Recall值时的Precision最大值,如下图所示:
然后计算PR曲线下面积作为AP值:
计算mAP就是将所有类别的相加再除以类别个数。
3.NMS(非极大值抑制)
非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素。我们在检测一个真实框时,可能出现很多备选测试框,这时我们选择某一指标最大的窗口,并且抑制那些分数低的窗口。
在目标检测中,NMS的目的就是要去除冗余的检测框,保留最好的一个,如下图所示:
NMS的原理是对于预测框的列表B及其对应的置信度S,选择具有最大score的检测框M,将其从B集合中移除并加入到最终的检测结果D中.通常将B中剩余检测框中与M的IoU大于阈值Nt的框从B中移除.重复这个过程,直到B为空。
使用流程如下图所示:
- 首先是检测出一系列的检测框
- 将检测框按照类别进行分类
- 对同一类别的检测框应用NMS获取最终的检测结果
通过一个例子看些NMS的使用方法,假设定位车辆,算法就找出了一系列的矩形框,我们需要判别哪些矩形框是没用的,需要使用NMS的方法来实现。
假设现在检测窗口有:A、B、C、D、E 5个候选框,接下来进行迭代计算:
- 第一轮:因为B是得分最高的,与B的IoU>0.5删除。A,CDE中现在与B计算IoU,DE结果>0.5,剔除DE,B作为一个预测结果,有个检测框留下B,放入集合
- 第二轮:A的得分最高,与A计算IoU,C的结果>0.5,剔除C,A作为一个结果
最终结果为在这个5个中检测出了两个目标为A和B。
代码实现如下:
def nms(boxes,score,threshold):
if len(boxes)==0:
return [],[]
# 类型转换
boxes = np.array(boxes)
score = np.array(score)
# 获取坐标
x1 = boxes[:,0]
y1 = boxes[:,1]
x2 = boxes[:,2]
y2 = boxes[:,3]
# 计算面积
areas = (x2-x1)*(y2-y1)
picked_boxed = []
picked_score = []
#按score排序,从小到大,返回索引值
order = np.argsort(score)
while order.size>0:
# 获取score最大值的索引
index = order[-1]
picked_boxed.append(boxes[index])
picked_score.append(score[index])
# 计算交集的面积
x11 = np.maximum(x1[index],x1[order[:-1]])
y11 = np.maximum(y1[index],y1[order[:-1]])
x22 = np.minimum(x2[index],x2[order[:-1]])
y22 = np.minimum(y2[index],y2[order[:-1]])
inter_area = np.maximum(0.0,x22-x11)*np.maximum(0.0,y22-y11)
keep_boxes = np.where(iou<threshold)
order = order[keep_boxes]
return picked_boxed,picked_score
假设有检测结果如下:
bounding = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.65, 0.8]
threshold = 0.3
picked_boxes, picked_score = nms(bounding, confidence_score, threshold)
print('阈值threshold为:', threshold)
print('NMS后得到的bbox是:', picked_boxes)
print('NMS后得到的bbox的confidences是:', picked_score)
返回结果:
阈值threshold为: 0.3
NMS后得到的bbox是: [array([187, 82, 337, 317])]
NMS后得到的bbox的confidences是: [0.9]