YOLOv5目标检测工程实战:从网络结构到训练调参与部署

发布时间:2026/9/15 22:07:38
YOLOv5目标检测工程实战:从网络结构到训练调参与部署 这两年我接过不少目标检测相关的实际项目从工业质检、安全帽识别到智慧养殖的动物计数十个需求里七八个最终都落在同一个模型上——YOLOv5。目标检测领域的算法迭代很快YOLOv6、YOLOv7、YOLOv8甚至YOLOv9都陆续出来了但YOLOv5在工程界的地位依然非常稳固原因不光是精度和速度的平衡更多在于它极低的上手门槛和极其成熟的生态。不管你是刚接触深度学习的小白还是已经跑过几个分类模型的开发者想把“目标检测算法”真正落地YOLOv5都是最适合作为第一站的那个。这篇文章不打算把论文公式全部罗列一遍而是从工程实践的角度把YOLOv5的网络结构、训练原理、环境配置、踩坑记录和调参技巧一次讲透。1. 为什么是YOLOv5它到底赢在哪里1.1 YOLO家族演进从v3到v5再到现在YOLO系列的第一篇论文是2016年的You Only Look Once它提出的核心思想就是把目标检测当成一个回归问题一次前向推理同时输出所有目标的位置和类别。这个“单阶段”思路在当时是很颠覆的因为之前的Faster R-CNN等两阶段方法需要先提候选区域再分类慢但准YOLO则是直接预测快但精度略逊。后续的v2引入了Batch Normalization和anchor boxv3引入了FPN特征金字塔和Darknet53骨干网络v4在v3基础上加入了CSP结构和Mish激活函数。到了v5作者Glenn Jocher没有发表正式论文而是直接开源了一个工业级项目仓库这个仓库的工程化程度远远超过了之前的任何YOLO版本官方权重、自动锚框、超参数进化、丰富的数据增强、一键导出多种部署格式全部齐备。你可以说v5在算法层面的创新没有v4那么“硬核”但它在易用性上补齐了最后一块短板。这也是为什么在工业界YOLOv5的采用率一直居高不下。1.2 易用性不是玄学工程化设计的胜利我经常跟团队里的人说YOLOv5最厉害的地方不是某个模块有多惊艳而是它把“从零开始训练一个检测模型”这件事的门槛降到了极低。下面这几点是我在实际使用中感受最深的数据集格式简单YOLOv5使用txt格式的标注文件每行是“类别 x_center y_center width height”坐标是相对于图片宽高的归一化值。相比VOC的xml和COCO的json这种格式人类可读性极强用脚本转换也方便。训练命令一键启动python train.py --data mydata.yaml --weights yolov5s.pt新手看一遍就能上手不用手写训练循环。官方提供了多个规模的模型s/m/l/x参数量从7M到86M适配从边缘设备到服务器集群的不同场景。detect.py推理脚本自带可视化、保存结果、视频流处理等功能调试极其方便。导出脚本支持ONNX、TensorRT、CoreML、TorchScript等格式部署链路完整。这些设计单独拿出来都很简单但合在一起就构成了一个非常完整的武器库。我见过不少算法研究员自己搭的检测项目功能可能很强大但数据加载、评估指标、可视化这些部分往往残缺不全而YOLOv5直接把这些都做成了开箱即用的标准件。2. YOLOv5网络结构拆解三段式架构的各自分工2.1 BackboneCSPDarknet到底改了啥YOLOv5的骨干网络叫CSPDarknet核心就是CSPNetCross Stage Partial Network的思想。你可以把CSP理解为“把网络中间的特征图分成两条路径一条走卷积堆叠一条直接连到后面”。这样做的好处有两个一是梯度流可以通过这条“捷径”直接回传避免深层网络中的梯度消失二是减少了重复梯度计算降低了计算量。具体到YOLOv5的代码里CSP模块体现在C3这个类v5.0之后叫C3之前叫BottleneckCSP。它先将输入通过一个1x1卷积分成两个分支其中一个分支经过若干个Bottleneck残差块另一个分支直接跳过最后在通道维度上拼接起来再通过一个1x1卷积整合输出。这个结构和ResNet的残差连接有本质区别ResNet是通过加法融合CSP是通过拼接融合融合方式的不同直接影响到了信息流的多样性。YOLOv5的Backbone里还有一个容易被忽略的小模块——Focus。它的操作是每隔一个像素取一个值把一张WxHxC的图变成W/2 x H/2 x 4C然后再做卷积。这个操作的目的是在不丢失信息的情况下对特征图进行下采样。不过在YOLOv8里官方把它替换掉了因为现代GPU对切片操作的优化并不好Focus模块节省的FLOPs在实际推理中体现不出来。但我个人觉得YOLOv5保留它是合理的它在CPU端或老GPU上还是有一定速度优势的。2.2 NeckPANet为什么比FPN好用Neck部分YOLOv5用的是PANet结构。先解释FPNFeature Pyramid Network它的思路是从上往下把高层语义信息逐层传递到浅层特征图让每个尺度的特征图都拥有丰富的语义信息。但FPN有个明显的不足——信息只能从高层往低层流浅层特征图的位置信息本来就比较丰富再往下传的也只是语义浅层定位信息和深层语义信息的交互不够充分。PANet在FPN的基础上加了一条自下而上的路径让低层的位置信息也能直接传递给高层。你可以想象一个双向的信息高速公路既有“语义信息往下游走”的通道又有“位置信息往上游走”的通道。这样做对检测小目标和定位精度都有帮助尤其是对于需要精确边界的场景比如工业零件检测PANet的优势非常明显。YOLOv5的Neck部分遵循的是“FPNPAN”的结构先从backbone的最后三层特征图开始做自上而下的上采样融合得到三个不同尺度的输出然后再从其中最小的特征图开始做自下而上的下采样融合最终输出给Head三个不同尺度的特征图。2.3 Head从anchor到预测输出的解码过程YOLOv5的Head部分是三个检测头分别对应三个不同尺度的特征图输出维度是(batch_size, 3*(5num_classes), grid_h, grid_w)。这里的3表示每个格子有3个anchor默认设置是每个尺度3个总共9个anchor5表示“目标置信度 中心点x偏移 中心点y偏移 宽度w 高度h”这5个参数num_classes就是类别数。在训练时模型输出的是原始预测值需要通过sigmoid函数把x、y偏移压到0-1之间然后加上grid坐标再乘以stride才能换算到原图上的像素坐标。w和h则会先用exp函数扩张再乘以anchor的宽度和高度。这就是常说的“解码过程”。我在第一次看源码时这部分看了挺久才明白其实理解的关键就是记住“网络预测的不是最终坐标而是一组相对anchor的偏移量”。用一张表简单总结一下三个检测头的分工检测头特征图尺寸感受野负责目标大小anchor示例P380x80输入640时小小目标[10,13] [16,30] [33,23]P440x40中中目标[30,61] [62,45] [59,119]P520x20大大目标[116,90] [156,198] [373,326]上面这组anchor是COCO数据集下默认的如果你换了数据集YOLOv5会自动调用kmeans_anchor重新聚类anchor所以并不需要太担心这个数字。3. 训练一块的核心机制正负样本分配与损失计算3.1 anchor分配策略从模板匹配到自适应目标检测训练过程中有一个关键问题一张图里那么多格子、那么多anchor哪些应该负责预测这个目标YOLOv5采用的方法是类似yolo v3/v4的“模板匹配”策略——如果一个目标的ground truth框和某个anchor的IoU大于指定阈值默认0.2这个anchor就负责预测这个目标。这里有个容易被忽视的细节YOLOv5为了让更多anchor参与训练还做了一个“跨grid匹配”的操作。正常情况下一个目标的中心点落在哪个格子就由哪个格子的anchor来预测但YOLOv5会让中心点附近的三个格子都能参与预测这个目标。具体做法是将目标中心点所在的格子及其上下左右相邻的格子都作为候选只要这些格子的anchor与目标框的IoU大于阈值就都分配为正样本。这样的好处是正样本数量显著增加训练更充分收敛更快。我还记得第一次看YOLOv5的损失计算代码时发现它同时用了build_targets这个函数来筛选正样本。那个函数初看很绕但核心逻辑就是上面说的跨网格匹配加上IoU阈值筛选。理解了这个你在调参时就不会瞎试了——anchor_t这个超参数就是控制IoU阈值的默认4.0其实是“宽高比阈值”不完全是IoU。3.2 损失函数的组成分类、定位、置信度YOLOv5的总损失由三部分构成分类损失、定位损失和置信度损失。分类损失用的是BCEWithLogitsLoss二元交叉熵因为YOLOv5把多分类问题拆成了多个二分类问题每个类别独立计算损失。这样设计的好处是允许一个目标同时属于多个类别当然通常不会这么用主要是在训练时梯度计算更稳定。定位损失在v5.0之后默认用CIoU Loss。CIoU相比原始的IoU Loss考虑了三个额外因素中心点距离、宽高比的一致性和重叠面积。用大白话说就是不仅要让预测框和真实框“重叠得多”还要让两个框的“中心尽量靠近”且“形状尽量一致”。这也解释了为什么CIoU在训练初期能让框更快地“挪”到目标附近。置信度损失同样用BCEWithLogitsLoss但它只计算正样本的置信度损失对于负样本只计算置信度损失不计算分类和定位损失。因为负样本数量远大于正样本YOLOv5在计算置信度损失时也加了正负样本权重避免负样本主导梯度。这三个损失加起来再按权重相加默认的box0.05, cls0.5, cls_pw1.0等得到总损失。理解损失函数的构成你在遇到训练不收敛或者loss波动诡异的时候才能快速定位究竟是哪部分出了问题。3.3 大白话解释Grid、Stride、Anchor三者的关系很多刚接触YOLO系列的朋友都会被Grid、Stride、Anchor这三个概念搞晕我用一个生活化的例子来解释。假设你在一张640x640的图上检测目标模型会把图划分成80x80的网格对应stride8的特征图。每个格子就像是在图上盖了一个“小窗户”这个窗户里可能有一只鸟也可能什么都没有。Anchor呢就是预先定义好的“建议框”它告诉模型这个格子里如果有目标目标大概会是什么形状。比如某个anchor是[10,13]就表示“这个格子里可能有一个很窄很高的物体”。Stride的作用是换算比例。stride8表示特征图上的一个像素对应原图上的8个像素。所以当模型预测“目标中心点在这个格子往右偏移0.3往下偏移0.6”时实际原图上就是(grid_x 0.3) * 8。每个尺度负责的目标大小不同80x80网格负责小目标20x20网格负责大目标这就是多尺度检测的直观理解。4. 把算法跑起来环境配置、数据准备与训练实操4.1 环境配置与最容易踩的坑YOLOv5的官方环境要求其实不算高Python 3.7以上PyTorch 1.7以上CUDA建议10.2以上。用Anaconda建环境是最稳妥的方式我一般按如下顺序操作conda create -n yolov5 python3.8 conda activate yolov5 cd yolov5 pip install -r requirements.txt这里有个坑requirements.txt里默认的torch版本可能和你本地的CUDA版本不匹配。我建议先单独安装对应CUDA版本的PyTorch再装其他依赖避免自动安装的torch是CPU版本。判断标准很简单——训练时如果提示Torch not compiled with CUDA enabled那就是装成了CPU版。还有一个容易踩的坑是pillow版本过高或过低导致图像预处理报错具体表现是某些图片在load时直接崩掉。我自己的做法是固定pillow版本在8.x到9.x之间同时把opencv-python升级到4.5以上能省去很多奇奇怪怪的图片读取问题。如果你用的是Windows还需要注意路径问题。YOLOv5的代码虽然做了跨平台处理但数据集yaml文件里如果用了相对路径在Windows下偶尔会因为反斜杠转义出问题。我的习惯是统一用绝对路径或者用正斜杠写相对路径训练时直接指定--data参数的路径。4.2 数据准备数据集格式、目录结构与标注要点准备数据是训练一个可用模型最关键、也最耗时的一步。YOLOv5的数据集目录一般长这样datasets/ mydataset/ images/ train/ img1.jpg img2.jpg val/ ... labels/ train/ img1.txt img2.txt val/ ...其中每个txt文件的内容是一行一个目标格式为class_id x_center y_center width height注意这些值都是归一化的。例如一张1000x800的图里有一个目标的边界框在原图上是从(200, 150)到(600, 450)那么x_center就是(200600)/2/10000.4y_center是(150450)/2/8000.375width是(600-200)/10000.4height是(450-150)/8000.375。标注工具我推荐LabelImg或者X-AnyLabeling。LabelImg是老牌工具输出PascalVOC格式之后再转成YOLO格式X-AnyLabeling支持自动标注和多种格式导出效率更高。标注时有个容易被忽略的细节边界框尽量贴紧目标边缘不要留太多背景也不要切到目标主体。这直接影响到模型学到的“目标边界”是否准确尤其是对于形状不规则的目标比如安全帽、螺丝钉标注质量决定了模型上限。数据准备好后在YOLOv5根目录下创建一个mydata.yaml文件内容如下train: /path/to/datasets/mydataset/images/train val: /path/to/datasets/mydataset/images/val nc: 2 names: [person, helmet]nc是类别数量names是类别名称列表顺序必须和标注文件里的class_id一致。4.3 训练命令与关键参数说明YOLOv5的训练命令我常用这一条python train.py --data mydata.yaml --weights yolov5s.pt --img 640 --batch-size 16 --epochs 100 --device 0几个关键参数的作用--weights指定预训练权重。用yolov5s.pt会加载在COCO上训练好的权重迁移学习能大大加速收敛如果你从头训练就用--weights 。--img训练时输入的图片尺寸。默认640如果你的目标普遍很小可以试试896或者1280但显存占用会暴涨。实测下来小目标场景把--img从640提升到1280mAP可能直接涨3-5个点但训练时间也会变成原来的4倍左右。--batch-size受显存限制。我的经验是30系显卡12GB显存跑yolov5s时batch-size可以开16甚至32但如果你加了复杂的数据增强可能需要调小到8。--epochs不是越大越好。小数据集几百张图跑300轮很容易过拟合我一般看验证集mAP的变化如果连续30轮没提升就直接停了。YOLOv5自带patience参数默认100但我实测设50更实际。训练过程中我习惯开着两个监控一个是终端里的loss输出一个是TensorBoard。YOLOv5默认会启动TensorBoard你也可以加--entity参数配合WandB做远程监控。我在公司一般用WandB因为可以在手机上看训练曲线但如果你更在意隐私和数据安全本地TensorBoard完全够用。5. 从“能跑”到“涨点”超参数调节与常见问题排错5.1 超参数文件解析loss权重、数据增强阈值、anchors设置YOLOv5的超参数都集中在data/hyp.scratch-low.yaml和data/hyp.scratch-high.yaml里前者适合小模型和慢学习率后者配合高版本模型使用。训练时可以用--hyp指定。下面挑几个我实际调过且有效果的超参数说lr0和lrf初始学习率和最终学习率系数。默认lr00.01lrf0.01表示学习率从0.01线性/余弦衰减到0.0001。如果你用小batch-size比如4或8lr0建议降到0.001左右否则容易发散大batch64以上可以适当调高到0.02。momentum默认0.937这个值比较“激进”适合大数据集。小数据集上有时我会降到0.9能减少震荡。weight_decay默认0.0005。如果你发现验证集loss和训练集loss差距越来越大可以试着增大到0.001。anchor_t就是上面说的anchor宽高比阈值默认4.0。如果一个目标的宽高比极端比如细长条可以把anchor_t调大到5.0或6.0让更多极端形状的目标有正样本。hsv_h、hsv_s、hsv_v色调、饱和度、亮度的增强幅度。小数据集可以适当调大这些值来增加数据多样性比如把hsv_h从0.015调到0.05hsv_s从0.7调到0.9能明显缓解过拟合。fliplr随机水平翻转概率。默认0.5如果你的目标具有左右方向性比如车牌、箭头、交通标志一定要把fliplr设为0否则模型会把左右颠倒的样本当成同一个类精度会明显下降。数据增强还包括mosaic四图拼接和mixup两图混合。YOLOv5从v6.0开始把mosaic概率设置在最后10轮逐渐关闭因为mosaic生成的样本和真实场景分布差异较大如果一直用到结束会让模型在真实图片上略掉点。这个细节是YOLOv5工程化到了一定程度的体现也提醒我们自己调参时别把增强开得太满。5.2 五个常见坑与我的排查路径训练YOLOv5一年多我碰到过各种预料之外的问题有几个非常典型值得展开说。Loss直接变NaN这个问题最常见的原因是学习率过大或梯度爆炸。我的排查步骤是先把--batch-size减半再把lr0降到0.0001如果还有问题就检查数据标注里有没有超出图片边界的框。YOLOv5对超出边界的目标会做裁剪但如果标注框坐标本身就是负值或大于1就可能导致loss计算异常。用官方提供的check_labels.py脚本校验一下数据能快速定位问题。显存不足OOM这个问题在小显存显卡上很常见。我的处理顺序是调小batch-size - 关掉mosaic--mosaic 0- 降低--img尺寸 - 换更小的模型yolov5s换成yolov5n。YOLOv5n参数量只有1.9M显存占用不到2GB对于快速验证数据集是否可用非常方便。验证集mAP一直很低但训练loss正常下降这通常是数据问题。我遇到过的原因有三类一是类别标签错乱标注时的class_id和yaml里的names顺序不一致二是训练集和验证集分布差异过大比如训练集全是白天的图验证集全是夜晚的图三是目标太密集导致NMS阈值不合适。最后一种情况可以调低--conf-thres和--iou-thres但更根本的办法是增加训练的负样本数量让模型知道“什么时候该不框”。训练速度越来越慢可能是缓存和无用的日志在积累。YOLOv5会缓存图片--cache参数如果你开了cache再次训练时加载会快很多但第一次会花很久。另一个容易被忽视的点是WandB的同步上传在弱网环境下会拖慢训练加--no-wandb可以关掉。小目标几乎检测不到这个问题我在第5.3节单独讲因为它是独立且高频的需求。5.3 小目标检测为什么难又该怎么改小目标检测是目标检测领域的老大难问题。一个小目标在640x640的输入下可能只占几个像素到十几个像素特征经过多次下采样后在深层特征图上只剩下一个点或者完全消失。所以我常用的改进手段有以下几种提高输入分辨率。把--img从640提高到1280甚至1536是最直接有效的方法。代价是显存和速度但效果立竿见影。使用更大的模型。从yolov5s换到yolov5m甚至yolov5l模型容量更大对小目标的特征表达能力更强。增加浅层检测头。YOLOv5默认从P3stride8开始检测如果你要检测的目标非常小比如几十像素可以把backbone中更浅的P2层也拿来作为检测头相当于增加一个stride4的输出。这个操作需要改模型配置文件略微复杂但确实是针对小目标的“结构性优化”。调整anchor大小。用官方提供的utils/autoanchor.py脚本重新聚类anchor确保anchor覆盖小目标尺度。默认anchor在COCO上表现不错但换到卫星图像、无人机图像这种场景一定要重新聚类。使用SAHISlicing Aided Hyper Inference切片推理。把原图切成多块重叠的小图分别推理再把结果合并。这个方案不改模型纯推理阶段处理实测对超大图小目标检测的提升非常明显。需要提醒的是小目标检测没有银弹。如果你在无人机图片上检测行人先把输入分辨率拉高和重新聚类anchor试了再说要是仍然不行再上P2检测头或者SAHI一步步来别一上来就改网络结构。6. 推理优化与部署从PyTorch走到TensorRT6.1 模型导出onnx与TensorRT训练好的模型只是第一步真正落地部署还需要将模型导出成推理引擎能够高效运行的格式。YOLOv5官方提供了export.py一条命令就能导出python export.py --weights runs/train/exp/weights/best.pt --include onnx python export.py --weights runs/train/exp/weights/best.pt --include engine --device 0第一条导出ONNX格式第二条在NVIDIA显卡上导出TensorRT引擎TensorRT是NVIDIA的推理加速库能对模型做层融合、量化等优化推理速度通常可以提升1.5到3倍。这里有两个注意点TensorRT的引擎文件是“绑定硬件”的换一张显卡型号就得重新导出导出TensorRT时--workspace参数控制了显存的使用上限如果你的显卡显存不大可以设成4或8避免导出过程中OOM。我自己在工业项目里最常用的组合是模型导出为TensorRT FP16推理框架用自己的C代码或者TensorRT官方Python API。实测一个yolov5s模型在FP16 TensorRT下于RTX 3090上推理时间可以到2-3ms已经能满足很多实时场景的需求。6.2 模型剪枝与轻量化思路如果你的部署设备是边缘设备比如Jetson Nano、树莓派或者你需要在CPU上做实时推理那么模型剪枝几乎是绕不开的。YOLOv5官方没有提供一键剪枝脚本但社区有非常成熟的项目原理基本都是基于结构化剪枝——对Batch Normalization层的缩放因子gamma进行稀疏化训练然后剪掉gamma值接近0的通道。我做过的实践流程是正常训练一个高精度模型。在训练后期加上稀疏化正则项用一个很小的系数比如0.001惩罚BN的gamma值诱导一部分通道接近0。设定一个剪枝比例比如30%或50%把低于阈值的通道直接剪掉。对剪枝后的模型做short-term微调恢复精度。剪枝后的模型虽然参数量变小但推理速度提升不一定成比例因为GPU的并行特性决定了一些冗余运算并不明显拖慢速度。如果你的场景是CPU部署剪枝效果就会明显很多。另一个更省事的选择是直接使用YOLOv5n或者YOLOv5s它们本身就是小巧高效的模型多数场景不需要额外剪枝。6.3 在Jetson Nano等边缘设备上部署的心得Jetson Nano这类设备性能有限部署YOLOv5时我认为最重要的一条经验是不要跑Python推理。虽然官方检测脚本能跑通但CPU上的Python推理速度慢到没法看。正确做法是导出TensorRT引擎然后用C或TensorRT的Python API做推理。我在Jetson Nano上跑yolov5n输入尺寸320x320FP16精度推理速度可以达到15-20 FPS基本满足实时性要求。如果你必须用Python就加上--device 0强制用GPU同时注意把图片预处理操作resize、归一化也放到GPU上不要在CPU上做。Jetson系列本身的CUDA核心和显存带宽有限任何CPU和GPU之间的数据拷贝都可能成为瓶颈。另外在边缘设备上部署还要注意动态尺寸问题。TensorRT引擎在导出时如果指定了固定输入尺寸比如320x320推理时就不能任意改分辨率。如果你希望模型支持多种输入尺寸可以把它转成ONNX后用TensorRT的dynamic shape功能但这样会稍微降低推理效率。我的做法是固定输入尺寸需要处理不同分辨率时就在预处理阶段做letterbox然后在后处理阶段把框坐标映射回原图尺寸YOLOv5的letterbox逻辑是现成的直接用就行。