ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5交通标志检测实战:从数据集配置到模型部署全流程解析

YOLOv5交通标志检测实战:从数据集配置到模型部署全流程解析 简介这是基于YOLOv5的交通标志物检测项目完整包面向计算机相关专业正在完成课程设计或期末大作业的学生以及需要人工智能、图像处理方向实战练习的深度学习开发者。项目整合了可直接运行的检测源码、训练好的模型权重与全部数据覆盖从数据准备、模型训练到推理检测的完整流程能够帮助使用者快速搭建交通标志识别系统。资源共266个文件压缩包约423MB主要包含yaml配置文件、py训练与推理脚本、jpg/png图片数据集、pt模型文件、sh辅助脚本以及训练日志、Dockerfile等目录结构清晰便于按模块查阅和二次开发。项目源自个人97分的期末大作业代码经过严格调试下载后即可运行适合作为课程设计或毕业设计的完整参考也可用于学习YOLOv5的工程组织、数据组织与训练细节并在此基础上拓展改进。目前已有362人学习使用。1. 交通标志检测为什么值得用 YOLOv5 源码包起步交通标志物检测是目标检测里最典型的“小目标密集类别多实时性要求高”场景YOLOv5 恰好是这些年做课程设计、竞赛和工程原型时最常见的选择。拿到一份带 YOLOv5 源码、训练好的模型权重和完整数据集的工程压缩包意味着你不用从零开始标数据、不用花两周调环境核心工作就变成三件事把数据格式捋顺、把训练参数跑通、把模型部署到实际图片或视频流里。这篇文章就按“源码包里有什么 → 数据怎么对齐 → 训练怎么调 → 模型怎么验证部署”的顺序把整个落地链路拆开讲顺带把最容易翻车的几个坑列出来。适合正在做毕设、准备竞赛或者刚接触 yolov5 目标检测、想用现成资源跑通一个真实项目的开发者。2. 先拆解 YOLOv5 交通标志检测方案结构、数据和选型理由2.1 交通标志检测为什么选 YOLOv5 而不是其他模型交通标志检测的难点不在“能不能检出”而在“小目标能不能稳定检出”。路牌在画面里往往只占几十个像素加上逆光、遮挡、模糊很多检测模型在通用数据集上表现不错一换到真实街景就掉点。YOLOv5 在这一点上有几个结构上的优势值得先理解。它仍然沿用 CSPDarknet 作为骨干网络。CSP 结构把特征图分成两部分一部分走密集卷积块一部分直接跨越连接这样既减少了计算量又保留了梯度信息。在列车上骨干网络输出三种尺度的特征图分别对应大、中、小目标。小目标检测靠的是浅层特征图分辨率高但语义信息弱所以 YOLOv5 在检测头前面加了一个 PANet 路径聚合结构把深层语义信息往浅层特征图上“喂”。这就解释了为什么它在路牌这类小目标场景下比早期 YOLOv3 稳定——不是玄学是特征融合的路径确实更合理。另一个实际选型理由是工程成熟度。YOLOv5 的源码组织方式是所有版本里最清晰的data、models、utils 三层目录职责分明训练、验证、导出、推理脚本各自独立。对新手来说改数据路径、换模型配置文件、跑通一次训练都是直接改参数就能完成的事不需要碰底层 CUDA 代码。对老手来说它的分布式训练、混合精度、模型导出 ONNX 的链路也都是现成的改造成本低。还有一个容易被忽略的点YOLOv5 对显存的容忍度比很多新模型好。低显存运行模型是很多学生党的刚需4G 显存的笔记本 GPU 或者纯 CPU 机器只要把 batch-size 降到 8 以内、开启梯度累积照样能训练小规模数据集。这一点在后面的参数设置里会具体展开。2.2 一份交通标志检测数据集应该包含什么拿到“全部数据”之后第一件事不是直接开训而是先核实数据的组织方式是否匹配 YOLOv5 的预期格式。常见的数据集包会包含 images 和 labels 两个顶层目录分别存图片和标注文本。标注文本每行对应一个目标格式是class_id x_center y_center width height注意这里是归一化后的中心点坐标和宽高不是像素值也不是 VOC 的 XML 格式。很多现成数据包是从 TT100K、CCTSDB 这类公开数据集转换过来的但转换过程常常留下一些边界问题比如类别编号不连续、某些图片没有对应标签文件、像素坐标和归一化坐标混用。这些问题在训练时不会立刻报错而是表现为 loss 降不下去或者某些类别完全检测不到。交通标志数据本身还有一个特点需要关注类别分布严重不均衡。限速标志、禁止标志在数据里可能占了大半而一些罕见的警告标志只有几十张。YOLOv5 对每个类别都会分配一个检测头但如果某个类别训练样本过少这个类别在训练过程中会被其他类别“带偏”。所以拿到数据后要做一个统计至少要看每个类别的图片数和目标数分布。用一段简单脚本就能做到python import os from collections import Counterlabel_dir labels/train cls_counter Counter() total_boxes 0for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), r) as f: for line in f: parts line.strip().split() if len(parts) 5: cls_counter[int(parts[0])] 1 total_boxes 1print(总目标数:, total_boxes) for cls_id, cnt in sorted(cls_counter.items()): print(f类别 {cls_id}: {cnt} 个目标)这段脚本遍历训练标签目录统计每个类别出现的次数。注意最后一行的循环里我只取了前五列因为有些标注文件会追加额外的属性列比如遮挡程度或难度等级直接取前五列解析成 class_id 四坐标即可。如果发现某些类别只有个位数目标建议先做数据增强或者干脆合并粗粒度类别否则训练出来的权重在那几个类别上基本是碰运气。2.3 源码包里的模型配置s、m、l 怎么选YOLOv5 的 models 目录下有一组以 yaml 结尾的配置文件常见的是 yolov5s.yaml、yolov5m.yaml、yolov5l.yaml、yolov5x.yaml。它们的差别不是算法结构而是网络的宽度和深度系数。s 的 width_multiple 是 0.50depth_multiple 是 0.33m 是 0.75 和 0.67l 是 1.0 和 1.0x 是 1.25 和 1.33。在交通标志检测这种场景下我的建议是优先从 s 或 m 起步。原因很直接路牌目标小但不复杂类别之间的区分度高不需要特别深的网络来提取细粒度纹理特征。s 模型在 1080Ti 上做推理能做到 100 FPS 以上训练一轮也要不了太久。m 模型精度会高一些主要高在重复检测的抑制上但代价是训练时间约为 s 的 1.5 倍。如果你的数据集只有几千张图直接上 l 或者 x 很容易过拟合验证集 mAP 反而往下掉。配置文件里还有几个关键数值建议改动。最值得调的是 anchors。YOLOv5 默认的 anchors 是针对 COCO 数据集聚类出来的COCO 里目标普遍偏大。交通标志普遍是扁平的、小尺寸目标用默认锚框会导致早期训练不稳定。源码里有自动计算锚框的逻辑训练时加一行参数即可bash python train.py --data data/traffic_sign.yaml --cfg models/yolov5s.yaml --weights --batch-size 16 --epochs 100 --img 640 --auto-anchorauto-anchor 会在训练开始前根据标注数据重新聚类 anchors并更新到模型配置里。首次训练时这个环节会多花一两分钟但对小目标检测的收益是立竿见影的。如果你用的是别人训练好的权重继续微调同样建议先让 auto-anchor 跑一遍因为原始权重的锚框来自源数据集不一定适配你的目标尺寸分布。3. 把源码包跑起来数据格式对齐、训练命令与关键参数3.1 数据目录组织与 YAML 配置的坑YOLOv5 在训练时通过一个 YAML 文件来定位数据集而不是直接在命令行里写死路径。拿到项目压缩包后先看有没有这个配置文件通常叫 data/traffic_sign.yaml 或类似的名字。标准内容应该是这样yaml train: ./datasets/traffic_sign/images/train val: ./datasets/traffic_sign/images/valnc: 6 names: [warning, prohibitory, mandatory, stop, yield, guide]train 和 val 指向的是图片目录YOLOv5 不直接读标签目录而是根据图片目录自动推导同级 labels 目录。也就是说 images/train 下的图片 train_001.jpg 会去找 labels/train/train_001.txt一一对应。推导规则是找图片路径中名为 images 的目录替换成 labels。如果压缩包里目录结构不规范比如 labels 和 images 不在同一根目录下训练时会报出一堆找不到标签的警告但进程不会中断只是那部分图片被跳过。很多项目包里的 YAML 文件名带空格或中文这本身不是问题问题是 Windows 环境下某些版本的 YOLOv5 在加载时对编码敏感。保险做法是把数据集目录和配置文件路径都保持纯英文中间不要有空格。路径的另一种常见翻车是用了相对路径且从别的目录启动训练脚本导致数据集找不到。建议在 train.py 所在目录下运行命令或者把 YAML 里的路径写成绝对路径。nc 和 names 必须和数据集的标注一致。这里的第一个坑是 nc 写错。比如数据里有 6 个类别但 YAML 里 nc 写成 5训练不会报错只是最后一个类别的所有标注会被当成 out-of-range 数据丢掉。真正坑的是 names 和标注里的 class_id 对不上因为训练时类别索引是从 0 开始的names 只是打印日志用的不会用来校验标注。这意味着如果你发现训练日志里显示类别名和实际内容对不上说明数据标注本身可能有问题需要重新核对标签文件。3.2 用训练好的权重跑通第一张图最小推理命令模型训练前先验证包里的预训练权重能不能正常加载和推理。这一步能帮你把环境问题、路径问题和模型结构问题一次性暴露出来。假设你有权重文件 best.pt放在 runs/train/exp/weights 下先跑一张测试图bash python detect.py --weights runs/train/exp/weights/best.pt --source test_images/street_01.jpg --img 640 --conf 0.25 --iou 0.45detect.py 执行后会在 runs/detect/exp 下生成标注了检测框的结果图。上图如果能看到标志被框出来且类别正确说明源码和权重都是可用的。如果结果图全空先调低 conf 阈值到 0.1 试一试很多时候不是模型没检测到而是置信度比你设的阈值低。真实道路场景中远处的小路牌只有 0.2 左右的置信度这在很大程度上是正常的。推理时还有一个关键参数是 imgsz也就是送入网络前图片被缩放到多大。默认是 640。交通标志检测建议推理时设成 960 或 1280代价是推理速度下降但小目标召回率会明显提升。原因是标志在 640 尺寸下可能只有 20 个像素宽经过三次下采样后特征图上就剩几个点了特征信息所剩无几。把输入撑到 960 后同样一个标志在特征图上能占据更多像素检测头更容易命中。3.3 从头训练的命令模板与 6 个必调参数预训练权重能跑通之后如果你要训练自己的数据集YOLOv5 支持两种初始权重策略。第一种是用 COCO 预训练权重做迁移学习适合数据量小但场景分布接近的情况这是最常见的做法。第二种是完全空白权重也就是 weights 参数设为空字符串模型会从随机初始化开始训练通常不推荐因为收敛慢且很容易在交通标志这种细节分辨任务上陷入局部最优。一个适合交通标志数据集从头训练的模板命令如下bash python train.py--data data/traffic_sign.yaml--cfg models/yolov5s.yaml--weights yolov5s.pt--batch-size 16--img 640--epochs 150--optimizer SGD--lr0 0.01--cos-lr--workers 4--device 0这个命令里有六个参数是在调参时会反复碰到的逐一说明。batch-size 受显存限制16 在 8G 显存下基本是上限如果只有 4G 显存降到 8并加上 --accumulate 4 用梯度累积保住等效批次大小。img 是训练输入尺寸交通标志建议 640 起步显存充裕可以提到 800。epochs 不要少于 100这类数据集小训练充分与否看的是模型对背景干扰的抑制能力轮次少了容易欠拟合。optimizer 推荐 SGD 而不是 Adam因为 Adam 在早期收敛快但最终 mAP 通常不如调好学习率的 SGD。lr0 初始学习率 0.01 是 YOLOv5 的默认推荐值数据集越小越要保守一点数据只有几百张图建议降到 0.005。cos-lr 启用余弦退火学习率它会让学习率在训练后期平滑下降对稳定最终权重有明显帮助。参数不是设完就完事。训练过程中要盯住两个东西做动态调整边界框损失和类别损失。YOLOv5 每个 epoch 都会把损失汇总打印到终端。正常情况下前 20 轮总损失会从两位数快速降到个位数然后进入缓慢下降区间。如果损失在前 10 轮不降反升基本可以确定是学习率过大停掉把 lr0 减半再继续。如果损失在 80 轮后还在明显波动说明模型在验证集上可能过拟合了这时候改成加载之前 checkpoint 继续训不如重新跑一个更短 epoch 的训练。4. 交通标志物检测落地中的 5 个高频坑现象、原因、处置4.1 显存不足导致训练中断现象训练跑到第几个 batch 时直接报 CUDA out of memory有时候还会连带把整个 Python 进程搞崩桌面花屏。原因imgsz 过大或 batch-size 超出显存容量。很多人拿默认的 batch-size 16、imgsz 640 直接跑在 4G 显存上必炸。YOLOv5 训练时缓存图片的机制也会额外占用显存特别是开启了 cache-images 参数后整个数据集直接映射进显存。解决先看一眼 GPU 显存总量nvidia-smi 命令即可。4G 到 6G 显存batch-size 设为 8imgsz 保持 640不要开 cache。6G 到 8Gbatch-size 16 可以跑但尽量关掉其它占显存的应用。如果 batch-size 降下来后梯度噪声变大导致训练不稳定加 --accumulate 4等效于用 32 的 batch-size 更新一次梯度。提示训练时不要开 TensorBoard 以外的浏览器页面Chrome 对显存的占用有时候大到足以让训练中断。4.2 标签文件报错 class id 超出范围现象训练日志里频繁出现 WARNING 提示 label class 超出 nc 范围同时对应图片被跳过。严重时训练完看 mAP某些类别结果为零。原因数据集整理时类别编号没有重新从 0 开始连续编号或者 YAML 中 nc 统计错了。比如原始数据有 8 类你删掉 2 类后标注文件里还残留 class_id 6、7但 nc 设成了 6于是训练时所有编号大于等于 6 的标注全部无效。解决写一个小脚本扫描所有标签文件里的类别编号分布前面 2.2 节那个脚本就可以用。确认最大编号后把 nc 设为最大编号加一。如果某些类别确实不要了用脚本把对应标注行删掉不要只改 YAML否则训练时标签文件残留的旧编号会持续产生告警。python import oslabel_root labels keep_classes {0, 1, 2, 3, 4, 5}for sub, _, files in os.walk(label_root): for fname in files: if not fname.endswith(.txt): continue path os.path.join(sub, fname) with open(path, r) as f: lines f.readlines() new_lines [] for line in lines: parts line.strip().split() if not parts: continue if int(parts[0]) in keep_classes: new_lines.append(line) with open(path, w) as f: f.writelines(new_lines)这个脚本会遍历所有标签文件把不在 keep_classes 集合里的目标整行删掉。注意脚本里读写都在同一个路径执行前建议把原始数据备份一份。别觉得这个步骤多余很多项目包里的所谓“全部数据”其实是别人从多个数据集合并后直接灌进 YOLOv5 的类别编号混乱是常态。4.3 训练损失下降但验证 mAP 很低现象训练 loss 正常下降到 100 轮后趋近稳定但 val 模式跑出来的 mAP 只有 0.2 左右比同样数据量的其它项目差一大截。原因这种情况八成是训练集和验证集数据分布不一致。压包里如果训练集和验证集来自不同数据源比如训练集全是中国路牌验证集里混了欧洲路牌模型在验证集上自然会翻车。另一个常见原因是默认的 10% 划分逻辑如果数据目录本身没有划分 valYOLOv5 会自动从训练集抽 10% 当验证集这会导致有标注错误的数据同时出现在训练和验证里评估结果虚高。解决自己动手重划分数据。用 sklearn 的 train_test_split 或者手写一个按 9:1 划分的脚本确保划分前把同一个场景的连拍帧归到一起。交通标志数据集中常常有同一块路牌在视频连续帧里的多张截图如果不做场景聚类直接随机划分这组极度相似的图片会同时出现在训练和验证中mAP 虚高到 0.8 以上实际部署时立刻原形毕露。python import os import shutil from sklearn.model_selection import train_test_splitimage_dir datasets/traffic_sign/images_full label_dir datasets/traffic_sign/labels_full images [f for f in os.listdir(image_dir) if f.endswith(.jpg)]train_imgs, val_imgs train_test_split(images, test_size0.15, random_state42)for split, imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(fdatasets/traffic_sign/images/{split}, exist_okTrue) os.makedirs(fdatasets/traffic_sign/labels/{split}, exist_okTrue) for img_name in imgs: base os.path.splitext(img_name)[0] shutil.copy( os.path.join(image_dir, img_name), os.path.join(fdatasets/traffic_sign/images/{split}, img_name), ) if os.path.exists(os.path.join(label_dir, base .txt)): shutil.copy( os.path.join(label_dir, base .txt), os.path.join(fdatasets/traffic_sign/labels/{split}, base .txt), )random_state 固定为 42 是刻意为之目的是一次划分后不确定的部分可以复刻。实际使用时建议换几个随机种子跑一两轮观察 mAP 波动幅度。波动大说明数据集规模不够或者标注质量不稳定这时候优先补数据比调模型更有效。4.4 小目标漏检严重远距离路牌完全没反应现象近处大路牌能正确识别但图片中远景处的限速牌、人行横道牌全部漏检甚至一些中等距离的指示牌也时有时无。原因除了前面提到的输入尺寸问题还有一个是 NMS 阈值设置。交通标志密集出现在同一区域时多个检测框重叠默认的 iou 0.45 会把部分正确检测框当作重复框抑制掉。另外如果训练时 imgsz 是 640但推理时直接提到 1280输入分辨率变化会改变小目标的响应强度但分布变了模型不一定适应得过来。解决先做推理侧调整。把 imgsz 从 640 提到 960conf 阈值降到 0.2看召回率有没有提升。如果提升明显说明问题出在输入尺度上需要重新在 imgsz 960 下微调模型。训练侧的做法是开启多尺度训练在 train.py 命令里加上 --multi-scale模型每个 batch 会从 480 到 960 之间随机取尺寸让模型对目标尺度变化更鲁棒。注意 multi-scale 训练会显著增加训练时间显存占用也会上升。提示小目标漏检的排查顺序永远是先看输入尺寸再看 anchors最后才动网络结构。大部分场景下前两招已经能解决 80% 的问题。4.5 换机器后权重加载失败或推理结果异常现象在 A 机器上训练好的权重拷贝到 B 机器后用同样的源码加载报错 Missing keys 或者 Unexpected keys。就算加载成功推理结果也和原来完全不一致。原因一种情况是源码版本不一致。YOLOv5 不同版本之间的模型定义细节有差别v5.0 训练出来的权重直接塞给 v7.0 的模型结构对不上是必然的。另一种情况是权重文件路径被別的东西覆盖了压缩包里 root 目录和子目录下各有一个同名 pt 文件加载时选错了一个。解决加载前先确认两边的源码版本一致最简单的方法是比对 models/yolo.py 里的 class Detect 定义。然后是核对权重文件的哈希值用 sha256 校验拷贝前后文件是否一致。最后如果加载时出现 missing keys不要硬加载先看一下当前模型配置文件的 yaml 和权重训练时使用的 yaml 是否同一个文件最常见的就是 model 的名字相同但 anchor 数量不一致。python import torchckpt torch.load(best.pt, map_locationcpu) print(ckpt.keys()) print(ckpt[model].yaml if model in ckpt else no model key)这段脚本读取权重文件后打印模型配置文件信息。如果这里显示的 yaml 结构物和你当前源码里的不一致基本可以断定是跨版本加载。跨版本加载不是不能解决但需要自己写权重迁移脚本非必要不建议走这条路直接找对应版本的源码重新训练或者推理更省时间。5. 验证模型效果并部署从 mAP 指标到实际视频流推理5.1 用 val.py 评估训练好的模型看哪几个指标训练不结束就不知道模型行不行YOLOv5 的验证脚本在训练过程中会自动跑但为了拿到一个可靠的最终结果训练完成后要单独用 val.py 手动跑一次全量验证。命令如下bash python val.py--weights runs/train/exp/weights/best.pt--data data/traffic_sign.yaml--img 960--conf 0.001--iou 0.6--half注意这里我把 conf 设成了 0.001这不是为了让结果好看而是为了计算 mAP 时把所有置信度档位覆盖全。mAP 的计算逻辑是统计不同置信度阈值下 precision 和 recall 的曲线面积如果验证时 conf 阈值太高曲线起点就会缺失mAP 值会失真。iou 0.6 是 COCO 风格的标准设置表示预测框和真实框重叠度超过 60% 才算正确命中。验证结果会输出一个表格里面逐类列出 precision、recall、mAP0.5 和 mAP0.5:0.95。对交通标志检测来说mAP0.5 是最关键的参考值它表示预测框大致位置正确即可判定无需像素级精确。因为路牌本身形状规整、边缘清晰mAP0.5 达到 0.85 以上基本满足部署要求。mAP0.5:0.95 是更严苛的指标它要求框的重叠度在 0.5 到 0.95 之间逐级计算路牌如果存在标注框偏移这个值会很难看所以这个指标的绝对值不必过度纠结只需要对比训练前和训练后是否提升。还有一个需要人工复核的维度是类别混淆。val.py 会生成一张混淆矩阵图保存在 runs/val/exp/confusion_matrix.png。查看这张图重点看对角线值是否远大于非对角线值。如果出现某两类互相误判严重比如“禁止驶入”和“禁止通行”混在一起说明这两类的视觉特征过于接近要么数据标注本身有争议要么需要增加这两类在训练集中的样本量。5.2 把模型部署到视频流用 detect.py 跑摄像头实时检测模型验证通过后部署的最常见形态是视频流检测。YOLOv5 的 detect.py 本身就支持从摄像头或 RTSP 流读取命令比图片推理多一个参数bash python detect.py--weights runs/train/exp/weights/best.pt--source rtsp://192.168.1.100:554/stream1--img 960--conf 0.25--iou 0.45--max-det 20--max-det 20 是建议加入的参数它限制了单张图片最多输出 20 个检测框。交通标志场景里一个画面往往同时存在多个标志但绝不会超过二三十个限制数量可以防止误检的零散小框刷屏也为后处理节省时间。如果摄像头是 USB 摄像头source 改成 0 即可0 代表第一个设备编号。视频流部署时最容易忽略的是帧率匹配。detect.py 默认逐帧处理如果你的 GPU 推理速度是 50ms 一帧摄像头输入是 30 FPS那么处理速度跟不上输入画面会出现堆积延迟。解决思路有两个一是把输入帧率降到 15 FPS在摄像头端或者用 OpenCV 的 VideoCapture 设置 CAP_PROP_FPS二是改用跳帧策略只处理关键帧。YOLOv5 的 detect.py 没有内置跳帧逻辑但你可以把视频流先经过一段采样脚本再送入推理适合用在边缘设备上。5.3 ONNX 导出与运行时部署脱离 PyTorch 环境的移植如果部署目标是嵌入式设备或纯 C 环境PyTorch 权重不能直接使用需要先导出为 ONNX 格式。YOLOv5 自带 export.py一条命令完成转换bash python export.py--weights runs/train/exp/weights/best.pt--img 960--batch-size 1--include onnx--simplify--simplify 会调用 onnx-simplifier 对计算图做常量折叠和冗余节点消除这个操作通常让模型体积减小 10% 左右推理速度提升约 20%。导出的 onnx 文件可以用 ONNX Runtime 在 CPU 上跑也可以用 TensorRT 转成 engine 文件在 N 卡上跑。导出过程中有一个常见坑PyTorch 模型里的 opset 版本和运行时库支持的版本不一致。如果运行时报 unsupported operator优先检查两边的 opset 版本。export.py 可以通过 --opset 参数指定一般设 12 或 13 兼容性最好。另一个坑是动态尺寸默认导出的是固定尺寸 960x960 的静态图如果推理时输入的图片尺寸不是整数倍模型会报尺寸不匹配。交通标志场景建议固定尺寸导出部署时统一做 letterbox 填充到 960x960比使用动态维度在多数设备上更稳定。导出后验证一步不可少同一张图分别用 PyTorch 权重和 ONNX 模型跑推理比对检测框坐标和类别。YOLOv5 的导出脚本会顺带做一次校准输出一个参考结果但如果那一帧恰好没有目标校准就形同虚设。建议自己挑一张包含多个不同类别标志的实拍图对比两边的输出框坐标误差误差超过 2 个像素就需要检查预处理逻辑是否一致特别是归一化系数和颜色通道顺序。6. 把交通标志检测做成可靠方案的三个进阶技巧第一学会用小目标切片推理绕过特征丢失问题。在做检测车道路牌这种长宽比极端的画面时全图直接缩放会让标志在输入图上变得过小。我的做法是把原图按横向切成三段每段独立推理再将结果拼回原坐标。这个技巧不改变模型但对召回率的提升比换大模型更明显而且切图后的推理天然支持多线程并发三个线程各跑各的分段最终延迟几乎不变。代价是相邻切片边界上的目标可能被切开所以我一般让每段之间重叠 30 像素重叠区域内的检测框取置信度更高的那个。第二在类别不平衡数据集上做针对性增强。交通标志数据集中警告标志和禁令标志数量通常碾压指路标志。老手不会在模型结构上折腾而是在数据增强上动手。YOLOv5 在训练时默认开启 mosaic 增强但它的含义是四张图拼在一起如果每张图里都是同一类标志增强效果反而加剧了类别不平衡。一个简单的替代方案把少数类图片在每次 epoch 前重复复制两次到训练目录相当于在采样阶段做了类别重加权。这个做法比改损失函数省事得多效果也更直接。有个前提是复制图片时要对应复制标签文件并且用随机重命名避免和原图重叠。第三做一次跨场景泛化测试。模型在验证集上分数高不代表实际部署没问题。我习惯在训练结束后手机到附近路口拍十张不同光线、不同角度的路牌照片直接喂给 detect.py 跑一遍要求每张图至少能检测出 80% 的标志。这步测试能暴露出数据增强的盲区比如你的训练数据全是白天晴天模型可能在逆光场景下漏检。发现这个问题后用 OpenCV 的 HSL 空间随机调整亮度做一轮增强再微调比重新标注数据快得多。这套流程走下来你会意识到交通标志检测这个项目真正的难度不在模型而在数据编排和部署细节。我踩过最深的一次坑是花了三天调模型结构最后发现数据目录里有一批图片的标签是错的类别名称和编号错位所有努力白费。从那以后我的习惯是训练前永远先跑一遍标签检查脚本推理前永远先用一张实际拍而不是网上下载的图片做冒烟测试。希望这个习惯也能帮你少走弯路项目落地顺利。本文还有配套的精品资源点击获取
返回列表