相比起Faster RCNN的两阶结构,2015年诞生的YOLO v1创造性地使用一阶结构完成了物体检测任务,直接预测物体的类别与位置,没有RPN网络,也没有类似于Anchor的预选框,因此速度很快。
6.1.1 网络结构
与其他物体检测算法一样,YOLO v1首先利用卷积神经网络进行了特征提取,具体结构如图6.1所示,该结构与GoogLeNet模型有些类似。
在该结构中,输出图像的尺寸固定为448×448,经过24个卷积层与两个全连接层后,最后输出的特征图大小为7×7×30。
关于YOLO v1的网络结构,有以下3个细节:
6.1.2 特征图的意义
YOLO v1的网络结构并无太多创新之处,其精髓主要在最后7×7×30大小的特征图中。如图6.2所示,YOLO v1将输入图像划分成7×7的区域,每一个区域对应于最后特征图上的一个点,该点的通道数为30,代表了预测的30个特征。
YOLO v1在每一个区域内预测两个边框,如图6.2中的预测框A与B,这样整个图上一共预测7×7×2=98个框,这些边框大小与位置各不相同,基本可以覆盖整个图上可能出现的物体。
如果一个物体的中心点落在了某个区域内,则该区域就负责检测该物体。图6.2中真实物体框的中心点在当前区域内,该区域就负责检测该物体,具体是将该区域的两个框与真实物体框进行匹配,IoU更大的框负责回归该真实物体框,在此A框更接近真实物体。最终的预测特征由类别概率、边框的置信度及边框的位置组成。
这里有以下3点值得注意的细节:
6.1.3 损失计算
通过卷积网络得到每个边框的预测值后,为了进一步计算网络训练的损失,还需要确定每一个边框是对应着真实物体还是背景框,即区分开正、负样本。YOLO v1在确定正负样本时,有以下两个原则:
YOLO v1的损失一共由5部分组成,均使用了均方差损失,
公式中i代表第几个区域,一共有S2个区域,在此为49;j代表某个区域的第几个预测边框,一共有B个预测框,在此为2;obj代表该框对应了真实物体;noobj代表该框没有对应真实物体。这5项损失的意义如下:
总体上,YOLO v1利用了回归的思想,使用轻量化的一阶网络同时完成了物体的定位与分类,处理速度极快,可以达到45 FPS,当使用更轻量的网络时甚至可以达到155 FPS。得益于其出色的处理速度,YOLOv1被广泛应用在实际的工业场景中,尤其是追求实时处理的场景。当然,YOLO v1也有一些不足之处,主要有如下3点:
6.2.1 网络结构的改善
首先,YOLO v2对于基础网络结构进行了多种优化,提出了一个全新的网络结构,称之为DarkNet。原始的DarkNet拥有19个卷积层与5个池化层,在增加了一个Passthrough层后一共拥有22个卷积层,精度与VGGNet相当,但浮点运算量只有VGGNet的1/5左右,因此速度极快。
相比起v1版本的基础网络,DarkNet进行了以下几点改进:
6.2.2 先验框的设计
YOLO v2吸收了Faster RCNN的优点,设置了一定数量的预选框,使得模型不需要直接预测物体尺度与坐标,只需要预测先验框到真实物体的偏移,降低了预测难度。
关于先验框,YOLO v2首先使用了聚类的算法来确定先验框的尺度,并且优化了后续的偏移计算方法,下面详细介绍这两部分。
先验框的设计为YOLO v2带来了7%的召回率提升。
1.聚类提取先验框尺度
Faster RCNN中预选框(即Anchor)的大小与宽高是由人手工设计的,因此很难确定设计出的一组预选框是最贴合数据集的,也就有可能为模型性能带来负面影响。
针对此问题,YOLO v2通过在训练集上聚类来获得预选框,只需要设定预选框的数量k,就可以利用聚类算法得到最适合的k个框。在聚类时,两个边框之间的距离使用式(6-2)的计算方法,即IoU越大,边框距离越近。
在衡量一组预选框的好坏时,使用真实物体与这一组预选框的平均IoU作为标准。值得一提的是,这一判断标准在手工设计预选框的方法中也可以使用。
至于预选框的数量选取,显然数量k越多,平均IoU会越大,效果会更好,但相应的也会带来计算量的提升,YOLO v2在速度与精度的权衡中选择了预选框数量为5。
2.优化偏移公式
有了先验框后,YOLO v2不再直接预测边框的位置坐标,而是预测先验框与真实物体的偏移量。在Faster RCNN中,中心坐标的偏移公式如式(6-3)所示。
公式中wa、ha、xa及ya代表Anchor的宽高与中心坐标,tx与ty是模型预测的Anchor相对于真实物体的偏移量,经过计算后得到预测的物体中心坐标x和y。
YOLO v2认为这种预测方式没有对预测偏移进行限制,导致预测的边框中心可以出现在图像的任何位置,尤其是在训练初始阶段,模型参数还相对不稳定。例如tx是1与-1时,预测的物体中心点会有两个宽度的差距。
因此,YOLO v2提出了式(6-4)所示的预测公式:
公式中参数的意义可以与图6.5结合进行理解,图中实线框代表预测框,虚线框代表先验框:
6.2.3 正、负样本与损失函数
关于正、负样本的选取,YOLO v2基本保持了之前的方法,其基本流程如下:
由于利用了先验框,YOLO v2的损失函数也相应的进行了改变,公式如式(6-5)所示。
损失一共有5项组成,意义分别如下:
在计算正、负样本的过程中,虽然有些预测框的最大IoU可能小于0.6,即被赋予了负样本,但如果后续是某一个真实物体对应的最大IoU的框时,该预测框会被最终赋予成正样本,以保证recall。
有些预测框的最大IoU大于0.6,但是在一个区域内又不是与真实物体有最大IoU,这种预测框会被舍弃掉不参与损失计算,既不是正样本也不是负样本。
6.2.5 工程技巧
除了模型上的改进,YOLO v2也是一个充满工程技巧的检测模型,下面从两个方面介绍其工程上的特点。
1.多尺度训练
由于移除了全连接层,因此YOLO v2可以接受任意尺寸的输入图片。在训练阶段,为了使模型对于不同尺度的物体鲁棒,YOLO v2采取了多种尺度的图片作为训练的输入。
由于下采样率为32,为了满足整除的需求,YOLO v2选取的输入尺度集合为{320,352,384,...,608},这样训练出的模型可以预测多个尺度的物体。并且,输入图片的尺度越大则精度越高,尺度越低则速度越快,因此YOLO v2多尺度训练出的模型可以适应多种不同的场景要求。
2.多阶段训练
由于物体检测数据标注成本较高,因此大多数物体检测模型都是先利用分类数据集来训练卷积层,然后再在物体检测数据集上训练。例如,YOLO v1先利用ImageNet上224×224大小的图像预训练,然后在448×448的尺度上进行物体检测的训练。这种转变使得模型要适应突变的图像尺度,增加了训练难度。
YOLO v2针对以上问题,优化了训练过程,采用如图6.6所示的训练方式,具体过程如下:
(1)利用DarkNet网络在ImageNet上预训练分类任务,图像尺度为224×224。
(2)将ImageNet图片放大到448×448,继续训练分类任务,让模型首先适应变化的尺度。
(3)去掉分类卷积层,在DarkNet上增加Passthrough层及3个卷积层,利用尺度为448×448的输入图像完成物体检测的训练。
总体上来看,YOLO v2相较于之前的版本有了质的飞跃,主要体现在吸收了其他算法的优点,使用了先验框、特征融合等方法,同时利用了多种训练技巧,使得模型在保持极快速度的同时大幅度提升了检测的精度。YOLO v2已经达到了较高的检测水平,但如果要分析其不足的话,大体有以下3点:
https://zhuanlan.zhihu.com/p/37201615
ModuleList(
(0): Sequential( (conv_0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(1): Sequential( (conv_1): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第一次下采样
(2): Sequential((conv_2): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False))
(3): Sequential((conv_3): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(4): Sequential((shortcut_4): EmptyLayer())
(5): Sequential( (conv_5): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第二次下采样
(6): Sequential( (conv_6): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(7): Sequential((conv_7): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(8): Sequential( (shortcut_8): EmptyLayer() )
(9): Sequential( (conv_9): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(10): Sequential( (conv_10): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(11): Sequential( (shortcut_11): EmptyLayer())
(12): Sequential( (conv_12): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第三次下采样
(13): Sequential( (conv_13): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(14): Sequential( (conv_14): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(15): Sequential( (shortcut_15): EmptyLayer( )
(16): Sequential( (conv_16): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(17): Sequential( (conv_17): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(18): Sequential((shortcut_18): EmptyLayer())
(19): Sequential( (conv_19): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(20): Sequential( (conv_20): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(21): Sequential( (shortcut_21): EmptyLayer())
(22): Sequential( (conv_22): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(23): Sequential( (conv_23): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(24): Sequential( (shortcut_24): EmptyLayer() )
(25): Sequential( (conv_25): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(26): Sequential( (conv_26): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(27): Sequential( (shortcut_27): EmptyLayer() )
(28): Sequential( (conv_28): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(29): Sequential( (conv_29): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(30): Sequential( (shortcut_30): EmptyLayer())
(31): Sequential (conv_31): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(32): Sequential( (conv_32): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(33): Sequential( (shortcut_33): EmptyLayer() )
(34): Sequential( (conv_34): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(35): Sequential( (conv_35): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(36): Sequential( (shortcut_36): EmptyLayer() )
(37): Sequential( (conv_37): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第四次下采样
(38): Sequential( (conv_38): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(39): Sequential( (conv_39): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(40): Sequential( (shortcut_40): EmptyLayer() )
(41): Sequential( (conv_41): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(42): Sequential( (conv_42): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(43): Sequential( (shortcut_43): EmptyLayer() )
(44): Sequential( (conv_44): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(45): Sequential( (conv_45): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(46): Sequential( (shortcut_46): EmptyLayer() )
(47): Sequential( (conv_47): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(48): Sequential( (conv_48): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(49): Sequential( (shortcut_49): EmptyLayer() )
(50): Sequential( (conv_50): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(51): Sequential( (conv_51): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(52): Sequential( (shortcut_52): EmptyLayer() )
(53): Sequential( (conv_53): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(54): Sequential( (conv_54): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(55): Sequential( (shortcut_55): EmptyLayer( )
(56): Sequential( (conv_56): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(57): Sequential (conv_57): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(58): Sequential( (shortcut_58): EmptyLayer() )
(59): Sequential( (conv_59): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(60): Sequential( (conv_60): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(61): Sequential( (shortcut_61): EmptyLayer() )
(62): Sequential( (conv_62): Conv2d(512, 1024, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第五次下采样
(63): Sequential( (conv_63): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(64): Sequential( (conv_64): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(65): Sequential( (shortcut_65): EmptyLayer() )
(66): Sequential( (conv_66): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(67): Sequential( (conv_67): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(68): Sequential( (shortcut_68): EmptyLayer() )
(69): Sequential( (conv_69): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(70): Sequential( (conv_70): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(71): Sequential( (shortcut_71): EmptyLayer() )
(72): Sequential( (conv_72): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(73): Sequential( (conv_73): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(74): Sequential( (shortcut_74): EmptyLayer() )
(75): Sequential( (conv_75): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(76): Sequential( (conv_76): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(77): Sequential( (conv_77): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(78): Sequential( (conv_78): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(79): Sequential( (conv_79): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(80): Sequential( (conv_80): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(81): Sequential( (conv_81): Conv2d(1024, 255, kernel_size=(1, 1), stride=(1, 1)) )
(82): Sequential( (Detection_82): DetectionLayer() ) # 32倍降采样
(83): Sequential( (route_83): EmptyLayer() ) # 此处route层直接把79层处特征取过来
(84): Sequential( (conv_84): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(85): Sequential( (upsample_85): Upsample(scale_factor=2.0, mode=nearest) )
(86): Sequential( (route_86): EmptyLayer() ) # 此处route将85和61层的特征拼接在一起
(87): Sequential( (conv_87): Conv2d(768, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(88): Sequential( (conv_88): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(89): Sequential( (conv_89): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(90): Sequential( (conv_90): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(91): Sequential( (conv_91): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(92): Sequential( (conv_92): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(93): Sequential( (conv_93): Conv2d(512, 255, kernel_size=(1, 1), stride=(1, 1)) )
(94): Sequential( (Detection_94): DetectionLayer() ) # 16倍降采样
(95): Sequential( (route_95): EmptyLayer() ) # 此处route层直接把91层处特征取过来
(96): Sequential( (conv_96): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(97): Sequential( (upsample_97): Upsample(scale_factor=2.0, mode=nearest) )
(98): Sequential( (route_98): EmptyLayer() ) # 此处route将97层和36层特征拼接在一起
(99): Sequential( (conv_99): Conv2d(384, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(100): Sequential( (conv_100): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(101): Sequential( (conv_101): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(102): Sequential( (conv_102): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(103): Sequential( (conv_103): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(104): Sequential( (conv_104): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(105): Sequential( (conv_105): Conv2d(256, 255, kernel_size=(1, 1), stride=(1, 1)) )
(106): Sequential( (Detection_106): DetectionLayer() )) # 8倍降采样
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。