RCNN、FastRCNN、FasterRCNN、YOLO、SSD网络结构通俗解读(三):YOLO系列

前言

(目前还是草稿版本,先发出来,还未修正)
这是目标检测专题系列的第二篇,重点讲解一阶段(one-stage)方法中的YOLO系列。前一篇文章链接在这里。链接: link
这篇文章的思路会和上一篇文章一样,以时间顺序,先讲YOLOv1,再讲YOLOv2,最后讲YOLOv3,最后到YOLOv4。在这个过程中,会依次列出每一次模型升级过程中的改进

**YOLO(You Only Look One)**是yolo系列的开山之作,在2016年被提出。YOLO相对于前面讲的FasterRCNN来说,检测速度又更进了一步。当然,准确率相对于FasterRCNN来说,准确率稍微有些下降,但是在可接受的范围之内。

YOLOv1

YOLO在2016年被提出,以简单,高效的优势,一跃成为与RCNN系列并驾齐驱的物体检测框架。
YOLOV1的优势可总结为三点:
1 速度快,具有较高的fps;
2 误检少,体现在检测出的物体中,其实是背景类的比较少
3 泛化能力强,体现在可以检测出美术画中的物体。
下面欣赏以下YOLOv1的运行过程
在这里插入图片描述
YOLOV1的运行过程分为三步,如上图所示:
1 输入一张图片,Resize到448x448
2 输入至CNN中(文中叫Darknet)
3 输出结果,然后经过后处理,包括非极大值抑制(NMS),最后得到框和类别。

其中CNN整体的结构是由24个卷积层2个全连接层组成,激活函数为Leaky ReLU。最后输出的向量维度是1470=7x7x30,说明最后输出了7x7组结果,每组结果由30个数组成,网络结构如下:
在这里插入图片描述
这里需要注意,输入图片是448x448,输出是7x7,Feature Stride为64.
再看下面这张图:在这里插入图片描述
我们把图片分成7 x 7的网格单元,每个网格单元会关联2个形状不规则的边界框(bounding box)。两个边界框由两组坐标来表示,因此每个边界框有5个参数:中心点的xywh和一个置信度confidence。此外,每个网格单元还有一个条件类别概率(个数等于训练类别的数目20个)。
这里起始有个问题,为何是每个网格单元有一组类别概率而不是每个边界框呢?这里就是YOLOv1的一个缺点,每个边界框是不允许有2个类别的,这也许是出于速度、准确度的考虑。

总结一下是这样的:YOLOv1将图像分成S x S个的网格单元(grid cell),并且每个网格单元负责预测B(B=2)个边界框(grounding box)。加上每个边界框的box的置信度confidence,每个网格的C个条件类别概率,每个网格最后可被编码输出为S x S x (B * 5 + C)的张量。
在这里插入图片描述
这里需要注意的是,每个单元网格不是说有物体存在就需要为其画边界框的。而是只有当物体的【中心】落到那个单元网格里的时候,那个单元网格才负责检测那个物体。
我们的置信度confidence的定义:
confidence的计算公式:
c o n f i d e n c e = P r ( O b j e c t ) ∗ I O U p r e d t r u t h confidence = Pr(Object) * IOU^{truth}_{pred} confidence=Pr(Object)IOUpredtruth
如果该网格单元中不存在目标的中心,则置信分数为0。此时,Pr (Object)为0。注意,是是目标的中心一定要在网格单元中。如果目标中心不在网格单元中的话,我们则希望置信度分数等于预测框和真实框之间的IOU(即Pr (Object)应该趋向于1,因而confidence=IOU)。
每个边界框包含5个预测:x,y,w,h和置信度c。即自身bounding box位置+confidence。(x, y)坐标表示边界框的中心点相对于这个网格单元左上角的便宜(因为xy代表的是框的中心,所以我们经常会用xc,yc表示框的坐标)。xywh都会归一化为(0, 1)区间内。wh归一化非常简单:w=框的宽度/图片总宽度)。因为xy只是中心点相对网格单元左上角的偏移,具体而言,xy是如下图所示:在这里插入图片描述
x = x c ∗ S w i d t h i m a g e − c o l = x c w i d t h g r i d − c o l x = x_{c} * \frac{S}{width_{image}} - col = \frac{x_{c}}{width_{grid}}-col x=xcwidthimageScol=widthgridxccol

y = y c ∗ S h e i g h t i m a g e − r o w = y c h e i g h t g r i d − r o w y = y_{c} * \frac{S}{height_{image}}- row =\frac{y_{c}}{height_{grid}} - row y=ycheightimageSrow=heightgridycrow
解读:x的计算公式是,先把图片分成7份, x c w i d t h g r i d \frac{x_{c}}{width_{grid}} widthgridxc是每个网格单元的宽度, x c ∗ S w i d t h i m a g e x_{c} * \frac{S}{width_{image}} xcwidthimageS表示xc包含几个小的网格单元,再减去col表示xc在单个网格单元里的相对位置。
通常情况下,YOLO不预测边界框中心的绝对坐标,它预测的是偏移量。预测的结果是通过一个sigmoid函数,迫使输出的值在0和1之间。例如,考虑上图中狗的情况,如果对中心xy的预测是(0.4,0.7),那么这意味着中心位于13x13特征图上的(2.4, 5.7)。

每个bounding box都有一个对应的confidence score, 如果当前的grid cell中不包含object的话,那么这个confidence 就是0, 如果有的话,这个confidence score就等于预测的box与ground truth的IOU的值。那么如何判断一个grid cell中是否包含object呢?作者是这样做的,如果一个object的ground truth的中心点落入一个grid cell中,那么这个grid cell就包含这个object,这个object的预测就由这个grid cell负责(也就是说每一个grid 只能预测一个物体,那么对于分布密集的物体,就极有可能造成漏检。因为密集的物体可能会有多个物体的中心落在同一个grid之中。)每个grid cell都要预测C个类别的概率,表示当前的grid cell在包含某个object的条件下属于类别的概率。文章中是这样说的:

