yolo设计理念
整体来看,Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测,首先将输入图片resize到448x448,然后送入CNN网络,最后处理网络预测结果得到检测的目标。其速度更快,而且Yolo的训练过程也是端到端的。
与滑动窗口不同的是,yolo先将图片分成S*S个块。
每个单元格会预测B个边界框(bounding box)以及边界框的置信度(confidence score)。所谓置信度其实包含两个方面,一是这个框中目标存在的可能性大小,二是这个边界框的位置准确度。
前者我们把它记做Pr(obj),若框中没有目标物,则Pr(obj)=0,若含有目标物则Pr(obj)=1 。那么边界框的位置的准确度怎么去判断呢?我们使用了一种叫做IOU(交并比)的方法,意思就是说我预测的框与你真实的框相交的面积,和预测的框与真实框合并的面积的比例。我们可以记做IOU(pred) ,那么置信度就可以定义为这两项相乘。
现在有了另一个问题,我每个格子预测的边界框应该怎么表示呢? 边界框的大小和位置可以用四个值来表示,(x,y,w,h) 注意,不要凭空想象是一个矩形对角两个点的位置坐标,这里面的x,y是指预测出的边界框的中心位置相对于这个格子的左上角位置的偏移量,而且这个偏移量不是以像素为单位,而是以这个格子的大小为一个单位。
如果不明白可以用下面这张图去举个例子。下面这个框的中心点所在的位置,相对于中心点所在的这个格子的x,y差不多是0.3, 0.7, (不明白的话就仔细琢磨一下)。
而这个w,h指的是这个框的大小,占整张图片大小的宽和高的相对比例,想一下,有了中心点的位置,有了框的大小,画出一个框是很容易的对吧。(x,y,w,h,c)这五个值理论上都应该在[0,1]区间上。
最后一个c是置信度的意思。一般一个网格会预测多个框,而置信度是用来评判哪一个框是最准确的,我们最想得到的框。
框预测好了,接下来就是分类的问题,每个单元格预测出(x,y,w,h,c)的值后,还要给出对于C个类别的概率值。现在重新提一下字母的含义,
B是边界框的个数,
C是我有多少个类别要分类。
S是我怎么划分单元格。
那么我每个单元格要预测B*5+C个值。如果将输入图片划分为S×S网格,那么最终预测值为S×S×(B∗5+C)大小的张量。
Yolo算法--从原理到实现(一)_Code坑似海的博客-CSDN博客_yolo算法
You Only Look Once:你只看一次
YOLO v5的最大特点
答案就是:一个字:快,应用于移动端,模型小,速度快。
有下面3部分组成:
- 前向传播部分:90%
- 损失函数部分
- 反向传播部分
其中前向传播部分占用的时间应该在90%左右,即搞清楚前向传播部分也就搞清楚了这模型的实现流程和细节。本着这一原则,我们开始YOLO系列模型的解读:
2 不得不谈的分类模型
在进入目标检测任务之前首先得学会图像分类任务,这个任务的特点是输入一张图片,输出是它的类别。
对于输入图片,我们一般用一个矩阵表示。
对于输出结果,我们一般用一个one-hot vector表示: [0,0,1,0,0,0] ,哪一维是1,就代表图片属于哪一类。
所以,在设计神经网络时,结构大致应该长这样:
img → cbrp16→ cbrp32→ cbrp64→ cbrp128→ ...→ fc256-fc[10]
这里的cbrp指的是conv,bn,relu,pooling的串联。
由于输入要是one-hot形式,所以最后我们设计了2个fc层(fully connencted layer),我们称之为 “分类头” 或者 “决策层” 。
3 YOLO系列思想的雏形:YOLO v0
有了上面的分类器,我们能不能用它来做检测呢?
要回答这个问题,首先得看看检测器和分类器的输入输出有什么不一样。首先他们的输入都是image,但是分类器的输出是一个one-hot vector,而检测器的输出是一个框(Bounding Box)。
框,该怎么表示?
在一个图片里面表示一个框,有很多种方法,比如:
x,y,w,h表示一个框
- x,y,w,h(如上图)
- p1,p2,p3,p4(4个点坐标)
- cx,cy,w,h(cx,cy为中心点坐标)
- x,y,w,h,angle(还有的目标是有角度的,这时叫做Rotated Bounding Box)
- ......
所以表示的方法不是一成不变的,但你会发现:不管你用什么形式去表达这个Bounding Box,你模型输出的结果一定是一个vector,那这个vector和分类模型输出的vector本质上有什么区别吗?
答案是:没有,都是向量而已,只是分类模型输出是one-hot向量,检测模型输出是我们标注的结果。
所以你应该会发现,检测的方法呼之欲出了。那分类模型可以用来做检测吗?
当然可以, 这时,你可以把检测的任务当做是遍历性的分类任务。
如何遍历?
我们的目标是一个个框,那就用这个框去遍历所有的位置,所有的大小。
比如下面这张图片,我需要你检测葫芦娃的脸,如图1所示:
图1:检测葫芦娃的脸
我们可以对边框的区域进行二分类:属于头或者不属于头。
你先预设一个框的大小,然后在图片上遍历这个框,比如第一行全都不是头。第4个框只有一部分目标在,也不算。第5号框算是一个头,我们记住它的位置。这样不断地滑动,就是遍历性地分类。
接下来要遍历框的大小:因为你刚才是预设一个框的大小,但葫芦娃的头有大有小,你还得遍历框的大小,如下图2所示:
图2:遍历框的大小
还没有结束,刚才滑窗时是挨个滑,但其实没有遍历所有的位置,更精确的遍历方法应该如下图3所示:
图3:更精确地遍历框的位置
这种方法其实就是RCNN全家桶的初衷,专业术语叫做:滑动窗口分类方法。
现在需要你思考一个问题:这种方法的精确和什么因素有关?
答案是:遍历得彻不彻底。遍历得越精确,检测器的精度就越高。所以这也就带来一个问题就是:检测的耗时非常大。
举个例子:比如输入图片大小是(800,1000)也就意味着有800000个位置。窗口大小最小 (1×1) ,最大 (800×1000) ,所以这个遍历的次数是无限次。我们看下伪代码:
滑动窗口分类方法伪代码
那这种方法如何训练呢?
本质上还是训练一个二分类器。这个二分类器的输入是一个框的内容,输出是(前景/背景) 。
第1个问题:
框有不同的大小,对于不同大小的框,输入到相同的二分类器中吗?
是的。要先把不同大小的input归一化到统一的大小。
第2个问题:
背景图片很多,前景图片很少:二分类样本不均衡。
确实是这样,你看看一张图片有多少框对应的是背景,有多少框才是葫芦娃的头。
以上就是传统检测方法的主要思路:
- 耗时。
- 操作复杂,需要手动生成大量的样本。
到现在为止,我们用分类的算法设计了一个检测器,它存在着各种各样的问题,现在是优化的时候了(接下来正式进入YOLO系列方法了):
YOLO的作者当时是这么想的:你分类器输出一个one-hot vector,那我把它换成 (x,y,w,h,c) ,c表示confidence置信度,把问题转化成一个回归问题,直接回归出Bounding Box的位置不就好了吗?
刚才的分类器是:img → cbrp16→ cbrp32→ cbrp64→ cbrp128→ ...→ fc256-fc[10]
现在我变成:img → cbrp16→ cbrp32→ cbrp64→ cbrp128→ ...→ fc256-fc[5],这个输出是 (x,y,w,h,c) ,不就变成了一个检测器吗?
本质上都是矩阵映射到矩阵,只是代表的意义不一样而已。
传统的方法为什么没有这么做呢?我想肯定是效果不好,终其原因是算力不行,conv操作还没有推广。
好,现在模型是:
img → cbrp16→ cbrp32→ cbrp64→ cbrp128→ ...→ fc256-fc[5] → c,x,y,w,h
那如何组织训练呢?找1000张图片,把label设置为 (1,x∗,y∗,w∗,h∗) 。这里 x∗ 代表真值。有了数据和标签,就完成了设计。
我们会发现,这种方法比刚才的滑动窗口分类方法简单太多了。这一版的思路我把它叫做YOLO v0,因为它是You Only Look Once最简单的版本。
4 YOLO v1终于诞生
- 需求1:YOLO v0只能输出一个目标,那比如下图4的多个目标怎么办呢?
图4:多个目标情况
你可能会回答:我输出N个向量不就行了吗?但具体输出多少个合适呢?图4有7个目标,那有的图片有几百个目标,你这个N又该如何调整呢?
答:为了保证所有目标都被检测到,我们应该输出尽量多的目标。
输出尽量多的目标
但这种方法也不是最优的,最优的应该是下图这样:
图5:用一个(c,x,y,w,h)去负责image某个区域的目标
如图5所示:用一个(c,x,y,w,h)去负责image某个区域的目标。
比如说图片设置为16个区域,每个区域用1个(c,x,y,w,h)去负责:
图6:图片设置为16个区域
就可以一次输出16个框,每个框是1个(c,x,y,w,h),如图6所示。
为什么这样子更优?因为conv操作是位置强相关的,就是原来的目标在哪里,你conv之后的feature map上还在哪里,所以图片划分为16个区域,结果也应该分布在16个区域上,所以我们的结果(Tensor)的维度size是:(5,4,4) 。
那现在你可能会问:c的真值该怎么设置呢?
答: 看葫芦娃的大娃,他的脸跨了4个区域(grid),但只能某一个grid的c=1,其他的c=0。那么该让哪一个grid的c=1呢?就看他的脸的中心落在了哪个grid里面。根据这一原则,c的真值为下图7所示:
图7:c的label值
但是你发现7个葫芦娃只有6个1,原因是某一个grid里面有2个目标,确实如此,第三行第三列的grid既有水娃又有隐身娃。这种一个区域有多个目标的情况我们目前没法解决,因为我们的模型现在能力就这么大,只能在一个区域中检测出一个目标,如何改进我们马上就讨论,你可以现在先自己想一想。
总之现在我们设计出了模型的输出结果,那距离完成模型的设计还差一个损失函数,那Loss咋设计呢?看下面的伪代码:
loss = 0 for img in img_all: for i in range(4): for j in range(4): loss_ij = lamda_1*(c_pred-c_label)**2 + c_label*(x_pred-x_label)**2 +\ c_label*(y_pred-y_label)**2 + c_label*(w_pred-w_label)**2 + \ c_label*(h_pred-h_label)**2 loss += loss_ij loss.backward()
遍历所有图片,遍历所有位置,计算loss。
- 好现在模型设计完了,回到刚才的问题:模型现在能力就这么大,只能在一个区域中检测出一个目标,如何改进?
答: 刚才区域是 4×4 ,现在变成 40×40 ,或者更大,使区域更密集,就可以缓解多个目标的问题,但无法从根本上去解决。
- 另一个问题,按上面的设计你检测得到了16个框,可是图片上只有7个葫芦娃的脸,怎么从16个结果中筛选出7个我们要的呢?
答:
法1:聚类。 聚成7类,在这7个类中,选择confidence最大的框。听起来挺好。
法1的bug: 2个目标本身比较近聚成了1个类怎么办?如果不知道到底有几个目标呢?为何聚成7类?不是3类?
法2:NMS(非极大值抑制)。 2个框重合度很高,大概率是一个目标,那就只取一个框。
重合度的计算方法:交并比IoU=两个框的交集面积/两个框的并集面积。
具体算法: