关于YOLOv1、YOLOv2的内容,请参考目标检测之解读YOLOv1论文、源码及背后逻辑、目标检测之解读YOLOv2论文、源码及背后逻辑。下面具体看下YOLOv3做了哪些改进。
YOLOv3结构图
为了让yolo_v3结构图更好理解,我对上图做一些补充解释:
DBL: 如图左下角所示,Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。对于v3来说,BN和leaky relu已经是和卷积层不可分离的部分了(最后一层卷积除外),共同构成了最小组件。
resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个残差组件(res_unit)。这是yolo_v3的大组件,yolo_v3开始借鉴了ResNet的残差结构,使用这种结构可以让网络结构更深(从v2的darknet-19上升到v3的darknet-53,前者没有残差结构)。对于res_block的解释,可以在图的右下角直观看到,其基本组件也是DBL。
concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。
新的特征提取网络结构Darknet-53
上图的Darknet-53网络采用256*256*3作为输入,最左侧那一列的1、2、8等数字表示多少个重复的残差组件。
YOLOv3使用了darknet-53的全局平均池化层和全连接层前的网络,yolo_v3这个网络是一个全卷积网络,大量使用残差的跳层连接,并且为了降低池化带来的梯度负面效果,YOLOv3,用conv的步长stride=(2, 2)来实现降采样。
YOLOv和3v2一样,特征提取网络的输入都是416*416,都会将输出特征图缩小到输入的1/32(13*13)。所以,通常都要求输入图片是32的倍数。
利用多尺度特征进行对象检测
YOLOv2用passthrough layer来提取细粒度特征用于检测小物体,YOLOv3进一步改进,输出了3个不同尺度的feature map,如下所示:
另一种更清晰的结构如下所示:
在上图中,卷积网络在79层后,经过下方几个黄色的卷积层得到一种尺度的检测结果。相比输入图像,这里用于检测的特征图有32倍的下采样。比如输入是416416的话,这里的特征图就是1313了。由于下采样倍数高,这里特征图的感受野比较大,因此适合检测图像中尺寸比较大的对象。
为了实现细粒度的检测,第79层的特征图又开始作上采样(从79层往右开始上采样卷积),然后与第61层特征图融合(Concatenation),这样得到第91层较细粒度的特征图,同样经过几个卷积层后得到相对输入图像16倍下采样的特征图。它具有中等尺度的感受野,适合检测中等尺度的对象。
最后,第91层特征图再次上采样,并与第36层特征图融合(Concatenation),最后得到相对输入图像8倍下采样的特征图。它的感受野最小,适合检测小尺寸的对象。
9种尺寸的anchor boxes
YOLOv2有5种尺寸anchor boxes,YOLOv3有3种尺寸anchor boxes,但是YOLO v3有3个输出层,所以YOLO v3的anchor boxes比YOLO v2还是要多,因为(13*13+26*26+52*52)*3 > 13*13*5。
YOLOv3延续v2已经开始采用K-means聚类得到先验框的尺寸这种方法聚类出9种尺寸的anchor boxes。
三个输出层对应三次检测,每次对应的感受野不同,32倍的降采样(对应13*13的输出)的感受野最大,适合检测大的目标,每个单元格的三个anchor boxes为(116 ,90); (156 ,198); (373 ,326)。16倍的降采样(对应26*26的输出)适合一般大小的物体,anchor boxes为(30,61); (62,45); (59,119)。8倍的降采样(对应52*52的输出)感受野最小,适合检测小目标,因此anchor boxes为(10,13); (16,30); (33,23)。所以当输入图像为416×416时,实际总共有(52×52+26×26+13×13)×3=10647个anchor boxes。
bounding box预测
feature map中的每一个点就代表一个单元格,都会预测3个边界框(bounding box) ,每个bounding box都会预测三个东西:(1)每个框的位置(4个值,中心坐标 b x b_x bx和 b y b_y by,,框的高度 b h b_h bh和宽度 b w b_w bw),(2)置信度 ,(3)N个类别,coco数据集80类,voc20类。
bounding box 与anchor box的区别:
anchor box只是一个尺度即只有宽高。
Bounding box会根据预测得到偏移量(
t
x
,
t
y
,
t
w
,
t
h
t_x,t_y,t_w,t_h
tx,ty,tw,th)和对应anchor box的宽高(
w
a
n
c
h
o
r
,
h
a
n
c
h
o
r
w_{anchor},h_{anchor}
wanchor,hanchor),通过公式计算得到中心坐标
b
x
b_x
bx和
b
y
b_y
by,,框的高度
b
h
b_h
bh和宽度
b
w
b_w
bw。计算公式如下:
p
w
=
w
a
n
c
h
o
r
w
i
m
a
g
e
p_w=\frac{w_{anchor}}{w_{image}}
pw=wimagewanchor
p h = h a n c h o r h i m a g e p_h=\frac{h_{anchor}}{h_{image}} ph=himagehanchor
t w = l n w p r e d w a n c h o r t_w=ln\frac{w_{pred}}{w_{anchor}} tw=lnwanchorwpred
t h = l n h p r e d h a n c h o r t_h=ln\frac{h_{pred}}{h_{anchor}} th=lnhanchorhpred
b x = σ ( t x ) + c x b_x=\sigma(t_x)+c_x bx=σ(tx)+cx
b y = σ ( t y ) + c y b_y=\sigma(t_y)+c_y by=σ(ty)+cy
b w = p w e t w b_w=p_we^{t_w} bw=pwetw
b h = p h e t h b_h=p_he^{t_h} bh=pheth
其中: w i m a g e , h i m a g e w_{image},h_{image} wimage,himage是指输入图像的宽和高。
代码如下:
box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
box_confidence = K.sigmoid(feats[..., 4:5])
box_class_probs = K.sigmoid(feats[..., 5:])
分类预测变化
softmax来分类依赖于这样一个前提,即分类是相互独立的,换句话说,如果一个目标属于一种类别,那么它就不能属于另一种。
但是,当我们的数据集中存在狗或柯基犬的标签时,上面所提到的前提就是去了意义。这就是作者为什么不用softmax,而用logistic regression来预测每个类别得分并使用一个阈值来对目标进行多标签预测。比阈值高的类别就是这个边界框真正的类别。
输入映射到输出
总的来说,对于一个输入图像,YOLO3将其映射到3个尺度的输出张量,代表图像各个位置存在各种对象的概率。
我们看一下YOLO3共进行了多少个预测。对于一个416*416的输入图像,在每个尺度的特征图的每个网格设置3个先验框,总共有 13*13*3 + 26*26*3 + 52*52*3 = 10647 个预测。每一个预测是一个(4+1+80)=85维向量,这个85维向量包含边框坐标(4个数值),边框置信度(1个数值),对象类别的概率(对于COCO数据集,有80种对象)。
对比一下,YOLO2采用13*13*5 = 845个预测,YOLO3的尝试预测边框数量增加了10多倍,而且是在不同分辨率上进行,所以mAP以及对小物体的检测效果有一定的提升。
损失函数
YOLO损失函数由四部分组成:中心坐标损失(xy_loss)、宽高损失(wh_loss)、置信度损失(confidence_loss)、分类损失(class_loss)。
在v1中使用了一种叫sum-square error(SSE)的损失计算方法,就是差方相加。
YOLOv3不再是全部使用均方和损失,xy_loss、confidence_loss、class_loss使用了二值交叉熵计算损失,wh_loss而然采用SSE计算损失。
对于confidence_loss,分为obj和no_obj两种情况计算。对于obj(该检测框有对应的真实框)需要参与计算损失;对于no_obj(该检测框无对应的真实框),当检测框和真实框的iou低于0.5,也需要参与计算。
代码如下:
# 这里是一个系数,面积越小权重越高
box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]
xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[...,0:2], from_logits=True)
wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh-raw_pred[...,2:4])
confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True)+ \
(1-object_mask) * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True) * ignore_mask
class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits=True)
#mf是参与损失计算的样本数
xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss + class_loss
参考
https://blog.csdn.net/guleileo/article/details/80581858
https://github.com/qqwweee/keras-yolo3