
简介这是一套基于mobileVIT网络与YOLOv5融合改进的交通指示牌目标检测工程包专为目标检测学习者和交通物流场景开发者设计用于解决真实路况下标志识别实战中的数据、代码与预训练权重分散的问题。工程已实测可跑通更换官方mobileVIT骨干网络后仅训练100轮便在验证集上取得0.988的mAP加大训练轮次有望继续提升验证集与训练集指标均达到可用级别。资源包共2000个文件包括1921个txt标签文件、40个Python脚本、23个YAML配置、9个Shell脚本及说明文档等整体压缩包约200.62MB数据集划分清晰训练集含2493张图片及标签验证集含716张图片及标签可直接按YOLOv5方式摆放并训练代码中预留了类别与超参数修改接口。除完整数据集和可运行代码外还内置了训练好的权重文件便于直接进行迁移学习或模型效果对比。已有177人学习适合需要从零搭建交通目标检测流程的中高级开发者。1. 交通指示牌检测不好做mobileVIT和yolov5不是“你换我”的关系交通指示牌平时看着很清楚一旦进入自动驾驶和道路养护场景就变成“又小又乱”的目标远处一个限速牌只占画面0.5%阴天颜色发灰树枝挡住半边夜间灯光一照还会反光。YOLOv5骨架能跑但漏检和误检往往卡在局部特征强、全局上下文弱这一点上。mobileVIT是轻量级视觉Transformer擅长补全局依赖两者融合不是简单替换而是把CNN的局部先验和Transformer的长程建模叠在一起。标题里这套工程包已经帮你配好了标注好的数据集、完整代码和训练好的权重文件读这篇文章的人不需要从头造轮子需要的是知道融合改在哪、参数怎么调、坑在哪里。2. 为什么要融合改进YOLOv5骨架的短板与mobileVIT的可补位之处2.1 YOLOv5在交通指示牌上的三个“够用但不够”的点交通指示牌目标检测和通用目标检测最大的差异不在“目标是什么”而在“目标在画面里有多小、多隐蔽”。按COCO对目标尺度的划分像素面积小于32×32就算小目标。车载相机、路口监控拍到的指示牌大量落在这个区间。YOLOv5的Backbone从640×640输入开始经过五次下采样最后输出20×20的特征图大目标的语义集中在这里而小目标主要靠80×80的浅层特征。浅层特征的优势是边缘和颜色信息清晰劣势是感受野不足模型很难判断“眼前这块红色”到底属于限速牌还是车尾灯。这个矛盾就是第一个“够用但不够”的点。第二个点来自指示牌的外观退化。在强逆光、雨雾和夜间场景下指示牌的颜色对比度会剧烈下降此时网络如果只依赖局部卷积核提取到的角点、纹理很容易把护栏反光片、轮胎、雨刮等噪声当成候选框。因为卷积的感受野是局部的它只能看到“一小块强边缘”却看不到“这块边缘和周围大面积同色区域共同构成一个正圆形指示牌”。YOLOv5的C3结构虽然通过堆叠残差卷积扩大感受野但本质上仍是在局部特征上做逐步映射全局语义信息传递路径太长。第三个点是误检来源多。城市道路里的广告牌、门头、铁皮箱都是矩形若模型缺少全局上下文很容易在相似纹理背景下给出高置信度误检。比如“限速40”和旁边商家招牌里的数字40单看局部非常像。这三个问题共同指向一个方向需要在特征提取层补上对全局关系的建模能力而mobileVIT正好提供这种能力。2.2 mobileVIT的核心模块拆解mobileVIT最早是面向移动端图像分类提出的轻量Transformer结构它的出发点很直接ViT需要海量数据和超长训练时间在中小规模数据集上不如CNN而普通CNN又缺少跨空间位置的全局注意力。mobileVIT采用混合架构在浅层保留卷积在深层把特征图切块后送入Transformer做自注意力再把结果恢复到特征图。空间自注意力让每个位置能直接“看到”远处位置的像素不再需要靠卷积一层层堆叠去扩大感受野。它有两个参数设计会影响工程结果。第一是head数一般取4或8太小注意力表现力不足太大计算量成倍增长。第二是depthTransformer block堆叠层数在YOLOv5里我一般设在2到4层再大性价比就很低了。因为YOLOv5本身有足够深的卷积层mobileVIT block只要承接全局上下文的补充不需要做成一个完全独立的检测架构。# 估算在某一特征层上跑自注意力的中间矩阵量级 def attn_cost(h, w, heads4): tokens h * w # 每个head的QK^T矩阵形状为 [B, tokens, tokens] cost tokens * tokens * heads return cost print(attn_cost(20, 20), attn_cost(40, 40))这段短代码用来决定mobileVIT应该插在哪一层。20×20特征图的自注意力开销只有40×40的约四分之一所以工程里常见的做法是P3层之前保持纯卷积只在P4、P5这种低分辨率特征图上插入Transformer block。如果你把block强行放到80×80的P2特征层训练速度会肉眼可见地变慢小目标的mAP反而可能因为优化困难而下降。2.3 融合改进的三种路径对比把mobileVIT和YOLOv5融合工程上常见有三种做法下面这张表可以帮你快速判断该选哪种。融合路径改动位置对检测结果的影响工程成本整体替换Backbonemodels/yolo.py中加载mobileVIT主干最彻底模型参数量变化大高需要重写通道对齐在C3里插入mobileVIT blockmodels/common.py新增blockyaml中替换部分C3局部和全局兼顾训练稳定中推荐在Neck后部加自注意力models/yolo.py在PANet输出层接attention只提升大目标低适合做消融结合“标注好的数据集、完整代码、训练好的权重文件”这种工程包来看第二种最常见。原因是它只改了common.py和模型配置文件yamlyolov5的损失函数、训练脚本、验证脚本都能原样复用。同时为了控制推理耗时通常只在40×40和20×20两路特征图上启用mobileVIT80×80的浅层分支保持原始卷积因为浅层分辨率高直接跑自注意力会让显存和延迟迅速上升。2.4 为什么还要保留yolov5的检测头换掉检测头收益很低。YOLOv5的检测头是卷积加anchor回归解码逻辑、NMS、训练时正负样本分配都经过大量场景打磨。交通指示牌本身类间形状和颜色差异大但类内尺度变化集中现有anchor机制足够拟合。若换成更复杂的Transformer检测头反而需要更多调参成本。保留检测头意味着融合改进的边界被限制在特征提取阶段项目方也更方便把自己的权重文件和其他YOLOv5系列权重做对比。回头看这个融合思路它的本质不是“用mobileVIT打败yolov5”而是“给yolov5补上全局上下文”。交通指示牌检测里漏检的往往不是最醒目的指示牌而是被遮挡、褪色、远距离背景下难以定义边界的目标。全局上下文能帮助模型在局部信息不充分时根据周围环境给出更合理的判断。3. 从项目资源到可运行数据、代码与权重文件的组织方式拿到一个“包含标注好的数据集、完整代码、训练好的权重文件”的工程包第一件事不是打开训练脚本直接跑而是确认三样东西能对上数据集路径、代码里的模型配置、权重文件的输出通道数。三者不一致后面所有步骤都是白费。3.1 先确认标注好的数据集是否“真的能用”交通指示牌数据集通常按YOLO格式存放目录结构是images和labels一一对应。标注文件是txt每一行代表一个目标框五列分别是class_id、中心点x、中心点y、宽w、高h后四列都是相对图像宽高的归一化坐标。打开一个标注文件如果出现大于1或小于0的坐标就说明标注文件坐标范围不规范训练时不会直接报错但会让边框回归学出一个偏移。拿到工程包后我一般会在数据集根目录执行一条检查命令python check_labels.py --images datasets/traffic_sign/images --labels datasets/traffic_sign/labels --img-size 640脚本逻辑是遍历每一组图片和txt检查类别数、坐标范围、目标框面积占比并把面积小于0.01的图像筛出来单独看一遍。交通指示牌的目标框面积经常不到整张图的1%这些框在标注和增强后很容易失真需要重点检查。如果发现标注框贴着图片边缘被切掉一半建议回到标注软件里重新给外接矩形留边距。工程包里的目录通常会按下面的方式组织跑训练前先把路径对上。文件/目录作用datasets/traffic_sign/images原始图片datasets/traffic_sign/labelsYOLO格式标注txtdatasets/traffic_sign/traffic_sign.yaml类别名称和路径配置runs/train/exp/weights/best.pt最优权重文件runs/train/exp/weights/last.pt最后一个epoch权重文件3.2 yolov5环境配置与mobileVIT依赖常见做法是在conda里新建Python 3.8环境安装PyTorch 1.10以上版本然后拉取yolov5官方仓库代码再把工程包里的models文件夹覆盖到项目同名目录。mobileVIT不依赖额外的大型包只要torch和torchvision版本匹配即可。requirements.txt里一般有opencv-python、pyyaml、tqdm、matplotlib这些常规依赖按顺序安装即可。conda create -n yolov5 python3.8 -y conda activate yolov5 pip install torch1.10.0 torchvision0.11.0 # 按你的CUDA版本选择 pip install -r requirements.txt这里有个容易踩的坑如果直接把工程包里的models放到和官方yolov5同名目录会因为文件覆盖导致yolo.py里的模块名不匹配。比较稳的做法是把models整个替换然后重启终端再运行python models/yolo.py验证模型能否正常构建。如果报了ModuleNotFoundError先检查common.py里新增的MobileViTBlock类有没有真正被yolo.py引用。3.3 融合模型的核心代码在C3里替换Bottleneck下面这段代码是融合改进里最核心的MobileViTBlock在工程包里通常写在models/common.py的末端。它不是官方yolov5自带组件而是根据mobileVIT思想补进去的轻量Transformer模块。class MobileViTBlock(nn.Module): def __init__(self, c1, c2, depth2, heads4): super().__init__() self.conv1 Conv(c1, c2, 1, 1) # 先降维 self.dconv nn.Sequential( nn.Conv2d(c2, c2, 3, padding1, groupsc2), nn.BatchNorm2d(c2), nn.ReLU(inplaceTrue) ) self.fold Conv(c2, c2, 1, 1) self.transformer nn.Sequential( *[TransformerLayer(c2, heads) for _ in range(depth)] ) self.unfold Conv(c2, c1, 1, 1) # 再升维 def forward(self, x): y self.conv1(x) y self.dconv(y) y self.unfold(self.transformer(self.fold(y))) return y xTransformerLayer负责实际的多头注意力核心是查询、键、值矩阵class TransformerLayer(nn.Module): def __init__(self, dim, heads): super().__init__() self.qkv nn.Linear(dim, dim * 3) self.proj nn.Conv2d(dim, dim, 1) self.heads heads self.dim dim def forward(self, x): B, C, H, W x.shape x_flat x.flatten(2).transpose(1, 2) # [B, H*W, C] q, k, v self.qkv(x_flat).chunk(3, dim-1) scale (self.dim // self.heads) ** 0.5 attn (q k.transpose(-2, -1)) / scale attn attn.softmax(dim-1) out attn v out out.transpose(1, 2).view(B, C, H, W) return self.proj(out)这段代码的逻辑是先通过1×1降维和深度可分离卷积提取局部特征再通过fold压缩空间Transformer层在整个特征图上做全局自注意力最后unfold把维度恢复并与原分支相加。参数上heads4、depth2是性价比比较高的起点如果显存充足depth4会带来一到两个百分点的提升但训练时间会增加30%左右。3.4 训练好的权重文件放到哪个路径训练好的权重文件一般有两个last.pt和best.pt。last.pt是最后一个epoch的权重适合断点续训best.pt是根据验证集指标保存的最优权重实际使用和评估都优先用这个文件。放到runs/train/exp/weights/下后可以用一条命令先验证权重文件能不能正常加载避免训到一半才发现通道数对不上python val.py --weights runs/train/exp/weights/best.pt --data datasets/traffic_sign/traffic_sign.yaml --img 640 --task val如果输出日志里mAP0.5的值和你预期的模型表现相差很远先别怀疑数据集先把best.pt放到原工程包自带的默认模型配置上跑一次确认权重本身没有损坏或类别数不对。还有一种情况是权重文件是在不同类别的数据集上训练的交通指示牌常见类别有禁行、限速、指路、警告如果类别顺序和你的数据集yaml不一致需要先对齐class顺序否则输出全是错位框。4. 训练、验证与小目标优化这一章进入实际操作。整个流程的最终目的有两个一是用训练好的权重文件把基础效果稳住二是在这个基础上把交通指示牌里的小目标漏检问题再压一压。4.1 先用权重文件跑一次验证验证是最先要做的事。它不产生新模型但能确认当前工程包的数据集、权重文件、模型结构三者是否匹配。常见的验证命令如下python val.py \ --weights runs/train/exp/weights/best.pt \ --data datasets/traffic_sign/traffic_sign.yaml \ --img 640 \ --conf-thres 0.001 \ --iou-thres 0.6 \ --save-json参数说明--conf-thres设置置信度阈值验证时设置0.001是为了不提前漏掉低置信度框这样计算mAP时能覆盖所有预测--iou-thres是NMS阶段的IoU阈值0.6是检测任务里比较常规的取值。--save-json会输出COCO格式的json结果方便后续用脚本分析错检和漏检。跑完看两个指标mAP0.5和mAP0.5:0.95。交通指示牌属于目标尺度差异大的场景如果mAP0.5在0.85以上说明基础能力没太大问题但如果mAP0.5:0.95明显偏低比如只有0.4说明边框和分类的精细化程度不够问题很可能出在小目标的定位精度上。4.2 用这份标注好的数据集做迁移学习如果工程包里已经带了训练好的权重文件最优策略不是从头训练而是基于它做微调。训练脚本里的--weights参数有几种传法传入官方yolov5s.pt表示用COCO预训练权重从头训练传入你手上的best.pt则是在已有模型基础上继续训练收敛更快。python train.py \ --data datasets/traffic_sign/traffic_sign.yaml \ --cfg models/yolov5s_mobilevit.yaml \ --weights runs/train/exp/weights/best.pt \ --epochs 100 \ --batch-size 16 \ --img 640 \ --hyp data/hyps/hyp.scratch-low.yaml这里的关键是--hyp参数。hyp.scratch-low.yaml是yolov5自带的数据增强保守配置适合从预训练权重继续微调的场景。如果数据集里的图片光照变化很大可以把hsv_h、hsv_s、hsv_v三个值从默认的0.015、0.7、0.4上调到0.02、0.8、0.5帮助模型适应不同光线下的指示牌颜色变化。注意不要一次性把增强参数调满否则验证集会因为训练数据和真实场景差异太大而出现震荡。4.3 yolov5超参数中三个最值得调的字段我一般不会一上来就调很多超参数只调三个见效最快的。超参数默认值交通指示牌推荐值作用anchor_t4.03.5控制正负样本匹配时目标框与anchor的宽高比范围小目标多时适当调低cls_pw1.00.8类别分支的损失权重类别不均衡时有用fl_gamma0.00.5Focal Loss削弱背景样本背景误检多时增大anchor_t影响最大。YOLOv5在训练前会自动计算基于数据集的anchors但是默认anchor_t4.0会允许目标框和anchor宽高比相差4倍以内都算正样本。交通指示牌里同时存在巨大近景牌和远景小牌宽高比跨度大把这个值降到3.5会让正样本匹配更严格减少把背景局部纹理当成目标的情况。改完超参数后用tensorboard监控训练曲线比只看loss数值更直观重点看val精度在50个epoch后是否还有下降趋势。4.4 小目标检测优化P2层、切图与Mosaic增强交通指示牌的小目标检测是融合改进里最常见的优化点。yolov5默认从P3层开始做检测P3层是80×80对应640输入下的8倍下采样理论上能覆盖小目标但实际效果受特征语义限制。想在不动网络结构的前提下提升小目标可以先试这三个方法。P2层是在原来的P3、P4、P5之外再增加一个160×160的检测分支。在模型配置文件的head段加上反向连接让P2特征图进入Neck和检测头。这会明显增加计算量但交通指示牌这种目标通常只占画面几个百分点收益很直接。不过要注意P2层特征图维度高直接跑mobileVIT会非常吃显存所以P2分支一般保持纯卷积结构。切图法适合标注框极小且分布稀疏的数据集。把一张1280分辨率的图切成四张640的图再分别训练和推理相当于不做网络改动就把小目标放大。缺点是推理时需要做边界框坐标还原在代码里加一个坐标偏移量即可。Mosaic增强是yolov5训练时最重要的数据增强手段它把四张图拼成一张迫使模型学习小目标位置同时能增加目标数量的多样性。在hyp配置里mosaic1.0表示100%概率应用如果训练后期loss不降可以在最后20个epoch把mosaic临时置为0让模型适应真实图像的单图分布。4.5 常见报错与排查这里列出训练过程中最常遇到的三类问题以及我通常会做的排查顺序。# 显存不足 python train.py ... --batch-size 8显存不足是最常见的优先把batch-size减半而不是调低图片分辨率因为分辨率变化会直接影响小目标的有效尺寸。如果batch-size降到4还不能训再看--workers多进程加载也有可能吃满内存。# 权重文件加载失败 python scripts/count_weights.py runs/train/exp/weights/best.pt如果加载权重时报unexpected key说明权重文件的模型结构和你当前的yaml配置不一致优先检查类别数量和backbone层的通道数是否匹配。如果报pkl.load错误可能是PyTorch版本跨度过大导致反序列化失败此时在同样版本的环境下重新保存一次权重文件即可。# 全图都是低置信度预测 python val.py ... --conf 0.01 --save-txt这种情况往往是anchors和数据集尺寸不匹配。先关掉--autoanchor重新计算再把anchor_t调回到4.0对比一次。如果两个实验指标差不多说明问题不在anchors而在模型结构里的融合模块没有真正参与训练此时需要回到代码检查MobileViTBlock的梯度是否正常传播。5. 部署与再训练把权重文件变成线上检测能力5.1 导出ONNX和TensorRT训练好的best.pt要上线一般先转成ONNX再视设备需求转TensorRT。YOLOv5官方脚本提供了现成导出命令python export.py --weights runs/train/exp/weights/best.pt --include onnx --simplify --opset 11导出后先用onnxruntime跑一遍单张推理确认结果和PyTorch推理一致。如果输出框的位置有偏移多数原因是opset版本过低改成--opset 12再试。对于Jetson Nano这类设备TensorRT比ONNX Runtime更快转换时注意把--workspace设为4G以上否则batch较大时会报显存不足。5.2 在Jetson Nano上做实时推理Jetson Nano是挑战性比较大的部署环境。它显存只有4GB跑到640分辨率时即使batch_size为1也需要锁频运行。建议把输入分辨率降到544×544并在导出时指定固定尺寸让TensorRT做Engine优化。如果想保持更高精度可以在Jetson上只跑预测脚本训练和调参仍然在PC上完成。import cv2 import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) image cv2.imread(sign.jpg) image, scale, pad letterbox(image, new_shape640) output session.run(None, {session.get_inputs()[0].name: image})逻辑说明letterbox操作用来把任意尺寸图像等比缩放并填充到640×640避免直接resize导致目标框变形。这里输出的坐标经过scale和pad计算要还原到原图是很多初学部署时最常出错的地方。5.3 新场景快速迭代的方法如果换到国道、高速公路或新的城市原有标注好的数据集不一定覆盖所有新指示牌。常见做法是拿当前best.pt对新场景图像做一次预测然后只修正置信度较低的框把修正后的结果追加到训练集重新微调20个epoch。这个过程可以迭代三轮每轮都能看到mAP0.5:0.95的小幅提升。此外用混淆矩阵交叉检查“警告牌被识别成指路牌”这类类别混淆比只看总mAP更有参考价值。最后一轮训练完成后记得输出三个文件best.pt、best.onnx和对应的检测类别顺序这个顺序差一位线上服务就会全错了。本文还有配套的精品资源点击获取