ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLOv1到v5演进全解析:从单阶段检测原理到工业部署实战

YOLOv1到v5演进全解析:从单阶段检测原理到工业部署实战 目标检测这个领域要说过去几年里哪个系列的工作被讨论得最多、被改得最狠、被部署得最广YOLO 系列绝对排得进前三。从 2015 年 Redmon 扔出第一版开始到 2020 年 YOLOv5 以工程化姿态横扫工业界这条线几乎串起了单阶段检测器从学术概念走向产线落地的全过程。我最早接触 YOLO 是做一个小目标检测项目当时拿 v1 的论文对着代码啃被那个把检测当回归做的思路震了一下后来一路跟到 v5踩过的坑从损失函数不收敛到 anchor 尺寸对不上基本把每一代的脾气都摸了一遍。这篇就把 v1 到 v5 这条演进线拆开讲重点不是复述论文而是讲清楚每一代到底改了什么、为什么这么改、实际用的时候哪些地方最容易翻车。不管你是刚入门想搞懂 YOLO 到底怎么回事还是已经在用 v5 训自己的数据集但总感觉哪里不对应该都能从里面找到点有用的东西。1. 从 YOLOv1 说起把检测塞进一个回归网络到底意味着什么1.1 为什么一个网络一次前向在当时是个激进想法在 YOLO 出来之前主流的目标检测走的是两阶段路线典型代表就是 R-CNN 那一套先想办法生成一堆候选区域再对每个区域单独做分类和边框回归。这套流程精度确实高但问题也很明显——候选区域生成和后续分类是分开的推理速度慢得让人难受一张图跑几秒是常态根本没法做实时。YOLOv1 的核心主张就是检测这件事本质上可以看成一个回归问题。把整张图丢进一个卷积网络网络直接输出每个网格里有没有目标、目标框在哪、是什么类别。整个流程只有一个网络、一次前向没有候选区域生成这一步。这个思路在当时是相当激进的因为大家普遍认为检测需要精细的两阶段处理才能保证精度。我个人的理解是v1 真正的贡献不在于精度而在于它证明了端到端单阶段检测这条路是走得通的。它把检测的速度拉到了实时级别虽然精度上比当时的两阶段方法差了一截但速度优势太明显了很多对实时性有要求的场景一下子就有了可用的方案。1.2 网格划分与一个格子只负责一个目标的硬约束v1 的做法是把输入图像划分成 S×S 个网格论文里用的是 7×7。每个网格负责预测 B 个边界框每个框包含 5 个值中心点坐标 x、y宽高 w、h以及一个置信度 confidence。同时每个网格还要预测 C 个类别的条件概率。这里有个关键约束一个网格只负责预测一个目标。也就是说如果两个目标的中心点恰好落在同一个网格里网络就只能预测其中一个另一个会被漏掉。这是 v1 最被人诟病的地方之一对小目标和密集目标特别不友好。置信度的定义也值得说一下它是这么算的confidence Pr(Object) * IoU(pred, truth)如果网格里没有目标Pr(Object) 就是 0置信度也就是 0如果有目标置信度就等于预测框和真实框的 IoU。这个设计的意思是让网络学会我预测的这个框有多靠谱。1.3 损失函数里那些容易被忽略的权重设计v1 的损失函数是整篇论文里最需要细看的部分因为它直接决定了训练能不能收敛。损失由三部分组成坐标损失、置信度损失、类别损失。但这三部分的权重是不一样的。坐标损失用了 λ_coord 5 的权重置信度损失里对没有目标的框用了 λ_noobj 0.5 的权重。为什么要这么设计因为一张图里大部分网格是没有目标的如果不对无目标框的置信度损失降权网络会倾向于把所有框的置信度都预测成 0这样损失最小但模型就废了。反过来坐标定位是检测的核心权重给大一点让网络更关注定位精度。还有一个细节w 和 h 的损失用的是平方根而不是直接平方。原因是同样大小的绝对误差对大框来说影响小对小框来说影响大。用平方根可以缓解这个问题让网络对小框的定位误差更敏感一些。这个技巧在后面几代里被保留了下来直到 v5 换了新的损失计算方式。提示如果你自己复现 v1损失函数里的这几个权重系数千万别随手改尤其是 λ_noobj改大了模型学不到东西改小了会满屏假框。1.4 v1 的硬伤定位不准、召回低、密集场景崩v1 的问题在实战里暴露得很清楚。我当年拿它跑一个街景检测的任务最大的感受就是定位精度不够框总是差那么一点尤其是小目标基本检测不到。原因有几个一是每个网格只能预测一个目标密集场景直接漏检二是 7×7 的网格分辨率对现代图像来说太粗了三是损失函数对定位的约束还不够强。另外 v1 对非标准长宽比的目标也不太友好因为它的框是从数据里学出来的没有 anchor 这种先验遇到训练集里少见的形状就容易跑偏。这些问题在后面几代里被逐步解决但 v1 作为开山之作它的框架思路是后面所有版本的基础。2. YOLOv2 的工程化补课anchor、BN 与多尺度训练2.1 引入 anchor boxes从学框到调框v2 最大的改动是引入了 anchor boxes这个思路其实是从 Faster R-CNN 那边借过来的。v1 是直接预测框的绝对坐标网络需要从零学出一个框应该长什么样难度大。v2 改成预设一组 anchor网络只需要预测相对于 anchor 的偏移量学习难度一下子降下来了。anchor 的尺寸不是拍脑袋定的v2 用 k-means 聚类在训练集上跑出来的。聚类的距离度量用的是 IoU 而不是欧氏距离因为框的相似度用 IoU 衡量更合理。论文里聚出来 5 个 anchor覆盖了常见的形状分布。这个做法后来成了标配v5 里也是用类似的方式生成 anchor。我实际用下来anchor 尺寸对检测效果的影响非常大。如果你的数据集里目标形状比较特殊比如特别细长的或者特别扁的一定要重新聚类 anchor直接用默认的 COCO anchor 效果会差不少。2.2 Batch Normalization 带来的训练稳定性提升v2 在所有卷积层后面都加了 Batch Normalization这个改动看起来不起眼但效果很实在。BN 把每层的输入分布拉回标准正态附近缓解了内部协变量偏移的问题训练更稳收敛更快而且可以去掉 dropout 这种正则化手段。从工程角度讲BN 让学习率的设置变得不那么敏感了以前调学习率要小心翼翼加了 BN 之后容错空间大了很多。这也是为什么 v2 的 mAP 比 v1 提升了十几个点其中 BN 贡献了相当一部分。2.3 多尺度训练让模型适应不同分辨率v2 还有一个很实用的技巧多尺度训练。每迭代若干次就随机换一个输入分辨率继续训。这样训出来的模型对不同尺寸的输入都有适应性推理的时候可以根据速度要求灵活调整输入大小。这个技巧在实际部署里特别有用。比如你在服务器上跑可以用大分辨率追求精度如果部署到边缘设备上换成小分辨率就能提速。同一个模型权重不用重新训练就能适配不同场景省了很多事。2.4 Darknet-19 与 v2 的整体架构取舍v2 的骨干网络换成了 Darknet-1919 个卷积层加 5 个 max pooling 层。相比 v1 的 24 层卷积Darknet-19 更轻量但通过 BN 和更好的结构设计特征提取能力反而更强。v2 还去掉了 v1 里的全连接层改成全卷积结构这样输入尺寸就可以灵活变化了。这个改动配合多尺度训练让 v2 在速度和精度之间有了更好的平衡。整体来说v2 是一次非常成功的工程化补课把 v1 的很多硬伤都补上了。3. YOLOv3 的多尺度检测FPN 思路的引入与骨干升级3.1 Darknet-53更深的骨干与残差连接v3 把骨干换成了 Darknet-5353 个卷积层并且引入了残差连接。残差连接的好处是让梯度能更好地回传深层网络也能训得动。Darknet-53 在 ImageNet 上的分类精度和 ResNet-152 接近但速度快了一倍多这个性价比在当时是很有竞争力的。残差结构在 v3 里的使用方式比较直接就是标准的 shortcut 连接。我自己的经验是这种结构对训练稳定性的提升很明显尤其是当你把网络加深的时候没有残差很容易出现梯度消失或者退化问题。3.2 三个尺度的特征图小目标检测的转折点v3 最重要的改动是多尺度检测。它从骨干网络的不同深度引出三个尺度的特征图分别是 13×13、26×26、52×52分别负责检测大、中、小目标。这个思路借鉴了 FPN特征金字塔网络通过上采样和特征拼接让浅层的高分辨率特征和深层的强语义特征融合在一起。这个改动对小目标检测的提升是决定性的。v1 和 v2 在小目标上表现都很差v3 通过 52×52 的高分辨率特征图让小目标有了专门的检测分支。我当年做小目标检测项目的时候从 v2 换到 v3召回率直接上了一个台阶。三个尺度的 anchor 分配也有讲究每个尺度分配 3 个 anchor一共 9 个。anchor 的尺寸还是通过 k-means 聚类得到的大尺度特征图配小 anchor小尺度特征图配大 anchor这样每个尺度的检测分支都能专注于自己擅长的目标尺寸范围。3.3 分类头改用 Logistic 回归多标签分类的考量v3 把分类的 softmax 换成了 logistic 回归也就是每个类别独立做二分类。这个改动的原因是现实场景里一个目标可能同时属于多个类别比如人和骑手可能同时成立。softmax 是互斥的只能选一个logistic 可以多标签同时激活。这个改动在实际使用里影响挺大的尤其是做多标签数据集的时候。不过如果你的数据集是单标签的这个改动带来的差异不会太明显但也不会有什么坏处。3.4 v3 的实战表现与遗留问题v3 在速度和精度之间找到了一个很好的平衡点在当时的工业界被广泛采用。我身边很多做检测的朋友v3 是他们的主力模型一直用到 v4、v5 出来才换。但 v3 也不是没有遗留问题。它的 anchor 匹配策略还是基于 IoU 的静态匹配对某些特殊形状的目标不够灵活损失函数里定位和分类的权重还是手工调的没有自适应机制另外 v3 的训练技巧相对朴素没有太多数据增强和正则化的手段。这些问题在 v4 和 v5 里被进一步优化。4. YOLOv4 与 YOLOv5训练技巧的集大成与工程化落地4.1 v4 的堆料哲学Bag of Freebies 与 Bag of Specialsv4 的论文读起来像一篇综述它把当时能用的训练技巧几乎全堆上去了。作者把这些技巧分成两类Bag of Freebies不增加推理成本的训练技巧和 Bag of Specials增加少量推理成本但提升精度的模块。Bag of Freebies 里包括 Mosaic 数据增强、CutMix、DropBlock、标签平滑、CIoU 损失等等。Bag of Specials 里包括 SPP 模块、PAN 结构、Mish 激活函数、CBAM 注意力等等。这些技巧单独看都不新鲜但组合在一起效果确实好v4 在 COCO 上的 mAP 比 v3 提升了一大截。Mosaic 数据增强是 v4 里最实用的技巧之一它把四张图拼成一张让模型在一张图里看到更多样的目标组合和背景。这个技巧对提升模型的泛化能力很有帮助尤其是小数据集上效果明显。我自己的经验是加了 Mosaic 之后模型在验证集上的表现稳定了不少过拟合的情况也减轻了。4.2 v5 的工程化定位为什么它成了工业界首选v5 严格来说不是一篇学术论文而是一个工程实现。它的核心贡献不在于算法创新而在于把整个训练、推理、部署的流程做得极其顺手。代码结构清晰配置文件规范训练脚本开箱即用这些工程上的优势让 v5 成了工业界最受欢迎的版本。v5 提供了多个规模的模型n、s、m、l、x从轻量到重型都有覆盖。这个设计很贴心你可以根据部署设备的算力选择合适的规模。我在树莓派上部署过 v5n在服务器上用过 v5x同一套代码改个配置就能切换非常方便。v5 还内置了自适应 anchor 计算训练开始前会自动根据你的数据集聚类 anchor省去了手动调 anchor 的麻烦。这个功能对新手特别友好不用再纠结 anchor 尺寸怎么设。4.3 v5 的训练流程与超参数设置v5 的训练流程比较标准化基本就是准备数据集、配置 yaml 文件、跑训练脚本。数据集格式用的是 YOLO 格式每张图对应一个 txt 文件每行是class x_center y_center width height坐标都归一化到 0 到 1 之间。超参数方面学习率用的是余弦退火加 warmup初始学习率一般设 0.01 左右batch size 根据显存来定。v5 的默认超参数在大多数数据集上都能跑出不错的效果但如果你的数据集比较特殊还是需要调一调。我一般会重点关注学习率和 anchor 尺寸这两个其他的用默认值基本够用。数据增强方面v5 默认开了 Mosaic、HSV 增强、随机翻转等。如果你的数据集比较小可以适当加大增强力度如果数据集已经很大了可以适当减弱避免过度增强导致训练不稳定。4.4 从 v4 到 v5 的选型建议v4 和 v5 在精度上差距不大v5 略好一些但 v5 的工程化优势太明显了。如果你是要做学术研究v4 的论文更值得读里面的技巧总结得很全面如果你是要做实际项目v5 基本是首选省时省力。我自己的做法是新项目直接用 v5遇到精度瓶颈的时候再回头看 v4 里的技巧看看有没有能借鉴的。v4 里的很多技巧 v5 其实也内置了只是没有在论文里强调。两者不是替代关系更像是同一思路的不同实现。5. 训练自己的数据集时那些绕不开的坑5.1 数据标注labelImg 用起来简单但细节决定成败标注是训练的第一步也是最容易被忽视的一步。labelImg 是最常用的标注工具操作简单但有几个细节一定要注意。第一标注框要贴紧目标边缘不要留太多空白也不要切掉目标的一部分。标注质量直接决定模型的上限标得不准后面怎么调都白搭。第二类别名称要统一不要出现person和Person这种大小写不一致的情况会导致类别混乱。第三标注完成后要检查一遍看看有没有漏标、错标、框重叠的情况。我踩过的一个坑是标注的时候没注意图片的旋转角度有些图片是横着的标完之后训练出来的模型对方向很敏感。后来统一做了方向校正问题才解决。5.2 数据集划分与格式转换的常见错误数据集一般按 8:1:1 或者 7:2:1 划分成训练集、验证集、测试集。划分的时候要注意类别平衡不能某个类别全在训练集里验证集里一个都没有。格式转换也是容易出错的地方。YOLO 格式要求坐标归一化很多人忘了除以图片宽高导致训练时框全跑到图像外面去了。还有就是类别索引要从 0 开始不要从 1 开始这个细节不注意的话训练会报错或者效果异常。5.3 训练过程中的 loss 曲线怎么看训练的时候要盯着 loss 曲线看但不要只看总 loss要分开看定位 loss、置信度 loss、分类 loss。有时候总 loss 在降但某个分项 loss 在涨说明模型在某方面出了问题。比如定位 loss 一直不降可能是 anchor 尺寸不合适或者学习率太大分类 loss 不降可能是类别不平衡或者标注有问题。我一般会在训练前期频繁看 loss 曲线中期稳定了就少看后期主要看验证集指标。5.4 过拟合与欠拟合的判断和处理过拟合的典型表现是训练 loss 一直降验证 loss 先降后升。处理办法有几种加数据增强、加正则化、减小模型规模、早停。欠拟合的表现是训练 loss 和验证 loss 都降不下去这时候要检查学习率是不是太小、模型是不是太简单、数据是不是有问题。我遇到过一次欠拟合查了半天发现是学习率设太小了改了之后立马就好了。所以调参的时候学习率永远是第一个要检查的。6. 部署环节从服务器到树莓派的实际经验6.1 模型导出与格式转换训练完之后要把模型导出成推理格式v5 支持导出 ONNX、TensorRT、OpenVINO 等多种格式。导出的时候要注意输入尺寸和 batch size 的设置这些参数要和部署环境匹配。ONNX 是最通用的格式基本什么平台都能跑。TensorRT 在 NVIDIA 显卡上速度最快但兼容性差一些。OpenVINO 在 Intel 平台上表现好。我一般先导出 ONNX 做验证确认没问题再转成目标平台的格式。6.2 树莓派上部署 v5 的实测体验在树莓派上部署 v5 是很多人的需求我实测过树莓派 4B 和 5。树莓派 4B 跑 v5n用 ONNX Runtime大概能到 2 到 3 FPS勉强能用。树莓派 5 性能提升明显同样的模型能到 5 到 8 FPS体验好很多。部署的时候要注意几点一是模型规模要选小的v5n 或者 v5s大的跑不动二是输入分辨率要降默认的 640 太大改成 320 或者 416 能提速不少三是要用推理框架的加速功能比如 ONNX Runtime 的量化能进一步提速。6.3 推理速度优化的几个实用手段推理速度优化有几个方向模型剪枝、量化、算子融合、输入分辨率调整。剪枝是把不重要的通道去掉量化是把浮点运算换成定点运算算子融合是把多个算子合并成一个。这些手段可以组合使用但要注意精度损失。我一般先调输入分辨率这个最简单效果也最直接。然后试量化INT8 量化能提速一倍左右精度损失通常在 1 到 2 个点。如果还不够再考虑剪枝但剪枝比较麻烦需要重新训练。6.4 部署后的监控与迭代部署不是终点上线之后要持续监控模型的表現。重点关注几个指标推理延迟、准确率、漏检率、误检率。如果发现指标下降可能是数据分布变了需要重新训练。我一般会定期收集线上的难例人工标注后加入训练集做增量训练。这样模型能持续适应新场景效果会越来越好。这个流程听起来简单但坚持做下来对模型效果的提升是很明显的。7. 一些零散但有用的经验7.1 小目标检测的额外技巧小目标检测是 YOLO 的弱项但有一些技巧可以改善。一是提高输入分辨率让小目标在特征图上有更多像素二是用更密集的 anchor覆盖更小的尺寸三是用专门的小目标检测头比如在更高分辨率的特征图上做检测四是数据层面多收集小目标的样本或者用复制粘贴的方式增强小目标。我做过一个鸟类检测的项目目标都很小最后是通过提高输入分辨率到 1280 加上专门的小目标 anchor 才把召回率提上来的。7.2 类别不平衡的处理类别不平衡在检测里很常见某些类别样本特别多某些特别少。处理办法有重采样、类别加权、focal loss。v5 里可以通过调整类别权重来缓解这个问题但效果有限。更根本的办法还是补充少数类别的样本。7.3 模型剪枝与量化的取舍剪枝和量化都能压缩模型但取舍不同。剪枝对精度的损伤通常更大但压缩率也更高量化对精度损伤小但压缩率有限。实际用的时候我一般先量化如果还不够再剪枝。剪枝之后一定要重新训练不然精度掉得厉害。7.4 关于 YOLO 版本选择的一点个人看法从 v1 到 v5每一代都有它的历史价值。v1 开创了单阶段检测的思路v2 补上了工程化的短板v3 解决了小目标检测v4 集成了大量训练技巧v5 把工程化做到了极致。如果你现在要开始一个新项目直接用 v5 就行不用纠结。但如果你想深入理解 YOLO 的设计哲学从 v1 开始读论文是值得的每一代解决的问题和留下的问题都是理解检测算法演进的好材料。我自己用下来v5 在大多数场景下都是够用的遇到特殊需求再考虑其他版本或者自己改。检测这个领域更新很快但 YOLO 这条线的核心思路一直没变在速度和精度之间找平衡用工程手段把算法落地。把这个思路吃透了换什么版本都能快速上手。
返回列表