YOLOV1的核心
YOLOV1的核心思想是把图片分成SXS个网格(grid cell),然后每个网格负责检测”一个“框和类别,再经过概率和NMS(非极大值抑制),得到最后结果。
下面从几个大家心中的疑问来给大家讲解YOLOV1。
1 S是多少?
直接回答S=7,原因是因为grid cell的数目需要跟最后特征图尺寸一一对应,最后特征图的尺寸为7x7,因此网格的数目也就是7x7。
2 为什么要分成SXS个网格?
用以预测结果(框和类别)的特征图大小为7x7,有49个向量(这里有点像RPN),每个向量都要去预测框和类别。训练时,我们需要为每个向量分配类别以及是否需要负责预测框,那么如何分配呢?我们需要把7x7个点映射回原图,正好形成7x7个网格,然后根据每个网格跟ground truth之间的关系(作者设定了规则去建立联系)来做后续分配。这也就是为何要分成SXS(7x7)个框了。
3 选择那些网格去预测框?
每个网格在最后7x7的特征图上对应一个向量,这个30维向量用来预测结果,那么我们应该选择哪个网格来负责预测框呢?通俗一些,就是哪些网格是正例,哪些网格是正例、哪些是负例。我们看文中的一段描述:
Our system divides the input image into an SXS grid. If the center of an object falls into a grid cell, that grid cell is responsible for detecting that object.
这句话给了我们标准答案,物体的中心落在哪个网格中,哪个网格就负责训练这个框(类别和坐标)。
4 选好了网格,下一步计算x,y,w,h(用来训练),他们是怎么计算的?
根据原论文我们可以确定几点,第一,x,y指的是中心点,是物体框的中心点,不是网格的中心点(当然物体的中心点必然会落在相应的网格中)。第二,w h指的是物体框的宽和高。第三,x y w h都在0-1之间。
我们在训练的时候,损失函数计算的是预测的(x’, y’, w’, h’)与实际值(w, y, w, h)之间的L2-Loss,预测值是通过网络,最后在30维向量中(7x7x30),实际值是怎么算的呢?
比如图片的宽高为width, height,物体的中心点为x,y,宽高为w,h。那么根据论文的描述:
w = w w i d t h w=\frac{w}{width} w=widthw
h = h h e i g h t h=\frac{h}{height} h=heighth
x,y在Loss中的值是相对网格的偏移。那么是相对于网格的中心点还是左上角的偏移呢?其实这个顾虑是多余的,因为在最后7x7大小的特征图上,网格仅仅是一个。相对于它的偏移,我们需要把x,y也映射到7x7的特征图上,然后与对应的网格的位置相减。不难发现,这个过程可以简化为:
x = x ∗ S w i d t h − i n t ( x ∗ S w i d t h ) x=\frac{x*S}{width}-int(\frac{x*S}{width}) x=widthxSint(widthxS)
y = y ∗ S h e i g h t − i n t ( y ∗ S h e i g h t ) y=\frac{y*S}{height}-int(\frac{y*S}{height}) y=heightySint(heightyS)
根据选取网格的办法,我们知道中心点x,y在7x7的特征图上的位置的整数部分就是对应网格的位置,因为他们在原图上的距离也不超过448/S(网格的大小)。所以在7x7的图片上,它们的距离不超过1.
5.实际的训练过程中,需要分SxS个网格吗?
根据上一个问题的答案,我们不难发现,实际的训练和预测的过程中,我们是不需要对原图划分网格的,论文中划分网格的主要目的是为了方便表达。
6.最后输出的30维向量,代表什么
论文中说了,我们一共预测98个框,所以30维向量中一定包含了2个框的信息,也就是两组(x, y, w, h)。由于是在Pascal VOC中训练的,所以还有20个类别信息。然而,YOLOv1不仅预测了框的分数(即用来判断是不是框),还预测了背景的信息。因此,30维向量成分:2组(x, y, w, h, score)+ 20个类别概率。
7.框的分数是怎么算的。
预测的时候,分数是直接输出的,那么训练的时候呢?是不是选中的网格分数为1,没选中的为0呢。答案是NO。这也是YOLOv1的巧妙之处。YOLOv1计算了一个所谓的IOU分数。IOU指的是预测框与实际框的IOU,作为输入到Loss Function中的“目标”,与预测的IOU分数做L2-Loss。举个例子,第一次迭代,网络会输出一个预测框P1,对应的真实框为G,程序会计算P1和G的IOU,然后继续做Loss。
==8.为什么每个输出都有2个框,实际却只负责一个框?
其中YOLOv1有一个细节,我们看图描述一下:
在这里插入图片描述
其中,绿色的框P1与白色的框P2是网络预测框,而红色的框G是真实值。YOLOv1会计算P1与G的IOU1,以及P2与G的IOU2,然后比较IOU1与IOU2哪个大。如果IOU更大,那么P2的信息会输入到LossFunction中,P1的IOU1,如果小于某个阈值(比如0.7),则会被忽视掉。若大于这个阈值,则会被赋值为-1,不参与Loss计算。

Loss Function

Loss = λ \lambda λcoord x 坐标预测误差 + (含Object的box confidence 预测误差+ λ \lambda λnoobj x 不含object的box confidence预测误差)+类别预测误差
展开就是:
在这里插入图片描述
上式共分为5行。
这里给大家依次解读一下。 1 i o b j 1^{obj}_{i} 1iobj表示目标的中心是否出现在网格单元i中。 1 i j o b j 1^{obj}_{ij} 1ijobj表示网格单元i中的第j个边界框预测器为预测“负责”。
解读Loss:
Loss = 回归框检测 + 预测置信度(物体性)+ 预测是背景的分数+ 预测物体类别

