YOLOv5改进系列(15)——增加小目标检测层
⭐一、小目标检测的介绍
1.1 什么是小目标?
(1)以物体检测领域的通用数据集COCO物体定义为例,小目标是指小于32×32个像素点(中物体是指32*32-96*96,大物体是指大于96*96)。
(2)在实际应用场景中,通常更倾向于使用相对于原图的比例来定义:物体标注框的长宽乘积,除以整个图像的长宽乘积,再开根号,如果结果小于3%,就称之为小目标。
其他定义:
- 目标边界框的宽高与图像的宽高比例小于一定值,通用值为0.1
- 边界框面积与图像面积之比的中位数在0.08%~0.58%之间
- 根据目标实际覆盖像素与图像总像素之间比例对小目标进行定义
1.2 小目标检测遇到的问题
(1)大小目标混合的场合
在这种场合中,一张图片上有少数的大目标,有小目标。
常见的问题有:
- 能够准确地检测到大目标,但检测不到小目标
- 小目标的recall 率很低,大量的小目标检测不到,被遗漏
- 小目标的数量太多,模型对小目标总是的支持不够
(2)只有小目标的场合
在这种场合中,一张图片上全是小目标。
常见的问题有:
- 小目标的recall 率很低,大量的小目标检测不到,被遗漏
- 小目标的数量太多,模型对小目标总数的支持不够
1.3 解决方法
(1)图像的缩放(数据角度的方法)
很直觉的一种方法,效果也不错。问题在于如果对整张图像进行放大,即上采样,训练的成本会大大增加。
(2)使用深度较浅的网络
小物体更容易被接受场较小的探测器预测。较深的网络具有较大的接受域,容易丢失关于较粗层中较小对象的一些信息。
(3)利用小目标周围的上下文信息(数据角度的方法)
超分辨率,指针对小目标的图像增强等。最典型的是利用生成对抗性网络选择性地提高小目标的分辨率。
(4)图像金字塔
较早提出对训练图片上采样出多尺度的图像金字塔。通过上采样能够加强小目标的细粒度特征,在理论上能够优化小目标检测的定位和识别效果。但基于图像金字塔训练卷积神经网络模型对计算机算力和内存都有非常高的要求。计算机硬件发展至今也难有胜任。故该方法在实际应用中极少。
(5)逐层预测
该方法对于卷积神经网络的每层特征图输出进行一次预测,最后综合考量得出结果。同样,该方法也需要极高的硬件性能。
(6)特征金字塔
参考多尺度特征图的特征信息,同时兼顾了较强的语义特征和位置特征。该方法的优势在于,多尺度特征图是卷积神经网络中固有的过渡模块,堆叠多尺度特征图对于算法复杂度的增加微乎其微。
(7)RNN思想
参考了RNN算法中的门限机制、长短期记忆等,同时记录多层次的特征信息。
1.4 YOLOv5中的优化方法
主要有以下几个方法:
(1)增加小目标检测层(本文)
(2)Transformer Prediction Heads (TPH)集成到YOLOv5(待研究)
(3)将CBAM集成到YOLOv5()
(4)用Bi-FPN替换PAN-Net()
⭐二、YOLOv5增加小目标层的方法
2.1 网络结构

上图是YOLOv5-6.0的网络结构,由 输入端+Backbone+Neck +Head 组成(详解点这里:)
这里我们可以看到原始的YOLOv5有3个检测头:分别是 20x20 (大目标) 40x40(中目标) 80x80(小目标),我们要增加小目标检测层,就可以在80 x 80 的上一步,也就是 160 x 160 尺寸增加。

增加了一层检测层之后,网络结构就变成了酱紫:

红框:这部分删除。原始网络结构中Neck部分是没有160 x 160的特征图,那么我们可以对 80 x 80的特征图再进行一次上采样,这样就得到160 x 160的特征图。
原来这一部分是网络的最后端,检测头可以直接获取特征图,现在增加一层检测层后就不是最后一层了,需要从它的下侧获取特征图。
蓝框:这一部分是我们新增的检测层,随着箭头往下走,先将Neck中80 x 80的特征图经过上采样变成160 x 160,这样就可以和Backbone中第2层160 x 160特征图进行concat融合,得到一个160 x 160的特征图。再往右走,然后经过下采样,又获得80 x 80的特征图( FPN + TPN)并经过C3传给检测头。
2.2 添加步骤
第①步 创建自定义yaml文件
首先创建yolov5s_add_one_layer.yaml文件。将下面一段代码粘贴上去:
- # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
- # Parameters
- nc: 80
- depth_multiple: 0.67
- width_multiple: 0.75
- anchors:
- - [4,5, 8,10, 22,18] # P2/4
- - [10,13, 16,30, 33,23] # P3/8
- - [30,61, 62,45, 59,119] # P4/16
- - [116,90, 156,198, 373,326] # P5/32
- # YOLOv5 v6.0 backbone
- backbone:
- [[-1, 1, Conv, [64, 6, 2, 2]], #0 卷积层 [64,320,320 ]
- [-1, 1, Conv, [128, 3, 2]], #1 卷积层 [128,160,160]
- [-1, 3, C3, [128]], #2 C3 [128,160,160]
- [-1, 1, Conv, [256, 3, 2]], #3 卷积层 [256,80,80]
- [-1, 6, C3, [256]], #4 C3 [256,80,80]
- [-1, 1, Conv, [512, 3, 2]], #5 卷积层 [512,40,40]
- [-1, 9, C3, [512]], #6 C3 [512,40,40]
- [-1, 1, Conv, [1024, 3, 2]], #7 卷积层 [1024,20,20]
- [-1, 3, C3, [1024]], #8 C3 [1024,20,20]
- [-1, 1, SPPF, [1024, 5]], #9 SPPF [1024,20,20]
- ]
- head:
- #neck
- #[512,20,20]
- [[-1, 1, Conv, [512, 1, 1]], #10 卷积层 [512,20,20]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #11 上采样 [512,40,40]
- [[-1, 6], 1, Concat, [1]], #12 Concat [1024,40,40]
- [-1, 3, C3, [512, False]], #13 C3 [512,40,40]
- [-1, 1, Conv, [256, 1, 1]], #14 卷积层 [256,40,40]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #15 上采样 [256,80,80]
- [[-1, 4], 1, Concat, [1]], #16 Concat [512,80,80]
- #[-1, 3, C3, [256, False]], # 被删了
- #下面是我们自己加的
- [-1, 3, C3, [256, False]], #17 C3 [256,80,80]
- [-1, 1, Conv, [128, 1, 1]], #18 卷积层 [128,80,80]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #19 上采样 [128,160,160]
- [[-1, 2], 1, Concat, [1]], #20 Concat [512,160,160]
- #head
- [-1, 3, C3, [128, False]], #21 C3 [128,160,160]
- [-1, 1, Conv, [128, 3, 2]], #22 卷积层 [128,80,80]
- [[-1, 18], 1, Concat, [1]], #23 Concat [512,160,160]
- [-1, 3, C3, [256, False]], #24 C3 [256,160,160]
- [-1, 1, Conv, [256, 3, 2]], #25 卷积层 [256,40,40]
- [[-1, 14], 1, Concat, [1]], #26 Concat [512,160,160]
- [-1, 3, C3, [512, False]], #27 C3 [512,40,40]
- [-1, 1, Conv, [512, 3, 2]], #28 卷积层 [512,20,20]
- [[-1, 10], 1, Concat, [1]], #29 特征融合 [1024,20,20]
- [-1, 3, C3, [1024, False]], #30 C3 [1027,20,20]
- [[21, 24, 27, 30], 1, Detect, [nc, anchors]], # 将21, 24, 27, 30传入检测头
- ]
我们来分析一下这段代码,这里主要是改了两个部分。
(1)修改Anchor:增加一组较小的anchor
- #---原始的anchors--#
- anchors:
- - [10,13, 16,30, 33,23] # P3/8
- - [30,61, 62,45, 59,119] # P4/16
- - [116,90, 156,198, 373,326] # P5/32
若输入图像尺寸=640X640,
- # P3/8 对应的检测特征图大小为 80X80,用于检测大小在 8X8 以上的目标。
- # P4/16对应的检测特征图大小为 40X40,用于检测大小在 16X16 以上的目标。
- # P5/32对应的检测特征图大小为 20X20,用于检测大小在 32X32 以上的目标。
- #---修改后的anchors---#
- anchors:
- - [4,5, 8,10, 22,18] # P2/4
- - [10,13, 16,30, 33,23] # P3/8
- - [30,61, 62,45, 59,119] # P4/16
- - [116,90, 156,198, 373,326] # P5/32
- 新增加的# P2/4 对应的检测特征图大小为 160X160,用于检测大小在 4X4 以上的目标
(2)修改Head部分:增加了一层网络结构
- #---原始的head部分---#
- head:
- [[-1, 1, Conv, [512, 1, 1]],
- [-1, 1, nn.Upsample, [None, 2, 'nearest']],
- [[-1, 6], 1, Concat, [1]], # cat backbone P4
- [-1, 3, C3, [512, False]], # 13
- [-1, 1, Conv, [256, 1, 1]],
- [-1, 1, nn.Upsample, [None, 2, 'nearest']],
- [[-1, 4], 1, Concat, [1]], # cat backbone P3
- [-1, 3, C3, [256, False]], # 17 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]],
- [[-1, 14], 1, Concat, [1]], # cat head P4
- [-1, 3, C3, [512, False]], # 20 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]],
- [[-1, 10], 1, Concat, [1]], # cat head P5
- [-1, 3, C3, [1024, False]], # 23 (P5/32-large)
- [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
- ]
YOLOv5中的Head包括Neck和Detect两部分。前两个阶段是向上concat,后两个阶段是向下concat,最后有三个检测层,分别是在17层下面、20层下面、23层下面。
- #---修改后的head部分---#
- head:
- #neck
- #[512,20,20]
- [[-1, 1, Conv, [512, 1, 1]], #10 卷积层 [512,20,20]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #11 上采样 [512,40,40]
- [[-1, 6], 1, Concat, [1]], #12 Concat [1024,40,40]
- [-1, 3, C3, [512, False]], #13 C3 [512,40,40]
- [-1, 1, Conv, [256, 1, 1]], #14 卷积层 [256,40,40]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #15 上采样 [256,80,80]
- [[-1, 4], 1, Concat, [1]], #16 Concat [512,80,80]
- #[-1, 3, C3, [256, False]], # 被删了
- #下面是我们自己加的
- [-1, 3, C3, [256, False]], #17 C3 [256,80,80]
- [-1, 1, Conv, [128, 1, 1]], #18 卷积层 [128,80,80]
- [-1, 1, nn.Upsample, [None, 2, 'nearest']], #19 上采样 [256,160,160]
- [[-1, 2], 1, Concat, [1]], #20 Concat [512,160,160]
- #head
- [-1, 3, C3, [128, False]], #21 C3 [128,160,160]
- [-1, 1, Conv, [128, 3, 2]], #22 卷积层 [128,80,80]
- [[-1, 18], 1, Concat, [1]], #23 Concat [512,160,160]
- [-1, 3, C3, [256, False]], #24 C3 [256,160,160]
- [-1, 1, Conv, [256, 3, 2]], #25 卷积层 [256,40,40]
- [[-1, 14], 1, Concat, [1]], #26 Concat [512,160,160]
- [-1, 3, C3, [512, False]], #27 C3 [512,40,40]
- [-1, 1, Conv, [512, 3, 2]], #28 卷积层 [512,20,20]
- [[-1, 10], 1, Concat, [1]], #29 特征融合 [1024,20,20]
- [-1, 3, C3, [1024, False]], #30 C3 [1027,20,20]
- [[21, 24, 27, 30], 1, Detect, [nc, anchors]], # 将21, 24, 27, 30传入检测头
- ]

首先在第17层,先将Neck中80 x 80的特征图经过上采样变成160 x 160,使得特征图继续扩大。
然后在第20层,将获取到的大小为160X160的特征图和Backbone中第2层的160 x 160的特征图进行concat融合,得到一个160 x 160的特征图,以此获取更大的特征图进行小目标检测。
最后在第31层,即检测层,增加小目标检测层,一共使用四层[21, 24, 27, 30]进行检测。
第②步 验证是否添加成功
将yolo.py文件中的配置改为刚刚我们配置好的yolov5s_add_one_layer.yaml,运行一下

这样就添加成功了~
(我去,计算量那么大的吗?)
PS:关于小目标检测的实验等我换完合适的数据集再发对比结果~