本篇博文是笔者结合百度paddle公开课的一个AI识虫项目对YOLOV3算法做的一个解析
完整项目可参考https://aistudio.baidu.com/aistudio/projectdetail/250211
相信有一定基础的深度学习开发者经常能听到目标检测这个词,那什么任务是目标检测呢。
简单地来说目标检测就是让计算机识别出图片里面对应的物体,并标上边界框
对于分类任务,我只需要识别这种图片是不是动物的图片即可
而对于目标检测任务,我并不是对整张图片做一个分类,而是对图片里的部分区域做一个分类,并绘制一个目标框标记物体
既然图片分类是对整张图片进行分类,我们能不能从原图里“抠”下不同区域的图片进行分类呢?
答案当然是可以的,而这也是最初目标检测算法的思路
我们将目标检测任务进行拆分成两个任务,分别是产生候选区域, 对候选区域进行分类。而这也引入了算法的区别,我们常听到的一阶段目标检测算法,就是用一个网络同时产生候选区域并进行预测。而二阶段目标检测算法,则是分别进行候选区域产生和分类任务。
最初产生的候选区域方法比较“笨”,更准确地来说应该称其为穷举法,也就是将图片中每个像素都遍历一遍,再遍历其右下方的像素。两个像素就能确定一个区域,穷举法的好处就是思想简单,产生的候选区域都遍布整张图片,但它带来的计算量实在是过于庞大
假设图片长宽分别为W, H
则其候选区域的数量约为: (WWH*H)/4
这种计算量放在当今的硬件条件上也是很不现实的。
当今图像分类已经发展地较为成熟,目标检测的工作更多的是放在如何更smart地产生候选区域上面
其中目标检测算法中具有代表性的就是R-CNN系列,SSD,YOLO这几种经典算法
其中YOLO算法是较为常用的一种算法,它推理速度快的同时也能保证一定的精度
我们对目标进行标注的时候采用的就是边界框, 也就是bounding-box。
该图片对其中的人物进行了标注,其中红色框就是边界框
而我们有两种格式来表示一个边界框
第一种就是xywh格式,其中xy是边界框的中心点xy坐标,wh分别是图片的宽度,高度
第二种则是xyxy格式,其中前两个xy是左上角的xy坐标, 后两个xy则是右下角的xy坐标
另外要注意的是,在图像中xy坐标系可能有些许区别,x坐标与我们数学中的x坐标是一样的,但是y坐标则是在最上方为0,从上到下依次递增
目标检测中常说的Anchor box指的就是锚框
它不是真实存在的,是人们假想出来的一种边界框
我们只需给定框的缩放尺寸,和框的中心位置,就能产生一系列不同大小的锚框
通常锚框在目标区域内就是候选区域,我们会检测锚框内是否含有目标物体,再进一步预测类别。
需要注定的是,锚框的中心点和尺度缩放是我们预先设定好的,所以它并不能很好地真正去逼近真实边界框。因此我们的任务就是对锚框进行微调,至于怎么微调,微调的幅度,这些就是网络需要学习的。
在目标检测中,我们使用交并比来对预测框是否很好覆盖真实框进行评估
IOU实质上是交并比,就是将图片的交集比上并集。该值越接近1,说明两个图片重合的区域越大
我们前面说过框的表示有xywh和xyxy两种格式,这里我贴上两种格式计算IOU的python代码
def box_iou_xyxy(box1, box2):
"""
计算IOU
:param box1: 框1 的左上角坐标和右下角坐标
:param box2: 框2 的左上角坐标和右下角坐标
:return: 交并比,即交集比上并集
"""
x1min, y1min, x1max, y1max = box1[0], box1[1], box1[2], box1[3]
x2min, y2min, x2max, y2max = box2[0], box2[1], box2[2], box2[3]
# 计算box1的面积
s1 = (x1max -x1min + 1.) * (y1max - y1min + 1.)
# 计算box2的面积
s2 = (x2max - x2min + 1.) * (y2max - y2min + 1.)
# 计算相交矩形框的坐标
# 由于图形坐标都是从上到下,从左到右进行递增
# 因此相交矩形的左上角x坐标应该用max计算
xmin = np.maximum(x1min, x2min)
ymin = np.maximum(y1min, y2min)
xmax = np.minimum(x1max, x2max)
ymax = np.minimum(y1max, y2max)
# 计算相交矩形的高度和宽度
inter_h = np.maximum(ymax - ymin + 1., 0.)
inter_w = np.maximum(xmax - xmin + 1., 0.)
intersection = inter_h * inter_w
# 计算相并面积
union = s1 + s2 - intersection
# 计算IOU,IOU = UNION / INTERSECTION
iou = union / intersection
return iou
def box_iou_xywh(box1, box2):
"""
以xywh形式计算IOU
:param box1:
:param box2:
:return:
"""
x1min, y1min = box1[0] - box1[2]/2.0, box1[1] - box1[3]/2.0
x1max, y1max = box1[0] + box1[2]/2.0, box1[1] + box1[3]/2.0
s1 = box1[2] * box1[3]
x2min, y2min = box2[0] - box2[2]/2.0, box2[1] - box2[3]/2.0
x2max, y2max = box2[0] + box2[2]/2.0, box2[1] + box2[3]/2.0
s2 = box2[2] * box2[3]
xmin = np.maximum(x1min, x2min)
ymin = np.maximum(y1min, y2min)
xmax = np.minimum(x1max, x2max)
ymax = np.minimum(y1max, y2max)
inter_h = np.maximum(ymax - ymin, 0.)
inter_w = np.maximum(xmax - xmin, 0.)
intersection = inter_h * inter_w
union = s1 + s2 - intersection
iou = intersection / union
return iou
它分两条路线,第一条就是经过卷积网络提取图片的特征, 第二条就是对图像划分成一个个小方块,再在上面生成不同尺度的锚框,最后标注预测框,预测框再与卷积出来的特征图结合起来输出结果
YOLO并不是以每个像素点作为基本单位,而是选取一个小尺寸的区域作为一个单位
比如大小为640 x 480的图片,YOLO以32x32的区域作为一个区域,那整张图片被分为20 x 15 的区域
然后再以每个区域的中心点生成不同尺度的锚框
接下来是生成预测框
我们前面有说过预测框是在锚框的基础上进行微调
具体计算公式如下,这里预测框锚框格式都是xywh
其中bx by bh bw分别是预测框的中心点x,y坐标,高度,宽度
σ(x)是Sigmoid函数
ph和pw是预先设定好的锚框大小,在模型中算是一个超参数
sigmoid和exp这两个函数输出都是正值,并且sigmoid函数输出是在0-1之间,能保证对中心点调整还是在我们划分好的小区域内部,不需要再后续网络输出做一个输出值的检查截断
对于候选区域,我们需要标注以下信息
1.我们的候选区域是否含有目标物体?我们使用标签objectness来表示,objectness=1 表示是正类,含有目标物体,反之则没有
2. 我们的候选区域对应的预测框是什么?即上面我们需要知道的tx ty tw th 是什么
3. 锚框包含的物体类别是什么?即对应的label
YOLO里面设置每个真实框只对应一个正确的预测框,即IOU交并比最大的那个预测框,但有些预测框可能跟真实框的交并比也很大,但这时候作为负样本扔进损失函数计算是不合适的,YOLO里面设定了一个阈值iou_threshold=0.7,即预测框与真实框的交并比大于这个阈值,但又不是最大的那个。将其objectness标签设置为-1,不参与后续损失函数的计算
接下来是处理tx ty tw th的问题,如果我们要想知道应该将这四个变量设置为什么值的时候,预测框与真实框重合,直接代入公式
其中gtx gty gtw gth是真实框的xywh
由于sigmoid反函数不好计算,因此回归目标我们换了种形式
网络通过不断学习,以达到逼近dx dy tw th
YOLO算法的一个巧妙之处就在于这里,将目标检测问题转化为一个回归问题