我们使用平方和衡量坐标误差,因为它很容易优化。但是并不完全符合我们使平均精度最大化的目标。
分类误差与定位误差的权重是一样的,虽然这并不理想。
另外,在每张图像中,许多网格单元不包含任何对象。这将导致这些网格单元的“置信度”分数趋向0。通常压倒了包含目标的单元格的梯度,这可能导致模型不稳定,从而导致训练早期发散。
**权重的理解:**大多数网格单元和边界框中并不包含object,正负样本并不均衡。比如输出7730的张量中可能大多数没有物体,代入上图的第四行公式进行计算。只有少数几个是代入第三行公式中进行计算。我们需要让背景的confidence权重减少一些。我们使用两个参数 l a m b d a c o o r d lambda_{coord} lambdacoord λ n o o b j \lambda_{noobj} λnoobj来完成这个工作。我们使 l a m b d a c o o r d = 5 lambda_{coord}=5 lambdacoord=5 λ n o o b j = 0.5 \lambda_{noobj}=0.5 λnoobj=0.5。这样,相当于背景的权重是前景物体框的一半,而前景框的权重是置信度的5倍,背景框不计算loss。
**框与真实边界框的匹配:**虽然YOLO每个网格预测多个框,但是在训练的时候,对于每个物体,我们只想让一个框去预测这个物体。我们就让与这个真实边界框有最高IOU的框去负责预测这个物体。
1 i j o b j 1^{obj}_{ij} 1ijobj 1 i j n o o b j 1^{noobj}_{ij} 1ijnoobj的理解: 1 i j o b j 1^{obj}_{ij} 1ijobj表示第i个网格单元中的第j个边界框负责预测。结合分配gt的理解方式即 1 i j o b j 1^{obj}_{ij} 1ijobj的个数等于gt框的个数。此外需要注意的是,如果box所在的框不是物体的中心,则只会代入loss公式中的第4行进行计算,而不会代入其他4行。第5行为分类误差,仅在该单元网格内有物体时才进行计算。
第1行和第2行为位置误差,仅在框与gt有最大IOU时才进行计算。这里我们也可看出,每个单元网格的2个边界框至少有1个当了背景,只取了其中一个。 1 i o b j 1^{obj}_{i} 1iobj
表示物体是否出现在了网格i中,第i个网格内的物体才会代入分类误差中。
注意:如果某个网格单元中没有目标,则不对分类误差进行反向传播,B个box中与GT具有最高IOU的一个进行坐标误差的反向传播,其余不进行。
大小框误差规模的解决:对于大的box来说,与gt的误差差1mm可能无所谓,但是对于小box来说则非常谨慎。为了部分解决这个问题,我们直接预测边界框宽度和高度的平凡根,而不是宽度和高度。

# class_loss, 计算类别的损失,p
class_delta = response * (predict_classes - classes)#response查看哪个cell负责标记object
class_loss = tf.reduce_mean(   #平方差损失函数
    tf.reduce_sum(tf.square(class_delta), axis=[1, 2, 3]),
    name='class_loss') * self.class_scale   # self.class_scale为损失函数前面的系数

# 有目标的时候,置信度损失函数
object_delta = object_mask * (predict_scales - iou_predict_truth)  
#用iou_predict_truth替代真实的置信度,真的妙,佩服的5体投递
object_loss = tf.reduce_mean(  #平方差损失函数
    tf.reduce_sum(tf.square(object_delta), axis=[1, 2, 3]),
    name='object_loss') * self.object_scale

# 没有目标的时候,置信度的损失函数
noobject_delta = noobject_mask * predict_scales
noobject_loss = tf.reduce_mean(       #平方差损失函数
    tf.reduce_sum(tf.square(noobject_delta), axis=[1, 2, 3]),
    name='noobject_loss') * self.noobject_scale

# 框坐标的损失,只计算有目标的cell中iou最大的那个框的损失,即用这个iou最大的框来负责预测这个框,其它不管,乘以0
coord_mask = tf.expand_dims(object_mask, 4)  # object_mask其维度为:[batch_size, 7, 7, 2], 扩展维度之后变成[batch_size, 7, 7, 2, 1]
boxes_delta = coord_mask * (predict_boxes - boxes_tran)     #predict_boxes维度为: [batch_size, 7, 7, 2, 4],这些框的坐标都是偏移值
coord_loss = tf.reduce_mean(  #平方差损失函数
    tf.reduce_sum(tf.square(boxes_delta), axis=[1, 2, 3, 4]),
    name='coord_loss') * self.coord_scale

测试

定义:类别置信度 = 条件类别概率 * 置信度
Pr( C l a s s i Class_{i} Classi|Object) * Pr(Object) * I O U p r e d t r u t h IOU^{truth}_{pred} IOUpredtruth=Pr( C l a s s i Class_{i} Classi) * I O U p r e d t r u t h IOU^{truth}_{pred} IOUpredtruth
即每个网格单元预测的class信息乘以每个边界框预测的confidence信息。这得到了每个bbox特定类别的置信度分数(暂且叫做类别置信度)。类别置信度包含了类别在框中出现的概率以及预测框与物体的拟合程度。
得到每个bbox的类别置信度后,设置阈值,滤掉得分低的boxes,对保留的boxes进行NMS非极大值抑制,就得到最终的处理结果。
类别置信度在输出的$7730的张量中并没有直观的表示,而是需要我们计算得出。在这里插入图片描述
在这里插入图片描述
如上面两图所示:同一个网格单元中有2个bbox,所以得到2组类别置信度。每组类别置信度是用每个box的confidence乘以同一个网格的类别概率得到的。这里还需说明的是既然后面乘的是同一组概率,那么两组类别置信度还是与置信度成正比的,因为乘的概率一定。因为概率的最大值对应的类别是确定的,所以两组类别置信度算出来的box只能被网络认为是同一分类,不可能出现同一gridcell中不同分类的情况的。
虽然每个grid cell可以预测B个bbox,但是最终只选择IOU最高的bbox作为物体健侧输出,即每个格子最多只预测一个物体。当物体占画面比例较小时,例如图像中包含鸟群时,每个gridcell包含多个物体,但是只能检测出其中一个。这是YOLO方法的一个缺陷。
接着每个网格都计算出了2个置信度向量,如下面3张流程图,注意找不同。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

共得到 7 ∗ 7 ∗ 2 = 98 7*7*2=98 772=98个类别置信度向量(竖条黄色线)。接下来我们从每类的视角分析类别置信度,比如对于狗类来说,狗的类别置信度都是在类别置信度向量的第一个位置。(狗是横条红虚线)在这里插入图片描述
第一步,我们先将横条中少于一定阈值的数值置为0,比如小于0.2,这一步为set zero。然后以竖条为单位,以狗的类别置信度(红线)从大到小排列,这样大值在前,最后是0。这步称为sort descending。然后对狗进行非极大值抑制NMS操作,以防止同一个物体被几个框同时圈到,很多余。
得到每个box的类别置信度之后,首先设置阈值,滤掉得分低的boxes,然后对保留的boxes进行NMS处理,就得到最终的检测结果。

