概述
YOLOv5的主要架构
Backbone(主干网络):负责提取输入图像的多层次特征
Neck(颈部网络):进行特征融合和多尺度特征处理,通常包含FPN(特征金字塔网络)和PAN(路径聚合网络)
Prediction(预测头):基于融合后的多尺度特征图进行目标预测,包括边界框回归、置信度预测和类别分类
关键组成总结
Backbone(主干网络)
- CSPNet:通过将特征图分为两部分,一部分直接传递,另一部分通过多个卷积层处理后再合并,减少了冗余计算,提升了模型效率
- Darknet:传统的YOLO系列采用Darknet作为主干,YOLOv5通过CSPDarknet优化了特征提取过程,进一步提高了性能
Neck(颈部网络)
- FPN:FPN通过自顶向下的路径,将高层次的语义信息传递到低层次特征图,增强了低层特征的表达能力,适用于检测不同尺度的目标
- PAN:PAN在FPN的基础上,加入了自底向上的路径,进一步聚合特征信息,增强了特征图之间的信息流动,提升了小目标的检测性能
- SPPF:SPPF模块通过多次最大池化操作,提取不同尺度的特征,并进行拼接和卷积处理,增强了模型对多尺度特征的捕捉能力,同时保持了计算效率
Prediction(预测头)
- 预测层:每个尺度的特征图对应一个预测层,预测输出包括边界框坐标(x, y, w, h)、置信度和类别概率
- 锚框机制:每个预测层使用预定义的锚框(Anchor Boxes)进行目标检测,适应不同尺寸和形状的目标
损失函数总结
- 边界框回归损失
- 目标置信度损失
- 类别分类损失
训练流程总结
- 标注数据,训练和验证数据进行分类,同时进行数据增强进一步提高模型的泛化能力
- 模型初始化,选择合适的模型,然后加载权重加速收敛
- 前向传播,输入图像通过主干网络提取特征,通过颈部网络进行特征融合,在不同尺度特征图上进行目标检测
- 损失计算,根据正负样本匹配结果,计算定位损失、置信度损失和分类损失,最后得到总损失
- 反向传播与优化,反向传播计算梯度,使用优化器(如Adam、SGD)更新模型参数
- 模型评估和调整,定期评估模型验证集的性能,根据评估结果调整学习率,损失权重等信息
推理过程总结
- 输入处理:针对输入的图像进行预处理,匹配模型输入要求
- 前向传播:图像通过主干网络、颈部网络进行特征提取和融合,从而实现在不同尺度上进行目标检测
- 后处理:
- 阈值筛选:过滤低置信度的预测框
- 非极大值抑制(NMS):去除重叠度高的预测框,保留最优的检测结果
- 边界框解码:将预测的边界框参数转换为实际图像坐标
- 输出结果:返回检测到的目标类别、边界框坐标和置信度
输入端
Mosaic增强
Mosaic增强的主要目的在于将多张图片进行组合,从而增加训练数据的多样性和复杂性,使得YOLO模型在训练过程中可以更好的学习到不同场景、不同尺度和不同位置的目标特征。这不仅提升了模型的检测精度,还增强了其在实际应用中的鲁棒性和泛化能力
分析1:基本原理是什么
多图像拼接
- 首先从数据集中随机选择四张图像,然后对这四张图像进行随机缩放,从而适应拼接后的整体布局
- 将四张缩放后的图像按照特定的方式(通常是四个象限)拼接成一张新的大图。例如,将四张图像分别放置在左上、右上、左下和右下四个位置,参考上面图片
边界框的调整
- 调整位置,因为图像被拼接到新的位置,原图像中的边界框需要根据新的图像布局进行位置调整
- 调整尺寸,因为在将图像拼接到四宫格中的时候无法避免有些图片会进行缩放,那么边界的尺寸的也是需要进行相应调整从而匹配新的图像尺度
实现增强效果
- 多样性增加:通过将不同图像的目标组合在一张图中,模型能够学习到更多样化的目标分布和背景环境
- 上下文信息丰富:目标可能出现在不同的上下文中,这有助于模型更好地理解目标的环境和相互关系
- 小目标检测提升:拼接后的图像可能包含更多的小目标,这有助于模型提升对小目标的检测能力
分析:具体实现分析
- 随机选择四张图像: 从训练数据集中随机选取四张不同的图像
- 调整图像尺寸: 对每张图像进行随机缩放,确保它们在拼接后能够合理地填充到新的大图中
- 确定拼接位置: 设定一个新的大图的尺寸(例如,原图的两倍),并确定每张图像在新图中的位置(左上、右上、左下、右下)
- 拼接图像: 将四张缩放后的图像按照预定的位置拼接成一张新的大图
- 调整和合并边界框: 根据每张图像在新图中的位置,调整各自的边界框坐标,并合并到新图中
- 在进行上述步骤后还是可以对图像进一步的增强,以增强数据的多样性
分析:拼接后的画布大小是实现预设好的吗?
拼接后的整体布局大小通常是预先设定好的。在 Mosaic 增强中,通常会设定一个固定的输出图像尺寸,例如原图的两倍(如 640x640 像素)。这个固定尺寸确保了拼接后的图像在模型训练过程中保持一致性,有助于模型更好地学习和泛化
也就是说,YOLO的实现中会定义一个固定的网格和画布尺寸,然后将四张图像按照指定的位置缩放填充到这个画布上,这样做的好处是确保所有拼接后的图像具有相同的输入尺寸,便于批量处理和加速训练过程
分析:不同照片进行拼接一定会导致留白区域,这些区域的处理方式如何
根据其处理步骤,首先对选中的图像进行随机缩放,然后将图片放到四宫格中,每个象限的具体位置和大小都是预先设计好的
那么处理留白区域的时候,在缩放和定位过程中,可能会出现某些区域的留白。这些留白通常会被填充为黑色(即像素值为0),或者采用与图像边缘相邻的颜色进行填充。留白区域虽然存在,但由于模型在训练过程中会见到各种组合和背景,通常不会对最终的检测性能造成显著影响
经查找YOLOv5源码,YOLOv5中默认使用的是114灰色
# 创建一张空白的大图像(画布)
mosaic_img = np.full((h, w, 3), fill_value=114, dtype=np.uint8)
分析:输入的使用使用四张图片合成一张图片,最后训练的是五张图吗?
结论:最后输入神经网络的图片不是五张,而是一张拼接后的Mosaic图像,四张原始图像被随机选取、缩放和拼接后,形成了一张新的综合图像(即 Mosaic 增强后的图像),这张综合图像作为最终的输入传递给神经网络
分析:通过Mosaic增强会导致数据减少吗?
不会导致数据减少,反而会导致数据增加从而被高效且多样化的利用,最终提高了训练样本的有效性和泛化能力
- 按批次加载数据
- 每一轮训练(epoch)都会将数据分成多个批次(batch),每个批次都有一组照片,假如我们总有10000张照片,那么每个批次的大小为16,那么总批次就是
- Mosaic增强数据(重点)
- 在每次加载一个批次的时候,从数据集中随机取出4张照片,然后将它们拼成一张Mosaic图像
- 那么当batch_size = 16的时候,也就是16张Mosaic图像,又因为每一张Mosaic图像都是由4张原始图像拼接而成,所以实际是又64张原始照片
- 随机选取4张照片组成
- 即使在第一个 epoch 没有完全遍历所有图片,后续的 epoch 会随机再次选取这些未被用到或未完全利用的图片
- 每张图片通过不同的 Mosaic 拼接组合,最终会多次出现在训练中,且背景、位置等都会变化
- 随机选取结合 shuffle,经过多个 epoch 后,每张图片都至少会被选中一次,同时还会出现在不同的 Mosaic 图像组合中
- 总结,随机选取是一定可以选取到全部的图片
综上所述,Mosaic 增强并不是减少了训练数据,而是多次动态组合训练数据。
自适应锚框
学习Yolov3原理时,也有类似于的预设锚框,该处主要解决自己针对于Yolov4自适应锚框的一些疑问
首先从锚框计算角度分析
- YOLOv3
- YOLOv5
- YOLOv5 引入了 自适应锚框计算,可以在训练前自动从数据集中分析目标框尺寸分布,并生成适合该数据集的锚框
- 使用 k-means 聚类或 k-means++ 聚类,结合 IoU 作为距离度量,动态生成锚框,不用像v3那样手动设置
然后分析锚框的生成方式
- YOLOv3
- 锚框的生成是基于 k-means 聚类 的,通常只计算一次,例如之前分析其是在COCO数据集上进行计算的
- YOLOv5
- YOLOv5 可以动态分析目标框的分布,并根据训练数据集生成锚框,后面详细讨论其生成的原理
锚框与网络层的分配
- YOLOv3
- 其使用的是多尺度特征图,在每个尺度上的分配特定的锚框
- 小目标:小尺寸锚框分配到高分辨率特征图(如13×13)
- 中目标:中尺寸锚框分配到中分辨率特征图(如 26×26)
- 大目标:大尺寸锚框分配到低分辨率特征图(52×52)
- YOLOv5
分析:自适应锚框的原理
数据统计
- 遍历训练数据集,收集所有目标框的宽高信息
- 将这些宽高信息进行归一化处理(通常相对于图像的尺寸进行归一化),以确保数据的统一性
- 类似于统计所有包裹的长和宽,发现它们的大小分布范围
聚类分析
- 使用 k-means 聚类算法 或 k-means++ 算法,对目标框的宽高比进行聚类分析
- 聚类的目的是将所有目标框划分为若干类,每一类的中心点代表一个锚框的宽高大小
- 使用IOU作为聚类的距离度量标准
- 优化箱子的尺寸,将包裹需要箱子主要分为三类,大中小
计算距离(IOU作为距离度量)
- IOU 越高,表示锚框越贴近目标框
- 在聚类过程中,k-means 会通过迭代更新,使得每个锚框(聚类中心点)与目标框的 IoU 达到最优
确定最终锚框
- 聚类完成后,选取每一类的中心点(也就是聚类的均值)作为最终的锚框尺寸
- 这些锚框就会作为模型的默认锚框,后续用于训练和推理
- 根据之前的统计结果,决定每种箱子的数量(例如各种箱子的比例),然后可以使用这些箱子去装包裹,从而确保大部分包裹都可以适配
自动调整(重点)
- YOLOv5 可以在训练时自动计算和推荐适合数据集的锚框配置
- 例如快递业务扩展到新城市(对应新数据集),你可以再次统计包裹尺寸并调整箱子配置,这个过程是自动化的,不需要每次手动测量和设计箱子
分析:锚框的尺寸开始已经计算完成,但是后面还需要自动化计算的原因?
YOLOv5在训练之前,也就是输入数据的时候,会通过自动化流程生成一组锚框,这些锚框在训练开始的时候就已经固定了,所以在输入数据的时候,锚框的尺寸也是已知的
后续的自动计算是为了验证和调整锚框
- 虽然之前已经根据输入数据锚框已经生成,但是锚框的尺寸不一定是最优的
- 需要注意的是这种检查和调整只会在训练开始之前进行,不会在训练过程中重复进行
锚框生成是训练前的优化步骤,目标检测模型的性能高度依赖锚框的设计。如果锚框与目标框的尺寸分布不匹配,会导致正样本数量不足,模型难以有效学习。因此,在训练前进行锚框的自动优化,确保它与数据集的目标框分布贴合,是提升模型性能的重要步骤
总结:自动计算锚框只发生在训练前,而在训练过程中,锚框是固定的,参与损失计算和目标检测
自适应图片缩放
使用自适应图片缩放的核心目的就是将输入图像缩小到一个统一大小,同事保持目标的比例以及位置信息不回失真,从而最终适配模型的输入要求
个人理解:自适应图片缩放是什么
例如我们要将一张照片打印到一个固定大小的相框中(例如该相框是20*20厘米),此时原始照片的比例如果是16 : 9,此时如果要是直接拉伸到20*20的话,势必会导致图像变形
所以,首先将图片等比缩小到宽或高最接近20厘米的一边(在很多图片调整工具中都可以看到类似效果),假如此时将图片调整到了高20厘米,宽度则是11.25厘米
最后为了让照片完整的放入相框,那么需要在左右填充空白,从而使其整体图像适配相框的大小
自适应图片缩放的主要步骤
- 等比例缩放:保证目标比例不失真、
- 边缘填充:将图像填充到固定尺寸
- 一致性处理:训练和推理阶段保持相同的缩放和填充方式
Backbone
Focus模块
6.0版本后不再使用,在YOLOv5主干网络中,主要用于高效的提取和处理输入图像的特征,其核心思想就是通过对输入图像进行切片操作,然后将图像的空间信息转化为通道信息,从而达到降采样的目的,同时尽可能的保留更多的细节特征
优缺点总结
优点总结
- 高效降采样
- Focus 模块通过切片和拼接操作实现降采样,比传统的卷积下采样(如 3×3 卷积加步幅 2)更加高效
- 这种操作减少了计算开销,尤其是在高分辨率输入的情况下
- 保留更多的细节信息
- Focus 模块将空间信息重新分配到通道维度中,避免了直接下采样导致的信息丢失
- 实现比较简单
- 模块实现仅需要简单的切片、拼接和卷积操作,易于集成到现有的网络架构中
缺点总结
- 特征选择有限
- Focus 模块的切片操作是固定的(即按像素位置切片),无法根据任务需求动态调整
- 很多情况下这种操作会忽略很多重要的特征区域
- 对特定任务的作用有限
- 虽然 Focus 模块在目标检测任务中表现优秀,但在其他任务(如图像分类)中未必有显著优势
该模块的主要步骤
- 输入图像
- 切片操作
- 对输入图像进行切片操作,也就是找到四个区域的像素点,分别是左上角、右上角、左下角、右下角像素点
- 通过该方式,将原始空间的信息压缩到通道维度上,原始输入 (640,640,3) 被切片为四个子图像,每个子图像的大小为(320,320,3)
- 通道拼接(下文详细分析)
- 将4个子图像的通道维度拼接在一起,形成一个新的张量,也就是( H/2 , W/2 , 4C)
- 注意此处的高和宽都缩小了一半,但是通道数扩大的了4倍
- 后续卷积操作
- 对拼接后的特征图执行1*1或者3*3的卷积,从而进一步提取特征和压缩通道数
分析:该模块主要干了什么事情
假设你在观察一幅地图,原始地图非常大(例如 640×640640 \times 640640×640 的分辨率),如果直接将地图缩小到 320×320320 \times 320320×320,一些细节可能会丢失
Focus的做法则是将地图切成四块(左上、右上、左下、右下),然后分别放在一起观察(拼接成新的图像),通过该方式不仅可以缩小视野,也可以更好的识别小物体对象
分析:该模块并不是简单的缩小(重点)
起初将该处理解成为类似于mosaic增强一样,将一张图片切割成四个小部分,然后再进行拼接的方法,但是该模块并不是直接对图像进行缩小,与其还是有本质区别
Focus 模块并不是将图像整个压缩到一个更小的分辨率,而是通过切片操作,将原始图像中的局部细节(像素点)重新组织到通道维度中,例如它将原始640*640图像分为四个区域,每个区域的大小就是320*320
- 左上角像素点:所有偶数行、偶数列的像素
- 右上角像素点:所有奇数行、偶数列的像素
- 左下角像素点:所有偶数行、奇数列的像素
- 右下角像素点:所有奇数行、奇数列的像素
这些切片区域不是简单的裁剪,而是将整幅图像的所有像素点重新分布到不同的通道中(从空间维度重新编码到通道维度)
例如,原始 640×640×3的图像,经过切片后变为 320×320×12(通道数增加到 12),从而完全保留了原始的像素信息
可以简单理解为,有640个文件,原本是逐个铺开,这样的话太占据空间,那么将其分为四个区域,每个区域中制作一个类似于多层抽屉的存储夹(拓展通道),这样就实现了将所有文件分别放入四个区域,同时不损失任何文件信息
分析:该模块会导致原图像发生变化吗
Focus 模块不是将相同颜色的像素放在一起,而是将原图像的所有像素信息重新组织到通道维度中,原始信息不会丢失,它的核心目标就是通过降采样和信息重组,保存细节的同时提高效率
原始的书柜(640 层)存储着书籍,Focus 模块将书按照奇偶顺序重新分组,比如奇数层的书分到左上通道,偶数层的书分到右上通道,最终所有书都存储到了新的维度(通道)中。书柜整体被压缩,但书的信息没有丢失。
结合开始图片理解
- 输入图片然后进行切片操作
- 左上:取所有奇数行和奇数列的像素
- 右上:取所有奇数行和偶数列的像素
- 左下:取所有偶数行和奇数列的像素
- 右下:取所有偶数行和偶数列的像素
- 拼接通道,整体缩小两倍,通道扩大4倍,保留了图像的所有细节
CSP结构
理解CSP
传统模型中没有CSP模块的时候,就像一个团队每一个人负责一些任务,最终合并这些成功,但是这种分配方式很容易出现问题,因为如果一个人出错,那么整个流程都会受到影响,因为每个人需要处理自己领到任务的所有信息,所有任务都是相互耦合的,最终导致效率低
CSP则是将任务分成两个部分,一部分是复杂任务让一部分人去负责,另一部人则是简单任务再安排一拨人负责
这样两组人独立完成工作,最终汇报任务成果,这个就类似于CSP中的拼接特征Concat,最终再统一优化一些细节,也就是最后卷积层的融合特征
CSP的核心作用分析
首先是分流,将工作拆分成复杂任务(也就是需要深度处理的特征路径)和简单任务(也就是需要保留原始特征路径),从而减少冗余计算。这个就像一些任务直接完成,不需要重复操作,可以提高效率
其次是并行,两条路径可以同时工作,类似于分组合作,大大减少了单线程的工作负担
最后进行融合,两组人(两条路径)的成果最终通过拼接(Concat)和优化(卷积)整合在一起,确保整体效果
通过源码中的两个类(C3类和Bottleneck类分析其关系)
C3类就是CSP结构,类似于厨房的整体布局和工作流程,负责将食材分流处理并最终组合,确保效率和质量
Bottleneck类则类似于厨房中具体的准备和烹饪步骤,负责对食材进行详细加工,提升菜品的复杂度和风味
分析C3类(CSP结构)
- 输入通道c1:类似于食材接收区,所有的食材和原材料首先被送到厨房的接收区中
- 分流处理:
- 主处理路径:经过多个处理步骤,类似于对食材进一步的进行深加工
- 保持原始状态:这条路径快速通过一些简单的准备步骤,保持食材的原始风味和质地
进一步分析C3结构的源码
前向传播过程分析
- 输入数据后,分别经过两条路径对参数进行处理
- 最后c1和c2在通道维度上进行拼接,最后通过cv3基进行最后的1*1卷积,得到最终的输出
Bottleneck模块
- 输入的x先通过self.cv1和self.cv2进行两次卷积交换
- 如果self.add为True的时候,那么就将变换后的输出与原始输入x进行相加,从而实现残差连接
- 如果self.add为False,那么就返回变换后的输出
class Bottleneck(nn.Module):# 标准瓶颈层,常用于减少参数并提高模型的计算效率def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansionsuper().__init__() # 调用父类构造函数进行初始化c_ = int(c2 * e) # 计算中间层的通道数,通过扩展系数 `e` 控制self.cv1 = Conv(c1, c_, 1, 1) # 第一个 1x1 卷积,用于降维self.cv2 = Conv(c_, c2, 3, 1, g=g) # 第二个 3x3 卷积,用于特征提取# 判断是否启用捷径连接(shortcut),仅当 `c1` 等于 `c2` 且 `shortcut` 为 True 时启用self.add = shortcut and c1 == c2def forward(self, x):# 如果 `self.add` 为 True,则返回输入 `x` 与经过两个卷积层后的输出的相加结果(残差连接)# 否则,仅返回卷积后的输出return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
总结
- C3 类: 管理整体的工作流程,分流和组合特征(食材)
- Bottleneck 类: 处理细节,增强和优化特征(食材的准备和烹饪步骤)
SPPF
基本了解
其是一种常用的多尺度特征池化方法,旨在通过不同尺度的池化操作捕捉图像的多层次信息。相比于SPP,SPPF在保持类似性能的同时,通过优化池化操作,显著提高了计算速度和效率
主要功能
- 多尺度特征捕捉:通过不同大小的池化窗口,捕捉图像的多尺度信息,增强模型对不同大小目标的检测能力
- 保持空间信息:通过最大池化操作,保持特征图的空间信息,有助于后续的特征融合和检测
- 高效计算:优化的池化策略减少了计算开销,提升了模型的推理速度】
理解(SPPF模块)
- 初级过滤(conv1):通过1x1卷积将原水(输入特征图)进行初步处理,调整水的流量(通道数)
- 多级过滤(p1, p2, p3):依次通过三层不同的过滤器(池化操作),捕捉不同大小的杂质(特征信息)
- 特征拼接(拼接水流):将初级过滤后的水流与各级过滤后的水流进行混合,确保水质的全面净化(多尺度特征融合)
- 最终处理(conv2):通过1x1卷积将混合后的水流进行最终处理,得到纯净的出水(融合后的特征图)
模块分析
import torch
import torch.nn as nn
import warningsclass SPPF(nn.Module):# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocherdef __init__(self, c1, c2, k=5):"""初始化SPPF层 (Spatial Pyramid Pooling - Fast),用于YOLOv5模型中。SPPF层通过多次最大池化操作,将不同尺度的信息融合,增强特征表示能力。参数:c1 (int): 输入特征图的通道数 (input channels)。c2 (int): 输出特征图的通道数 (output channels),即最终卷积层的输出通道数。k (int): 最大池化层的核大小 (kernel size),默认为5,表示使用的池化核大小为k=(5, 9, 13)。"""super().__init__() # 调用父类 nn.Module 的初始化方法,初始化层和参数c_ = c1 // 2 # 计算隐藏通道数,通常取输入通道数的一半。这用于第一层卷积。# 第一层卷积:将输入通道数c1压缩为c_,即输入特征图的通道数减少一半self.cv1 = Conv(c1, c_, 1, 1) # 1x1卷积,作用是减少通道数,并保持空间大小# 第二层卷积:将拼接后的特征图通道数转换为输出通道数c2self.cv2 = Conv(c_ * 4, c2, 1, 1) # 1x1卷积,输出特征图通道数为c2# 最大池化层:定义了大小为k的池化核,步长为1,填充为k的一半self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)def forward(self, x):"""前向传播函数,定义了SPPF层的数据流。该函数通过两次最大池化和卷积层处理输入特征图,并融合多个尺度的信息。参数:x (Tensor): 输入特征图,形状为 (batch_size, c1, h, w),其中c1为输入通道数。返回:Tensor: 经过SPPF层处理后的输出特征图,形状为 (batch_size, c2, h, w)。"""# 第一次卷积操作,将输入特征图x的通道数从c1压缩为c_(通常为c1的一半)x = self.cv1(x)# 抑制一些警告,防止在使用max_pool2d时产生冗余警告with warnings.catch_warnings():warnings.simplefilter('ignore') # 忽略池化操作中的一些警告(特别是来自torch 1.9.0版本的警告)# 进行最大池化操作,首先对x进行池化y1 = self.m(x) # 第一次池化y2 = self.m(y1) # 第二次池化,池化两次后可以捕捉更大范围的信息# 拼接原始输入x和两次池化后的特征图(x, y1, y2, 经过第二次池化后的y2),# 然后通过第二个卷积层进行处理,最后返回输出特征图return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1)) # 拼接后的特征图通过1x1卷积进一步处理
Neck网络
该处的网络结构(即通过FPN和PAN协同进行工作)与YOLOv3中的上采样和下采样原理一样
下文是yolov3中相关原理(参考理解)
上采样与下采用
YOLOv3中上采样和下采样是配合使用的,其中下采样是在BackBone中完成的,上采样则是在Neck层中完成,该处进行整合一同学习
理解下采样与上采样
下采样就类似于如何我们想要在电子地图上看到这个城市的全貌,我们此时需要会使用一张缩略图(类似于深层特征图),因为缩略图能快速提供全局信息(大目标的语义信息),但是通过缩略图会失去一些细节,也就是会忽略小目标的信息
上采样则是想要了解城市中的某个街道,那么就需要将图片放大到跟高的分辨力,放大后,你还需要结合更详细的原始地图(类似于拼接浅层特征),才能获得准确的细节信息
下采样的实现
通过Backbone多次卷积核池化操作对输入的图像进行下采样,每次下采样都会将特征图尺寸缩小一半,最终得到13*13的深层特征图
- 13×13(下采样 32 倍):深层语义特征,用于检测大目标
- 26×26(下采样 16 倍):中层特征,用于检测中目标
- 52×52(下采样 8 倍):浅层特征,用于检测小目标
上采样
- 13×13 的深层特征图 → 上采样到 26×26 → 与 Backbone 输出的 26×26 特征图拼接
- 26×26 的特征图 → 上采样到 52×52 → 与 Backbone 输出的 52×52 特征图拼接
上采样的主要目的是将低分辨率特征图放大到更高分辨率,从而能够与浅层的高分辨率特征图进行融合,这样非常有利于检测小目标,因为小目标在低分辨率特征图中可能已经失去了关键信息
上采样后,与中层或浅层特征图进行拼接(Concat),融合语义信息和空间细节信息,从而提升对不同尺度目标的检测能力
特征拼接
理解
深层特征图类似于全局地图,适合宏观看全局,知道整体布局,但是缺乏细节;浅层特征图则类似于一个局部放大镜,更加适合观察细节,但是难以观察全局
比如你在找一座城市中的某个建筑物(目标检测),全局地图告诉你大概在哪个区域(深层特征图的语义信息),放大镜帮助你找到建筑物的具体位置和形状(浅层特征图的细节信息),将两者结合后,你既能快速找到建筑物的区域,又能精确确定它的形状和位置
特征图拼接实现分析
低分辨率的深层特征图和高分辨率的浅层特征图都来自于Backbone中
通过按通道维数进行拼接,也就是在特征图的通道上进行连接,例如上采样和下采样都是13*13的图,最后拼接后的只会是通道数增加,但是其图片的分辨率不变
拼接后的特征图通过卷积操作(CBL,卷积 + BN + Leaky ReLU)进一步提取信息,最终用于检测中目标
特征拼接的优点
提升网络对小目标的检测能力,小目标的信息通常在深层特征图中被稀释甚至丢失,特征拼接通过引入浅层特征图中的高分辨率信息,弥补了深层特征图的不足
同时可以提高目标定位的准确性,深层特征图中虽然包含语义信息,但定位能力较弱,拼接浅层特征图后,网络可以结合细节信息,更精确地定位目标的位置
卷积调整
理解
主要作用是优化,对合并后的信息进行归纳总结,去掉多余的噪声和重复部分,从而使得信息更加的准确
从Neck层整体理解该处的作用,例如一个团队项目正在整理与优化
- 假设两个小组
- 一个小组提供高层次的战略规划(深层特征图)
- 另一个小组提供具体的实施细节(浅层特征图)
- 拼接相当于将两个小组的报告合并成一份综合报告
- 卷积调整相当于一个总负责人对这份报告进行整理和提炼,最终生成一份清晰简洁的方案,供领导决策(目标检测)
具体实现分析
输入
- 拼接后的特征图(例如,来自上采样后的 26×26 特征图和浅层的 26×26 特征图拼接)
- 假设拼接后特征图的通道数为 512
卷积操作
- 1×1 卷积:用于减少通道数,降低计算量
- 3×3 卷积:用于捕捉局部空间特征
- 最终将特征图的通道数调整到适合输出的形状(如 256)
输出
- 调整后的特征图仍然是 26×26,但通道数已经减少,信息更集中
目标框回归
因为在目标检测任务中,除了识别出目标物体的类别外,准确定位目标的位置是一样重要。目标框回归可以使得模型不仅直到图像中存在什么物体,同时还可以确定它们具体位于图像的哪个部分
简单来说,模型如果需要预测边界框那么就需要四个参数
- 中心坐标(x, y):边界框中心点相对于图像的比例位置
- 宽度和高度(w, h):边界框的宽度和高度,相对于图像尺寸的比例
原理分析
1. 网格划分与锚框机制
首先就是将输入图像划分为S×S的网格,每个网格负责检测位于其范围内的目标。每个网格单元预测多个锚框,每个锚框对应一个潜在的目标
锚框也就是Anchor Boxes,参考YOLOv3原理中的锚框设定以及自适应锚框章节的知识。每个网格单元预测多个锚框,其中每个锚框都有以下参数
- 偏移量(tx, ty):相对于网格单元左上角的中心点偏移
- 宽度和高度(tw, th):相对于锚框预定义尺寸的缩放因子
- 置信度(to):表示该锚框包含目标的置信度
- 类别概率(tc):表示目标属于某个类别的概率
2. 通过网络预测上述参数,然后将它们转换为实际的边界框坐标
中心坐标预测(tx,ty):使用sigmoid激活函数将tx和ty限制在[0, 1]之间,(cx,cy)是网格左上角的坐标
宽度和高度预测(tw , th):也就是预测框的锚框尺寸
最后计算置信度与类别概率:使用sigmoid激活函数将置信度限制在[0, 1]之间,类别概率通过softmax或者sigmoid函数进行计算
3. 损失函数
- 定位损失
- 通过使用CIoU(Complete IoU)或GIoU(Generalized IoU)损失来衡量预测边界框与真实边界框之间的差异
- 通过上述计算损失,考虑边界框的重叠程度、中心点距离以及宽高比例
- 置信度损失
- 使用二元交叉熵损失衡量预测的置信度与真实标签(0或1)之间的差异
- 分类损失
- 使用交叉熵损失或其他适当的损失函数来衡量预测的类别概率与真实类别标签之间的差异
4. 进入后处理阶段以及非极大值抑制(NMS)
在预测完成后,YOLOv5对生成的边界框进行后处理,包括置信度筛选和非极大值抑制(NMS),以去除冗余的重叠框,仅保留最优的预测结果
理解(重要)
假设你是一个学校的保安,负责监控中心内的人群流动。你使用一个摄像头监控商场的某个区域,希望识别并标记出进入商场的每一个人
摄像头捕捉图像
- 摄像头相当于YOLOv5的输入图像
划分监控区域
- 你将监控区域划分为多个小区域(网格),每个小区域负责观察和记录其内是否有新的人进入
预定义观测框(锚框)
- 你为每个小区域预设几个不同大小的观测框,分别适用于不同体型的人(例如小孩、高中生、大学生、老人)
检测和记录
- 当有新的人进入某个小区域时,你记录下他们的位置和体型
- 中心坐标(x, y):记录每个人站立的位置
- 宽度和高度(w, h):估算每个人的体型大小
- 置信度:评估该记录是否准确,例如是否有遮挡或模糊
优化与调整
- 根据记录的准确性,你调整观测框的位置和大小,以提高未来记录的精度
去除冗余记录
- 如果在同一个小区域内有多个观测框记录到相同的人,你选择最准确的一条记录,去除其他重复的记录
正负样本匹配
目标检测任务中,模型需要识别图像中的目标物体并准确定位其边界框。在训练过程中,模型生成的多个预测框需要与真实的目标框进行匹配,以确定哪些预测框是有效的(正样本),哪些是无效的(负样本),上述的过程就是正负样本匹配
- 正样本:与真实目标框匹配度较高的预测框,负责学习目标的定位和分类
- 负样本:与任何真实目标框匹配度较低的预测框,负责学习背景的区分
理解(重要)
例如通过监控设备检测教室中的学生,从而达到考勤的目的
- 输入图像,然后将教室划分成多个网格
- 根据与设定的观察框,得到锚框
- 识别出勤学生
- 正样本:观察框内有学生出现
- 负样本:观察框内无学生出现
- 记录学生位置和出勤状态:对于正样本,记录学生的具体位置和出勤
- 过滤冗余记录
- 如果多个观察框覆盖同一个学生,选择最准确的记录,避免重复记录
正负样本匹配机制
锚框和网格划分
首先预定义几个锚框,通过K-Means聚类算法在训练数据集上预先计算得到,从而可以适应数据集中的目标尺寸分布
然后将输入的图像划分为多个网格,每个网格单元负责检测位于其范围内的目标,每个网格单元会预测多个锚框,每个锚框包含边界框坐标、置信度和类别概率。
匹配策略分析
- 计算IOU:预测框与真实框的交集面积除以预测框与真实框的并集部分
- 确定正样本
- 最大IoU匹配:对于每个真实目标框,选择与之具有最高IoU的预测锚框作为正样本
- 阈值匹配:若预测锚框与真实目标框的IoU超过设定的阈值(如0.5),则将其视为正样本
- 确定负样本
- 未匹配的锚框:未被任何真实目标框匹配且IoU低于阈值的预测锚框被视为负样本
- 背景学习:负样本用于学习区分背景与目标,提高模型的分类能力
具体实现步骤总结
- 预定义锚框:在不同尺度的特征图上预定义多个锚框
- 真实目标分配:将每个真实目标框分配给具有最高IoU的锚框作为正样本
- 筛选负样本:将未被任何真实目标框匹配且IoU低于阈值的锚框视为负样本
- 限制正负样本数量:为了平衡正负样本的比例,YOLOv5可能对正负样本的数量进行限制
- 损失函数计算:基于正负样本,分别计算定位损失、置信度损失和分类损失
实现原理
1. 锚框匹配算法
初始化锚框,锚框通常通过K-Means聚类在训练数据集上预先计算得到。YOLOv5在不同尺度的特征图上使用不同的锚框尺寸,以适应不同大小的目标(参考自适应锚框)
计算IOU,对于每个真实目标框,计算其与所有预测锚框的IoU值
选择匹配锚框,将每个真实目标框分配给与之IoU最高的锚框作为正样本。若该锚框的IoU低于设定阈值,则可能不被视为正样本
处理重叠匹配,一个锚框只能对应一个真实目标框,即避免多个真实目标框匹配到同一个锚框
2. 负样本筛选
- IoU阈值:通常设定一个IoU阈值(如0.5),低于该阈值的锚框被视为负样本
- 背景学习:负样本用于训练模型识别背景,提高模型的分类能力,减少误报
3. 损失函数与权重
- 定位损失
- 注意这里仅仅针对正样本计算,主要用于衡量预测边界框和真实框的差异,一般常用的损失函数有CIoU损失或GIoU损失
- 置信度损失
- 此处针对正样本和负样本都需要进行计算,主要用于衡量预测置信度与真实标签之间的差异
- 该处主要使用二院交叉熵损失
- 分类损失
- 该处仅对正样本计算,用于衡量预测类别概率与真实类别标签之间的差异
- 使用交叉熵损失或Focal Loss
激活函数
SiLU函数
分析1:YOLOv5采用SiLU激活函数原因分析
如果将这两个激活函数作为调味品的话,那么两者就可以理解成如下
- ReLU 激活函数就像简单的盐,快速、高效,但不够细腻(对负值直接截断)
- SiLU 激活函数像复合调味料,可以根据输入的特征调整输出(对负值调整后输出一个小值),从而使得整体模型更加灵活
激活函数作用回顾
神经网络的每一层是一个线性变换(如矩阵乘法),如果没有激活函数,整个网络就会退化为一个线性系统(无论有多少层,整体还是线性变换)。激活函数通过引入非线性,使得网络能够学习复杂的非线性关系,从而提升模型表达能力
同时激活函数可以提高模型的泛化能力,激活函数可以帮助模型在训练的时候更好的拟合数据,同时在测试的时候对未见数据有更好的泛化能力
- ReLU 会在负值区域“截断”梯度,可能导致梯度消失问题
- SiLU 的梯度连续且不会为 0,这有助于稳定梯度传播,提高训练效果
分析2:激活函数存在于YOLOv5的整体过程
输入层到卷积层,输入图像会先通过一系列的卷积操作,每个卷积层的输出会经过SiLu激活函数,提取多尺度的特征
特征提取阶段,Backbone中SiLU激活函数应用于每个卷积模块的输出,主要用于增强特征表达能力
检测头阶段,检测头中,分类和回归部分的中间层也会使用 SiLU 激活函数,增强检测性能
输出阶段
- 边界框回归:通常使用线性变换
- 类别置信度预测:使用 Sigmoid 将结果限制在 [0, 1] 范围内
损失函数
损失函数即衡量模型预测结果和真实标签之间差异的函数,因为在目标检测任务中,损失函数不仅需要考虑目标的分类准确性,还需要关注目标的位置和大小的精确度
主要组成部分
- 边界框回归损失:确保预测的边界框准确的覆盖目标物体
- 目标置信度损失:评估预测边界框是否包含目标
- 类别分类损失:准确识别目标物体的类别
分析:正负样本匹配在损失函数中的作用
首先正负样本匹配在损失函数中的作用
- 定位损失:仅对正样本计算,优化边界框的准确性
- 目标置信度损失:对正样本和负样本都计算,正样本希望置信度接近1,负样本希望接近0
- 分类损失:仅对正样本计算,优化目标类别的准确性
目标置信度损失
目标置信度损失用于衡量预测边界框是否包含目标,YOLOv5中使用二元交叉熵损失来计算预测置信度和真实标签之间的差异
- y 是真实标签(1表示包含目标,0表示不包含)
- y^是预测的置信度
类别分类损失
类别分类损失用于衡量预测类别概率与真实类别标签之间的差异,YOLOv5通常使用交叉熵损失或Focal Loss来计算这一部分的损失
边界框回归损失
边界框回归损失用于衡量预测的边界框与真实边界框之间的差异,YOLOv5常使用**CIoU或GIoU作为边界框回归的损失函数
CloU损失不仅考虑了预测框和真实框的重叠程度也就是IOU,同时还考虑到了中心点距离和宽高比例差异,从而进一步提升了边界框回归的精确度
GIoU损失在IoU基础上引入了最小闭包框的面积差,解决了IoU在没有重叠时梯度为零的问题
注:部分图片来源于“炮哥带你学”