简介:基于mobileVIT与yolov5融合改进的交通指示牌目标检测项目,面向视觉学习者和工程落地人员,可直接用于训练、验证与推理。压缩包内含完整代码、标注好的数据集和训练好的权重文件;数据已按yolov5规范划分为训练集2493张与验证集716张,每张图片均有对应txt标签,修改yaml中的类别信息即可复现训练。项目将骨干网络替换为官方实现的mobileVIT结构,仅训练100个epoch,mAP已接近0.988,同时另一项指标为0.757,网络尚未完全收敛,适当增加轮次还可继续提升精度。整个压缩包共2000个文件,大小约200.62MB,其中txt标签占1921个、Python脚本40个、yaml配置23个,另有sh脚本与md说明文档辅助环境配置和项目理解,目录结构清晰,便于二次开发和部署。其中标注数据覆盖多类交通指示牌场景,可直接用于迁移学习与模型评估。目前已有177人学习使用,适合需要快速上手交通标志检测改造任务或进行算法对比实验的读者。
1. 把 YOLOv5 的骨干换成 MobileViT,交通指示牌检测的轻量化捷径
如果你的目标是在车载 Jetson 或者嵌入式设备上实时识别道路两侧的交通指示牌,直接用原版 YOLOv5s 很容易在算力和时延之间反复纠结:模型大了跑不动,模型小了漏检多。这条融合改进思路是把 YOLOv5 默认的 CSPDarknet 骨干直接替换成官方 MobileViT 实现,然后保留 YOLOv5 的 PANet 和检测头,等于不动整体训练管线,只换特征提取器。这份资源自带标注好的数据集、完整代码以及已经训练出的权重文件,训练集 2493 张,验证集 716 张,作者只训练了 100 个 epoch 就拿到 mAP50 为 0.988、mAP50-95 为 0.757 的结果,而且网络还远没有收敛。对于想在自定义数据集上快速复现轻量化目标检测、或者想直接拿现成交通标志权重做验证的工程师,这是一条值得完整走一遍的技术路线。
2. 换掉主干而不动检测头:MobileViT 融合 YOLOv5 的技术拆解
2.1 为什么替换骨干而不是替换整个检测器
YOLOv5 的训练流程在社区里已经被打磨得非常成熟,损失函数、锚框分配、数据增强和评测脚本都高度可复用。一个更省力的改进策略不是另起炉灶重写一个检测器,而是把最能影响特征表达能力的骨干网络换掉。原版 YOLOv5 的 CSPDarknet 擅长通过残差连接和跨阶段局部结构提取多尺度特征,在通用目标上表现稳定,但在交通指示牌这类小目标、复杂背景交织的场景里,局部感受野对全局上下文信息的建模能力偏弱。
MobileViT 是 Apple 提出的一种轻量级混合架构,它的核心思想是把卷积神经网络的局部建模能力与 Transformer 的全局自注意力结合起来。与原始 ViT 不同,MobileViT 不把整张图切成大量 patch 然后做昂贵的全局注意力,而是先通过卷积处理局部特征,再在每一个 patch 内部计算自注意力,最后通过卷积恢复空间信息。这种设计保持了接近 CNN 的推理效率,同时获得了类似 Transformer 的全局感受野,正好补上交通指示牌检测中对道路背景、标志牌上下文关系的理解能力。
把 MobileViT 插入 YOLOv5 后,neck 部分的 PANet 依然负责把高层语义信息与低层细节信息融合,检测头也还是原来的 anchor-based 结构。这样训练时损失函数不用动,评测指标不用动,数据集格式不用动,唯一需要改的是 backbone 的模块注册和网络结构配置。这也是这类融合改进项目最舒服的地方:理论收益明确,代码改动可控,出问题时定位也相对容易。
2.2 结构配置与模块注册
在 YOLOv5 工程里,模型结构通过 yaml 文件描述,backbone 每一行对应一个网络层或者一个自定义模块。将 MobileViT 注册进去的常见做法是先把官方实现里的 MobileViTBlock 和对应 stage 复制到models/common.py,然后在models/yolo.py的解析逻辑中增加这个模块的类型判断。下面是一个经过简化的配置示例,具体参数需要以你下载工程里common.py的构造函数为准。
# models/yolov5_mobilevit.yaml(示意,需按实际模块注册调整) nc: 4 # 类别数,按数据集实际标注修改 depth_multiple: 1.0 width_multiple: 1.0 anchors: - [10,13, 16,30, 33,23] # P3 层 - [30,61, 62,45, 59,119] # P4 层 - [116,90, 156,198, 373,326] # P5 层 backbone: - [-1, 1, Conv, [32, 3, 2]] - [-1, 1, MobileViT, [64, 2, 96]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 1, MobileViT, [128, 2, 144]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 1, MobileViT, [256, 2, 192]]这里MobileViT后面的三个参数通常分别表示输出通道数、MobileViTBlock 堆叠层数和 feature map 维度。[-1, 1, ...]中的-1表示上一层的输出作为当前层输入,1表示当前模块只执行一次。实际工程里还需要保证 MobileViT 输出的特征图 stride 与 YOLOv5 的 P3、P4、P5 层级对齐,这样 PANet 才能正确融合多尺度特征。如果你不确定某一个 stage 的 stride,可以在注册完成后用随机张量跑一次前向,逐步打印 shape,避免在训练阶段才发现维度不匹配。
2.3 替换前后各模块的分工对比
| 模块 | 原版 YOLOv5 | 本资源融合版 |
|---|---|---|
| 骨干网络 | CSPDarknet | MobileViT stage 堆叠 |
| 颈部网络 | PANet | PANet,保持不变 |
| 检测头 | anchor-based Detect | anchor-based Detect,保持不变 |
| 锚框机制 | 三尺度 anchor | 三尺度 anchor,保持不变 |
| 预训练权重 | COCO 预训练 | 可选 ImageNet 预训练或从零训练 |
从表格可以看到,这种融合方案的最大价值在于把改动范围压缩到骨干网络本身。训练时如果发现 mAP 上不去,优先检查的是 MobileViT 各 stage 的输出是否和 PANet 期望的输入尺寸匹配,而不是去排查整个训练链路。作者训练 100 轮的结果里 mAP50 已经到 0.988,说明骨干替换没有破坏检测器基本能力,mAP50-95 只有 0.757 则说明框回归精度还有明显提升空间,长期训练会进一步改善。
3. 从 0 摆放数据集到修改 data.yaml:训练前最容易被卡住的一步
3.1 数据集目录结构与 YOLO 标签格式
这份资源的数据集已经把训练集和验证集严格分开,目录结构应该保持为 YOLOv5 默认期望的形态,否则训练启动时assert就会直接给出路径错误。常见结构如下:
datasets/ ├── images/ │ ├── train/ # 2493 张图片 │ └── val/ # 716 张图片 ├── labels/ │ ├── train/ # 2493 个 txt 标签 │ └── val/ # 716 个 txt 标签 └── classes.txt # 类别清单,每行一个类别名每个 txt 标签的文件名必须和对应图片名完全一致,后缀不同没关系,因为 YOLOv5 读取时会把图片后缀替换为.txt去查找标签。txt 文件中的每一行对应一个目标,格式是class_id x_center y_center width height,其中x_center、y_center、width、height都必须是相对图片宽高的归一化小数,取值在 0 到 1 之间。交通指示牌图片里经常出现标志牌不完整被裁剪到图片边缘的情况,这时中心点可以贴近 0 或 1,但依然要保证归一化坐标在合理范围内,否则会警告甚至被过滤。
下面用一个简单的字段说明表帮助定位标签异常:
| 字段 | 含义 | 取值范围 |
|---|---|---|
| class_id | 类别编号 | 0 到 nc-1 |
| x_center | 目标中心点横向比例 | 0 到 1 |
| y_center | 目标中心点纵向比例 | 0 到 1 |
| width | 目标宽度比例 | 0 到 1 |
| height | 目标高度比例 | 0 到 1 |
如果一个标志牌位于图片边缘,相对应的中心点值可能会接近 0.99,这是正常的。但如果是负数或者大于 1,那就意味着标注脚本或数据划分出了问题,必须修复后再训练。
3.2 使用脚本校验标签是否可训练
拿到数据集后不要急着直接开训练,先用脚本检查一遍标签质量。交通指示牌数据集的标注由人工或半自动工具生成,偶尔会出现空 txt 文件、坐标越界、类别编号超出范围等情况。下面这段脚本可以快速扫描训练集标签:
from pathlib import Path label_dir = Path("datasets/labels/train") for txt in label_dir.glob("*.txt"): with open(txt, encoding="utf-8") as f: lines = f.readlines() if not lines: print(f"空标签文件:{txt.name}") continue for line in lines: parts = line.split() if len(parts) != 5: print(f"字段数量错误:{txt.name} -> {line.strip()}") continue cls, cx, cy, w, h = map(float, parts) if not (0 <= cx <= 1 and 0 <= cy <= 1): print(f"中心点越界:{txt.name} -> {line.strip()}") if not (0 < w <= 1 and 0 < h <= 1): print(f"宽高越界:{txt.name} -> {line.strip()}")这个脚本会把异常标签的图片名和具体行内容打印出来。cls用来确认类别编号是否连续,如果数据集中只有 4 类目标,但某个标签出现了类别编号 5,训练时就会在计算损失的地方直接报错。w和h通常不会出现负数,但如果出现 0,代表标注工具导出了面积为 0 的框,这种标签训练时会产生无效梯度。
3.3 修改 data.yaml 与类别映射
YOLOv5 训练时通过 data.yaml 文件指定数据集路径和类别信息。下面是一个适用于交通指示牌项目的配置示例,实际类别名需要根据这份资源和你的任务来对应:
path: datasets # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 4 # 类别数 names: 0: warning # 警告标志 1: prohibition # 禁令标志 2: mandatory # 指示标志 3: guide # 指路标志path字段是根目录,train和val中的路径都会自动拼接在path之后。如果训练脚本从工程的runs/目录启动,而数据集放在工程根目录下的datasets/文件夹中,这样配置就能直接工作。nc必须与标签中实际出现的最大类别编号 +1 一致,多类别编号空档最好重新整理,否则训练时类别匹配会出现混乱。建议在修改完 yaml 后运行一次python train.py --data datasets/traffic_sign.yaml --cfg models/yolov5_mobilevit.yaml --epochs 1来验证配置是否被正确解析,这比训练到一半才发现官问题要高效得多。
4. 100 个 epoch 的训练实验与指标解读
4.1 完整训练命令与恢复机制
这份资源本质上是把 MobileViT 注册进 YOLOv5 之后形成的工程,所以训练入口依然沿用原版 torchvision 风格的 YOLOv5 训练脚本。第一次训练建议使用和作者一致的配置,方便对照指标:
python train.py \ --data datasets/traffic_sign.yaml \ --cfg models/yolov5_mobilevit.yaml \ --weights '' \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --hyp data/hyps/hyp.scratch-low.yaml--weights ''表示不使用预训练权重,从零开始训练。MobileViT 混合了卷积和 Transformer 结构,如果显存允许,建议先加载 ImageNet 预训练权重再训练,收敛速度会快不少。--batch-size 16是空间和性能之间的折中,如果显存只有 8GB,可以减到 8,同时把初始学习率对应下调。--imgsz 640是 YOLOv5 的标准输入尺寸,交通指示牌本身是小目标,继续提高到 960 或 1280 会提升小目标召回率,但训练时间也会成倍增加。--hyp hyp.scratch-low.yaml代表使用较低的数据增强强度,交通标志要求保留清晰的文字细节,过强的 mixup 会把图形混合得面目全非。
训练过程中如果中断,资源里出现的optimizer_config.json会保存优化器状态。继续训练时不需要从头开始,而是用--resume参数加载最近一次 checkpoint:
python train.py --resume runs/train/exp/weights/last.ptresume会自动读取上次训练的超参数、优化器状态、当前 epoch 和数据集配置,相当于从暂停点继续跑。用户经常会忽略这一步,断了之后就重新开始,导致前面的训练时间全部浪费。
4.2 关键超参数调整表
| 参数 | 建议值 | 作用 | 调大或调小的后果 |
|---|---|---|---|
| lr0 | 0.01 | 初始学习率 | 过大导致损失震荡,过小收敛慢 |
| lrf | 0.1 | 最终学习率倍率 | 决定训练末期是否转精细拟合 |
| mosaic | 1.0 | 马赛克增强概率 | 对多尺度小目标有利,但过强会破坏完整性 |
| mixup | 0.5 | 样本混合增强 | 太高会让标志牌文字模糊 |
| warmup_epochs | 3.0 | 预热轮数 | Transformer 结构需要更长预热稳定 |
MobileViT 骨干里包含 LayerNorm 和注意力层,对学习率的敏感度比纯 CNN 更高。如果训练初期 loss 出现偶发 spike,优先把lr0降到 0.005,并观察前 10 个 epoch 的曲线是否平滑。mosaic和mixup在交通标志场景里建议保持中等强度,因为交通指示牌的文字部分一旦被裁切或混合,模型就会学到错误的纹理特征。
4.3 验证阶段如何解读 mAP50 与 mAP50-95
100 个 epoch 训练结束后,需要用独立的验证集评估最终权重,而不是直接用训练进程输出的最后一轮结果。运行:
python val.py \ --data datasets/traffic_sign.yaml \ --weights runs/train/exp/weights/best.pt \ --batch-size 16这条命令会输出类别维度上的平均精确率,其中 mAP50 表示 IoU 阈值取 0.5 时的平均精度,mAP50-95 表示 IoU 阈值从 0.5 到 0.95 每间隔 0.05 计算一次再取平均。这份资源给出的 0.988 和 0.757 正好对应这两个指标。mAP50 高说明目标基本能被找到,mAP50-95 较低说明预测框和真值框的重合度还不够高,常见原因是训练轮数不足、框回归损失没有收敛完全。作者提到加大训练轮次还能继续提升,就是因为 mAP50-95 还没走到平台期。
训练到 100 轮时会发现验证集精确率曲线通常还在缓慢上升,这时候不要急着停止,可以把 epoch 数提高到 300 或 500,并开启早停策略。同时建议查看runs/train/exp/下自动生成的confusion_matrix.png和results.png,如果各类别之间的混淆集中在颜色相近的标志上,比如红色禁令和红色警告,就需要补充更多该类别的训练数据,而不是继续盲目加大 batch size。
5. 用训练好的权重做推理时容易忽略的三个细节
5.1 推理命令与置信度阈值设置
拿到训练好的权重后,直接对图片或视频做推理的命令和原版 YOLOv5 一致:
python detect.py \ --source ./test_images \ --weights runs/train/exp/weights/best.pt \ --conf-thres 0.35 \ --iou-thres 0.45 \ --imgsz 640 \ --agnostic-nms--conf-thres 0.35表示只保留置信度超过 0.35 的检测框,交通指示牌在远景中尺寸很小,如果你发现漏检多,可以降到 0.25。--iou-thres控制 NMS 的框合并程度,两个不同标志牌贴得很近时,把这个值调低到 0.4 能减少误合并。--agnostic-nms表示对全部类别的框执行统一的 NMS,而不管类别是否相同;如果项目里同时检测指示牌和路牌,并且它们经常重叠,这个参数值得打开。
5.2 小目标场景的两种补救手段
MobileViT 骨干在推理时对全局上下文有建模能力,但交通标志牌远距离情况下可能只有十几个像素宽。此时直接把输入分辨率从 640 提升到 1280 作用有限,因为 anchor 是在 640 分辨率下统计的,盲目放大输入会导致 anchor 分布不匹配。推荐两个更稳妥的手段,第一种是把大图切块后做 Tiled 推理,然后合并检测结果;第二种是训练阶段对包含小目标的样本做过采样,也就是从训练集里单独筛出小目标占比高的图片,复制一份加入训练。两者都不需要改模型结构,改动成本很低。
5.3 导出 ONNX 时的 MobileViT 算子兼容性
如果要部署到端侧,可以用下面命令导出 ONNX:
python export.py --weights best.pt --include onnx --opset 12 --simplify因为 MobileViT 内部包含 LayerNorm、自适应平均池化和注意力中的 reshape 操作,导出后建议用onnxruntime跑一遍同一张输入,对比原模型输出差异。TensorRT 部署时优先使用固定输入尺寸,避免动态 shape 带来的额外算子转换工作量。整个项目从骨架改进到部署验证链路完整,值得花时间在导出阶段做误差分析。
本文还有配套的精品资源,点击获取