NMS原理:首先从所有的预测框中找到置信度最大的那个bbox(已经排序放在最前面了),然后挨个计算其与剩余bbox的IOU,如果IOU大于一定的阈值(重合度过高,例如0.5,)那么就将该类别置信度设置为0,把该bbox剔除;然后对剩余的预测框重复上述过程,直到处理完所有的预测框。
虽然每个网格单元可以预测B个bbox,但是最终只选择IOU最高的bbox作为物体健侧输出,即每个格子最多只检测出一个物体。当物体占画面比例较小,如图像中包含鸟群时,每个格子包含多个物体,但却只能检测出其中一个,这是YOLOv1的一个缺陷。

附:NMS实例

狗类别的置信度效果如下:
在这里插入图片描述
然后我们以此计算其他类别的,直到算到最后一个类别。
在这里插入图片描述
对于整张图是这样的效果。
在这里插入图片描述
NMS进行后,很多类别置信度被置为0了。(实际上我此时也不清楚到底内存空间里里有没有存放类别置信度,我觉得很可能没用,这里“类别置信度置为0”只是把之前的置信度confidence置为0了,同样的效果)。然后我们就可以如上图算NMS后的,每个box的类别置信度向量,其中每一条的最大值即为这个box的预测类别。(很多box都没0称为背景了)。

然后我们依次计算每个box的最大值,每1竖条的最大值即该box的预测类别。下图表示第一个box是物体在这里插入图片描述
下图第2个box不是物体,因为最大值还是0.
在这里插入图片描述
然后我们就得到了3个框。
在这里插入图片描述

YOLOv1总结

YOLOv1的特点:
YOLO 对相互靠的很近的物体,还有很小的群体检测效果不好,这是因为一个网格中只预测了两个框,并且只属于一类。

同一类物体出现的新的不常见的长宽比和其他情况时,泛化能力偏弱。

由于损失函数的问题,定位误差是影响检测效果的主要原因。尤其是大小物体的处理上,还有待加强。

速度特别快,泛化能力强,提供速度,降低了精度

但小物体,重叠物体无法检测

v1对于整个yolo系列的价值,即v2/v3还保留的特性,可以总结为3点:

  1. leaky ReLU,相比普通ReLU,leaky并不会让负数直接为0,而是乘以一个很小的系数(恒定),保留负数输出,但衰减负数输出;公式如下:
    y=x,x>0;y=0.1x,otherwise
  2. 分而治之,用网格来划分图片区域,每块区域独立检测目标;
  3. 端到端训练。损失函数的反向传播可以贯穿整个网络,这也是one-stage检测算法的优势。
    待解决问题:
    最后为何要用FC,直接从7 x7 x1024卷积到 7 x7 x30不就可以了吗?中间为何要有4096个全连接?

YOLOv2

YOLOv2快过Faster R-CNN,SSD

这篇文章一共介绍了YOLO v2和YOLO9000两个模型,二者略有不同。前者主要是YOLOv1的升级版,后者的主要检测网络也是YOLO v2,同时对数据集做了融合,使得模型可以检测9000多类物体。而提出YOLO9000的原因主要是目前检测的数据集数据量较小,因此利用数量较大的分类数据集来帮助训练检测模型。

Better,Faster部分讲YOLOv2;Stronger讲YOLO9000

2.1 Better更好

2.1.1 Batch Normalization:

使用 Batch Normalization 对网络进行优化。对网络的每一层的输入都做了归一化,这样网络就不需要每层都去学数据的分布,让网络提高了收敛性,更快收敛,同时还消除了对其他形式的正则化(regularization)的依赖。

使用 Batch Normalization 可以从模型中去掉 Dropout,而不会产生过拟合。

通过对 YOLO 的每一个卷积层增加 Batch Normalization,最终使得 mAP 提高了 2%,同时还使模型正则化。YOLO3代均采用BN。

2.1.2 High resolution classifier

目前业界标准的检测方法,都要先把分类器(即卷积分类网络)放在ImageNet上进行预训练,这样卷积网络对物体更敏感。在预训练的基础上对网络进行改进与进一步训练。从 Alexnet 开始,大多数的分类器都运行在小于 256×256 的图片上,因为一般是预训练的ImageNet。而现在 YOLO 从 224×224 增加到了 448×448,这就意味着网络需要适应新的输入分辨率。

为了适应新的分辨率,YOLO v2 的分类网络以 448×448 的分辨率先在 ImageNet上进行微调,微调 10 个 epochs,让网络有时间调整滤波器(filters),好让其能更好的运行在新分辨率上,还需要调优用于检测的 Resulting Network。最终通过使用高分辨率,mAP 提升了 4%。

2.1.3 Convolution with anchor boxes

YOLO 一代包含有全连接层,从而能直接预测 Bounding Boxes 的坐标值。 Faster R-CNN 只用卷积层与 Region Proposal Network 来预测 Anchor Box 偏移值offset与置信度(前景得分),而不是直接预测坐标值。作者发现通过预测偏移量offset而不是坐标值能够简化问题,让神经网络学习起来更容易。

所以 YOLOv2 去掉了全连接层,使用 Anchor Boxes 来预测 Bounding Boxes。作者去掉了网络中一个全连接和一个池化层,这让卷积层的输出能有更高的分辨率。

用 416×416 大小的输入代替原来448×448。由于图片中的物体都倾向于出现在图片的中心位置,特别是那种比较大的物体,所以有一个单独位于物体中心的位置用于预测这些物体。YOLO 的卷积层采用 32 这个值来下采样图片,所以通过选择 416×416 用作输入尺寸最终能输出一个 13×13 的特征图。(416/2^5=13)

使用 Anchor Box 会让精确度稍微下降,但用了它能让 YOLOv2 能预测出大于一千个框,同时 recall 达到88%,mAP 达到 69.2%。在这里插入图片描述

Dimension clusters

之前 Anchor Box 的尺寸是手动选择的(一般指faster rcnn中的anchor),所以尺寸还有优化的余地。 为了优化,在训练集的 Bounding Boxes 上跑一下 k-means聚类,来为anchor找到一个比较好的初始值。(anchor基本等同于prior)

如果我们用标准的欧式距离的 k-means,尺寸大的框比小框产生更多的错误。因为我们的目的是提高 IOU 分数,我们对好坏的标准是IOU值,而与 Box 的大小无关,所以距离度量的使用下面公式更好:

                   d(box,centroid)=1−IOU(box,centroid)

在这里插入图片描述
通过分析实验结果(Figure 2),左图:在模型复杂性与 high recall 之间权衡之后,选择聚类分类数 K=5。

结果是矮宽框较少,而高瘦框多。

Table1 表面用k聚类和原始的anchor box时的平均IOU。(这个平均IOU是与gt框最接近的prior的IOU的平均数)可以看出使用5个prior的平均IOU(61%)就好过了原来使用8个anchor的平均准确率(60.9%)。当选用9个prior时,效果更明显,平均准确率达到了67.2%,可见用k聚类很有效。

2.1.5Direct location prediction

用 Anchor Box 的方法,会让 model 变得不稳定,尤其是在最开始的几次迭代的时候。大多数不稳定因素产生自预测 Box 的(x,y)位置的时候。

因为像在RPN网络中使用的offset策略: x = ( t x ∗ w a − x a ) , y = ( t y ∗ h a ) − y a x=(t_{x}*w_{a}-x_{a}),y=(t_{y}*h_{a})-y_{a} x=(txwaxa),y=(tyha)ya。可想而知,只要t=-1和1这个框就可能偏移了整张图像,这使训练话费较长时间趋于稳定。

与RPN不同,YOLOv2还是像YOLOv1一样,预测的t是与网格相关的。假设一个网格的大小是1,只要在t输出之前加一个sigmoid即可让比例t在0-1之间,从而让框的中心点限制在一个网格中。

YOLOv2相对于YOLOv1的改进:

1.增加了Batch Normalization层

在V1中的基础上增加了BN层,位置在Conv层的后面,同时去掉了Dropout层。增加了BN层后,给YOLOv2的mAP带来了2%的mAP的提升,也说明了BN的重要作用。
Conv + BN + Leaky ReLU
可以参考下torch的实现过程。

import torch
import torch.nn as nn

class ConvBNLayer(nn.Module):
    def __init__(self, ch_in, ch_out, filter_size=3, padding=1, stride=1, bnorm=True, leaky=True):
        super(ConvBNLayer, self).__init__()
        self.conv = nn.Conv2d(ch_in, ch_out, filter_size, stride, padding, bias=False if bnorm else True)
        self.bnorm = nn.BatchNorm2d(ch_out, eps=1e-3) if bnorm else None
        self.leaky = nn.LeakyReLU(0.1) if leaky else None

    def forward(self, x):
        x = self.conv(x)
        x = self.bnorm(x)
        x = self.leaky(x)
        return x

2.使用高分辨率的分类器(+4%)

对于物体检测,通常使用ImageNet上预训练的分类模型提取特征,而训练ImageNet模型时,输入图像在224x224左右,而训练物体检测时,需要分辨率更大的图像(因为图像越清晰,越容易检测上面的物体),这样就会造成两者在输入图像分辨率上的不一致;

比如YOLO V1:在224x224大小的输入图像(ImageNet)上训练分类模型,然后将图像分辨率提升至448x448,在检测数据集上Finetune。

为了更好的适应分辨率的差异,YOLO V2选择先在ImageNet上Finetune高分辨率(448x448)的模型(10 epochs),然后再训练检测模型,这样就“弱化”了分类模型和检测模型在输入图像分辨率上的“不一致”。使用高分辨率分类模型,给YOLO带来了4%的mAP提升。

同时YOLO V2提出了新的网络结构DarkNet-19,顾名思义就是指19层卷积,具体的结构如下图所示:在这里插入图片描述
DarkNet-19在ImageNet上Top1的准确率为72.9%,其中我们可以发现里面有很多1x1的Conv在Channel维度上压缩特征图。

3.使用了anchor的思想(-0.3%)

YOLO V1没有Anchor的概念,而且候选框一共98个(7x7x2),所以可想而知,模型的Recall是个问题,所以YOLO V2为了增加Recall(如果连正确的框都没有检查出来,就更不用想着后续的优化了,所以需要增加Recall),使用了Anchor的概念,像Faster RCNN,一个Anchor会产生9个候选框,这样在YOLOV1的情况下,原本的98个框就变成了441(7x7x9)。

同时为了进一步增加候选框的数目,YOLO V2中最后用来预测的特征图尺寸是原图的1/32(YOLO V1是1/64),由于448 / 32 = 14,图像中心点是4个点(图7方便理解),这样会导致在图像中心的物体的中心点落在4个Grid Cell中,不方便分配,所以作者把输入图像的尺寸改成了416(416/32=13),中心点只有一个。增加了Anchor的概念后,mAP由原来的69.5%降低到了69.2%,但是Recall从81%增加到88%,这也为后续的优化提供了可能性。在这里插入图片描述

4.K-means聚类获取Anchor框 +直接预测位置 (+5%):

像Faster RCNN,Anchor产生的9个候选框是“人为”选择的(“瞎编”出来的),YOLO V2为了选择更合理的候选框,使用了聚类(K-means)的策略,选出最具有代表性的5个尺寸作为Anchor候选框的尺寸。

同时YOLO V2虽然使用了Anchor的思想,而预测框的坐标时依然使用YOLO V1的策略,直接预测坐标相对于Grid Cell的位置,而不是Anchor Box与Ground Truth之间的Offset。(具体K-means和Anchor如何使用,后文会详细介绍)

5.细粒度的特征(+1%):

考虑到13x13的特征图,对于小物体不是特别奏效(很小的物体在下采样1/32的情况下就消失了),所以YOLO V2使用13x13的特征图的同时,又利用了26x26大小的特征图,将两个尺寸的特征图结合。具体过程如下图:在这里插入图片描述

6.多尺度训练

为了适应不同尺寸的输入图片,YOLO V2采用了多尺度训练的策略,尺度的选择:从320到608,{320,352,…,608},每隔32选取一个尺度(其中320/32=10,与上文奇数尺寸相违背了,有些尴尬)。训练的时候,系统每隔10个batch,随机从这些候选尺度中选择一个进行训练。“多尺度”在目前的物体检测训练中,已经属于常见的操作了。
== YOLO v2的三个核心==:

1)如何通过K-means选取Anchor候选框尺寸
K-means是聚类的方法,最后得到K个聚类中心以及每个样本属于哪个中心,其中K是聚类的个数,需要人为指定,其中使用“距离”来衡量每个样本与聚类中心的关系,通常使用欧式距离,但是对YOLO V2来说,需要选取框的尺寸,衡量的标准应该是“覆盖率”,所以YOLO V2利用IOU来计算,所以距离公式如下:d(box,centroid)=1−IOU(box,centroid)
论文里,人为设定K=5,这样就完成了K-means计算Anchor候选框尺寸的过程。。。

首先一个问题是,如何计算IOU?大家头脑里浮现的往往是:在这里插入图片描述
仔细想想,这是不可能的,为什么,因为我们聚类目标仅仅是W和H,并没有X,Y的概念,也就是说我们需要在IOU的计算中去掉X,Y,所以能想到的方法就是将两个框的“中心点对齐”,然后再计算IOU,计算的过程如下:
在这里插入图片描述
有了IOU的计算方式后,我们再来看看如何通过K-means的思想得到5个候选框尺寸。首先这里向大家抛出一个小问题,最后得到的5个尺寸一定会是样本集合中的某个值么?在这里插入图片描述
上图就是K-means计算5个候选框尺寸的过程,也回答了上面的问题,因为是相加取平均的结果,所以往往不会是已知的样本尺寸。
2)YOLO v2中是如何利用了anchor的思想
跟Faster RCNN相同的地方,YOLOV2使用了Anchor的概念,其中在处理W和H的部分基本一致,我们设置Anchor框的Wa和Ha,Ground Truth为W和H,那么最后回归的目标为:
g w = l o g ( W W a ) g_{w}=log(\frac{W}{W_{a}}) gw=log(WaW)
g h = l o g ( H H a ) g_{h}=log(\frac{H}{H_{a}}) gh=log(HaH)
这样避免了YOLO V1中直接回归W和H的尴尬。
与Faster RCNN不同的是,YOLOV2回归的坐标位置时没有选择Offset,而是选择YOLO V1的策略,我们这里回忆一下Faster RCNN是如何做的,我们设置Anchor的坐标Xa和Ya,Ground Truth的坐标为X和Y:
g x = X − X a W a g_{x}=\frac{X-X_{a}}{W_{a}} gx=WaXXa
g y = Y − Y a H a g_{y}=\frac{Y-Y_{a}}{H_{a}} gy=HaYYa
YOLO V2指出这种做法是存在风险的,什么风险呢,假设我们最后的预测结果为tx, ty, tw, th,那么最后预测的框的坐标为:
x = W a ∗ t x + X a x=W_{a} * t_{x}+X_{a} x=Watx+Xa
y = H a ∗ t y + Y a y=H_{a}*t_{y}+Y_{a} y=Haty+Ya
从上述公式发现,如果tx=1,那么最后的结果相对于Anchor向右偏移了1倍(同理,tx=-1,则是向左偏移了1倍),说明这种回归目标对取值范围“约束”很弱,这样最后得到的框会出现在图的任何位置,训练就很难稳定下来,为了解决这个问题,YOLO V2沿用了V1的策略,直接预测坐标相对于Grid Cell的位置,计算公式如下(数值在[0, 1)之间:
g x = x ∗ S w i d t h − i n t ( x ∗ S w i d t h ) g_{x}=\frac{x*S}{width}-int(\frac{x*S}{width}) gx=widthxSint(widthxS)
g y = y ∗ S h e i g h t − i n t ( y ∗ S h e i g h t ) g_{y}=\frac{y*S}{height}-int(\frac{y*S}{height}) gy=heightySint(heightyS)
其中x,y为物体的中心点坐标,分成SxS个Grid Cell,针对YOLOV2,S=13,width和height为原图的宽和高。为了增加约束,YOLO V2在预测坐标时,使用了Sigmoid激活使结果在(0,1)之间。
同时也说明了YOLO V2分配训练样本的策略与V1相同,物体的中心点落在哪个Grid Cell中,其对应的Anchor候选框负责这个物体
3) 如何实现细粒度特征的
有两种实现策略,原版和改进版。
原版:首先需要两个特征图26x26x512和13x13x1024,分成两步,1)26x26x512的特征图重组成13x13x2048的特征图;2)将13x13x2048的特征图与13x13x1024的特征图Concatenate,得到13x13x3072的特征图。
这里有个细节,“重组”是如何做的?我这里简单的用下图描述一下:在这里插入图片描述
上图简单的描述了重组的过程,一个2x2x1的特征图变成了1x1x4的特征图,这个过程不涉及任何参数变量,所以重组后的特征图很好的保存了原特征图的信息。
(对于Tensorflow有对应的API实现:
tf.space_to_depth(x, blocksize=2),我们只要把特征图丢进这个API就能得到重组后的特征图了)
改进版:改进版在原版的基础上增加了一次卷积操作,先将26x26x512的特征图变为26x26x64的特征图,然后再执行后续的操作。原版和改进版的对比图如下:在这里插入图片描述

YOLOv2的损失函数

最后聊聊YOLO V2的损失函数,YOLOV2的整篇文章并没有给出损失函数的公式。YOLO V2的损失函数依然以“回归”为主,里面没有Cross Entropy Loss,全部都是L2-Loss,整体的感觉和YOLO V1很像,我这里自行把Loss分解成三个部分:
Loss = confidence_loss
+ classification_loss
+ coordinates_loss
其中 confidence_loss = no_obj_loss + obj_loss

下面我们一一分解:
首先我们先来看看confidence_loss,它代表是不是物体的概率,那么如何判断“是不是”物体呢?跟YOLO V1相似,首先判断哪个Grid Cell负责哪个物体,比如A Grid Cell负责B物体,然后每个Grid Cell中有5个预测框(根据网络的更新,候选框的位置会实时改变),然后计算预测框(5个框)与B的IOU(5个IOU),最大的IOU的Anchor框Label=1(或者=计算出的IOU),其余的IOU如果小于阈值(实际我们用的是0.6),则该Anchor框的Label=0;
具体的公式如下:
∑ j = 0 W ∑ i = 0 H ∑ k = 0 A ( 1 I O U < t h r e s h ∗ ( 0 − b i j k ) 2 ∗ w n o o b j + 1 k g t ∗ ( I O U k g t − b i j k ) 2 ∗ w o b j ) \sum_{j=0}^{W}\sum_{i=0}^{H}\sum_{k=0}^{A}(1_{IOU<thresh} * (0-b_{ijk})^{2} * w_{noobj} + 1^{gt}_{k} *(IOU^{gt}_{k}-b_{ijk})^{2} * w_{obj}) j=0Wi=0Hk=0A(1IOU<thresh(0bijk)2wnoobj+1kgt(IOUkgtbijk)2wobj)

其中W,H是特征图的大小,比如13 x 13,A是Anchor框的数量,比如5,然后 b i j k b_{ijk} bijk是预测的confidence,根据策略 b i j k b_{ijk} bijk会被当做背景与0做回归或者当作正例与IOU做回归。

然后分析classification_loss,这个Loss需要建立在 b i j k b_{ijk} bijk被当作正例的基础上,具体的公式如下:
∑ j = 0 W ∑ i = 0 H ∑ k = 0 A ∑ c = 1 C 1 k g t ∗ ( g t c − b i j k c ) 2 ∗ w c l a s s \sum_{j=0}^{W}\sum_{i=0}^{H}\sum_{k=0}^{A}\sum_{c=1}^{C}1_{k}^{gt} * (gt^{c}-b^{c}_{ijk})^{2} * w_{class} j=0Wi=0Hk=0Ac=1C1kgt(gtcbijkc)2wclass
其中我们需要注意,c是从1开始的,因为没有背景类,比如Pascal VOC有20类,那么C=20.
最后分析coordinates_loss,这个Loss也是建立在bijk被当作正例的基础上的,具体的公式如下:
∑ j = 0 W ∑ i = 0 H ∑ k = 0 A ∑ x y w h 1 k g t ∗ ( g t r − b i j k r ) 2 ∗ w c o o r d \sum_{j=0}^{W}\sum_{i=0}^{H}\sum_{k=0}^{A}\sum_{xywh}1_{k}^{gt} * (gt^{r}-b^{r}_{ijk})^{2} * w_{coord} j=0Wi=0Hk=0Axywh1kgt(gtrbijkr)2wcoord

三 YOLOv3

众所周知,YOLOV3是YOLO系列的集大成版,在YOLOV2的基础上,汇集各家所长于一身,兼顾了速度与精度。

3.1YOLOv3与YOLOv2的比较:

3.1.1与YOLOv2相同的地方

(1) 框的表示方式:聚类anchor,预测的是t
(2) 激活函数leakly relu
(3) 端到端训练,一个loss函数
(4) batch normalization+leakly relu接在每层卷积之后
(5) 多尺度训练

3.1.2 YOLOv3与YOLOv2显著区别

(1) darknet-19改成darknet-53。后者还提供了tiny darknet版本,想要速度快就改用tiny darknet

(2) 类FPN结构:输出3个尺度的feature map,多尺度预测,每个尺度3个prior。prior聚类结果

10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326

(3) 残差结构,v2没有

YOLOV3的核心
YOLOV3可近似看作是:
YOLOv3 ≈ YOLOV2+DarkNet-53+FPN-Like+Sigmoid_Class_Prediction。
下面我们上张YOLOV3的整体架构图:
在这里插入图片描述
YOLOV3中的主体结构叫DarkNet53,共有53层。之所以叫DarkNet,主要承接于YOLOV2的DarkNet19。下面上一张DarkNet53结构图。在这里插入图片描述
DarkNet53的pytorch实现可参考我之前的一篇博客链接: link
1.为什么叫做DarkNet-53层?
我们把DarkNet53形式化,则变成了如下结构:
DarkNet-53 = Conv 3x3 + Sum_(Dwon-Sample Conv3x3-S2 + N x DarkNetBlock)
DarkNetBlock = Conv1x1 + Conv3x3 + Residual
因此,我们可以发现DarkNet-53共有1+(1+1x2)+(1+2x2)+(1+8x2)+(1+8x2)+(1+4+2) = 52个卷积层。
此时我们有些惊慌失措,因为论文中明明说有53个卷积层,我猜作者是自己搞混了,因为DarkNet-19中,GlobalAvaragePooling之前有一个1x1的卷积(大家回顾一下),而DarkNet-53并没有,然而这并不影响,因为即便在DarkNet-19中,那个1x1的卷积也仅用于Classification任务。
2.每个Convolutional具体是什么结构
Conv + BN + Leaky ReLU。
在前面YOLOv2中已经讲过torch的实现过程。
3.一共有几次降采样过程?
我们在第一个问题中可以发现,我们一共有5个Conv3x3-S2操作,其中S2代表Stride为2,也就是特征图尺寸会减少为原来的一半,所以在DarkNet-53中我们可以得到相对于原图1/2,1/4,1/8,1/16,1/32比例的特征图。注意,这个信息在“物体检测”任务中很重要。

4.YOLOv3中的FPN结构
FPN结构是YOLO V3的重要的特点,FPN的具体思想可以参考我的“懒人学FPN”,这里主要对比FPN原文中的结构与YOLO V3中的FPN有什么区别。
首先我们回顾FPN原文中的结构:在这里插入图片描述
这张图直观的向我们展示了:小尺寸特征图上采样一倍,然后加上大尺寸特征图,以此类推,得到最后每个尺寸的特征图,其中选择ResNet结构中的C2,C3,C4,C5,产生P2,P3,P4,P5;其中P5=C5,Pi=UPSAMPLE(Pi+1)+Ci。
我们进一步的,把Pi=UPSAMPLE(Pi+1)+Ci 的具体结构画出来在这里插入图片描述
然后我们看看YOLO V3的具体实现,对比两者的差别:
YOLOV3是选择了DarkNet-53最后三个尺寸的特征图,我们姑且叫它们C3,C4,C5,通过FPN的思想,最后得到P3,P4,P5,其中P5是如何得到的呢?C5经过了YOLODetectionBlock操作,得到了P5,那么我们来看看YOLODetectionBlock长什么样子:在这里插入图片描述
其中每个Conv都是如下结构:

Conv+BN+Leaky ReLU
所以YOLO V3的P5比原FPN的P5经历了更多的卷积。然后我们在看看多尺度融合过程:在这里插入图片描述
我们需要注意与FPN的几点不同:
多了一个1x1的Transition模块,其中包括BN和Leaky ReLU;
Upsample操作不是线性插值,而是简单的复制,与YOLO V2类似;
融合过程不是相加,而是使用了Concat,然后再经过一个YOLODetectionBlock得到最后的特征图;
整体来看YOLO V3多尺度特征融合部分相比原FPN还是复杂一些的。

Sigmoid Class Prediction

YOLOV3在做检测框类别判断时,使用的不是Softmax而是Sigmoid,究其原因,主要是作者考虑到Softmax是针对label是one-hot的形式,而实际场景的label可能是multi-hots(我自己编的词儿),也就是说一个框的label可以是人,可以是司机,也可以是男人(女人),这种情况使用Softmax就行不通了,所以可以使用Sigmoid(对应Loss:Sigmoid Cross Entropy Loss),每个类别单独判断(是不是人,是不是司机,是不是男人,是不是火车…)。

YOLO v3的细节

首先我们做个假设,输入图片是416 x 416,那么最后被用来做检测的特征图大小分别为(52 x 52), (26 x 26), (13 x13)。已知每个特征图的单元分配3个Anchors,这样我们知道一共我们有3 x (52 x 52 + 26 x 26 + 13x 13) = 10647个样本。
再做个假设,我们输入的图片有5个Ground Truth。那么我们有几个正例,几个负例呢?

1 最后10647个样本有几个正例,几个负例呢?

正例的数目对于YOLOV3来说是确定的,就是GroundTruth的数目,因为根据YOLOV3的匹配原则,选择与GT IOU最大的PredictedBox(也可以是Anchor)作为正例,我们试图想象一下正例的匹配过程:
本人看到的实现方式分为两种,本人称之为“前处理方式”和“后处理方式”,这里的“前”和“后”是针对是否运行整个网络而言。
先讲一下“前处理”方式
“前处理”方式指的是在运行网络之前,我们有9种尺寸的Anchor(每个特征图3种尺寸)和5个Ground Truth,然后进行匹配,每次选取一个Ground Truth计算9个IOU,然后选取产生最大的IOU的Anchor,这样5个Ground Truth就选出了5个Anchor(注意,这5个Anchor可以是不同尺寸也可以是相同尺寸);进一步,我们根据选出的Anchor尺寸,把Ground Truth定位到特定的特征图上,比如选出的是最小的Anchor,那么我们应该选择最大的特征图负责这个Ground Truth;最后,根据Ground Truth的中心点对应于特征图的位置,计算10647中哪个Anchor负责回归它,具体的,如果我们选择了52x52这个特征图,同时选择了这个特征图3个Anchor中的第2个Anchor,通过计算中心点落在第5个位置,那么第5 x 3 + 2 = 17个Anchor为正例。
“后处理”方式
“后处理”方式指运行网络之后,这样我们每个特征图都会预测对应的Predicted Box,一共有10647个Predicted Box,然后计算Predicted Boxes与GT的IOUs,选择与GT的IOU最大的Predicted Boxes作为正例,由于我们有5个GT,所以我们一共有5个正例。“后处理”方式与YOLO V2的匹配方式相似。

根据上面的描述,我们得知有多少GroundTruth,就有多少正例,那么负例的个数是多少呢,是10647 – 5 = 10642个负例么?对于负例的选取,V3的做法跟V2类似,首先设置个阈值Threshold,然后运行网络(“后处理”方式),得到Predicted Boxes,计算跟Ground Truth的IOUs,其中IOU大于阈值同时不是正例的Predicted Box,不参与训练(Ignore模式),其他的都是负例。具体多少个负例,需要具体问题具体分析。

2负例数量远远大于正例数量,是如何处理的呢?

大家此时肯定注意到问题所在了,负例的个数远远大于正例,会不会存在样本不均衡的问题呢?这是One-Stage中常见的问题,Focal Loss的提出就是为了解决这个问题,然而YOLO V3的机制可以很好的化解这个问题。YOLO V3在训练的时候,Classification是不包含负例的,比如COCO80类,那么最后预测的class向量就是80维,这与SSD等方法不同(SSD是81,需要包含背景),这种方法缓解了负样本过多的问题。

那么大家一定有个疑问,YOLOV3怎么使用的负样本呢
我们知道,V3中有个Objectness值,表示是或者不是物体,在训练过程中,这个值的训练是需要用到正样本和负样本一同参与训练的,但负样本不参与分类的训练。

那么大家难道不会产生另一个疑问?Objectness值的作用是什么呢
的确在训练时,Objectness不参与Classification分类,然而在测试时,最后的Class的分数是Objectness的分数与预测的Class的分数的乘积。最后,需要说明的是,训练的时候是否可以使用乘积的分数参与Classification的训练,其实也没有强制的约束。

大家是否记得YOLOV1提到的不同大小的框,回归同样的长度,影响的权重是不同的?

详细描述,就是在回归box时,回归同样大小的长度,对“小”的框影响更大,对“大”的框影响小一些,所以我们用同一种标准去度量回归的值是不公平的。当时YOLO V1使用的是对宽和高开根号操作,减弱这种“不公平”。对于YOLO V3,它是根据不同框的大小(也就是面积),事先计算一个权重,比如weight = 2 – w * h / (img_w* img_h),这样面积越大的框,权重越小,毕竟同样的回归值对它的影响也小。公式里使用2而不是1,是为了保证weight都大于1。然后在计算Loss的时候,把权重信息考虑进去,缓解了“不公平”。

本文主要参考资料(图片来源):(若有意义,请联系笔者删除或修改)
[1]懒人学AI公众号《懒人赏析YOLOV1》《懒人赏析YOLOV2》《懒人赏析YOLOV3》

本文全部参考资料:
[1] YOLOV1论文:https://arxiv.org/pdf/1506.02640.pdf
[2]YOLOv2论文:
[3]YOLOv3论文:
[4]https://www.jianshu.com/p/57ea656660f7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值