ARTICLE DETAIL

资讯详情

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

YOLOV5交通标志识别实战:数据集转换到夜间场景调优全流程

YOLOV5交通标志识别实战:数据集转换到夜间场景调优全流程 简介基于YOLOv5的交通标志识别检测项目面向毕业设计、课程设计与期末大作业等场景提供从数据集标注、模型训练到推理部署的完整闭环。资源共266个文件、压缩包约423MB核心包括53个Python源码、10个PT预训练权重、YAML模型配置、图像样本与TXT标签文件同时附带Dockerfile、Shell脚本和TensorBoard事件文件整体目录组织清晰可直接复现训练流程或进行二次开发。项目代码注释细致关键模块均经过严格调试普通环境下简单配置即可运行新手也能快速上手训练结果CSV与事件日志可辅助理解模型收敛过程便于论文与答辩材料中的结果分析。该方案可整体作为毕业设计或期末大作业使用曾获导师高度认可目前已有105人浏览学习适合需要高效完成高质量视觉检测项目的学生参考借鉴。1. 交通标志识别项目为什么绕不开 YOLOV5、数据集与模型这三件套毕业设计拿到“交通标志识别检测”这个题目时大多数人第一反应是这不就是拿 YOLOV5 训练一个模型嘛。真做起来你会很快发现模型反而是最不用操心的部分真正卡住人的是交通标志数据集的整理、标注格式的转换、以及训练完成之后怎么把模型效果讲清楚。这个题目能拿高分通常不是因为你把 YOLOV5 的代码改得多花哨而是因为你把数据、训练、评估这条链路走得完整且可复现。这篇笔记会从交通标志数据集的选型与清洗到 YOLOV5 的自定义训练、推理导出再到常见训练踩坑和夜间场景调优完整讲一遍我实际跑这个课题时的做法新手能照着复现熟手也能对照参数和边界。2. 数据集的筹备与转换拿到交通标志图之后第一件事是清洗标注2.1 交通标志检测数据集怎么选公开数据集与自采数据的分工交通标志识别在学术界有个常见误区一上来就找 GTSRB。GTSRB 是分类数据集一张图一个标志只有类别标签没有目标框。做检测你需要的是带标注框的数据集所以更常见的选择是 TT100K——腾讯街景全景下的交通标志数据集。TT100K 覆盖了多种标志类别拍的是真实街景车型、遮挡、光线变化和国内交通标志的样式都贴近实际场景。另一个可用来源是 Mapillary Vistas但它更偏向分割坐标要二次转换预处理成本高。我给毕设项目的建议是双轨走先用公开数据集把模型基线跑通再采一批校园或城市道路的实景图做泛化验证。公开数据集负责证明你的模型结构和训练流程正确自采数据负责证明你的方案在真实场景下可用这正好对应毕设评审里“模型有泛化能力”这一条。自采数据不需要太多300 到 500 张包含标志的图片就够但每张图都要检查标注框是不是把标志完整框住。实际经验是公开数据集里的标志通常又大又正自采图里经常出现路边杆子上的侧视角标志这类图不参与训练单独作为验证集会给你一个很扎心的结果训练 mAP 很高实拍图检测一塌糊涂。TT100K 的原生标注是 JSON 格式类别定义也不完全是简单的数字 ID。直接拿它喂 YOLOV5 之前我一般会把 JSON 统一转成更通用的 VOC XML 中间格式再用脚本转成 YOLO 需要的 txt。这样做的原因是后续如果换数据集比如换成 CCTSDB 或自己标注的数据中间格式的转换脚本可以复用。CCTSDB 是长沙理工发布的交通标志检测数据集标注格式就是 VOC 风格很多做这个题目的同学最后都落在 CCTSDB 和 TT100K 的混合训练上。混用前一定要检查两类数据集的类别 ID 是否对齐常见翻车点是 TT100K 里禁止鸣喇叭和限速标志在 CCTSDB 里被分到不同类不合并直接训练会导致模型对类别边界非常混乱。2.2 把 VOC 格式转成 YOLOV5 需要的 txt转换脚本与四个边界坑YOLOV5 的标注格式是每个 txt 文件一行内容为 class_id x_center y_center width height坐标全部是归一化后的小数中心点和宽高都除以图片宽高。VOC 格式则是左上角和右下角坐标且可能是像素值。所以转换逻辑并不复杂但有不少边界情况要处理。下面这段脚本是我在项目里用的精简版覆盖了单类别和多类别两种场景并且处理了坐标越界和图片读取失败。import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_dir, img_dir, out_dir, class_names): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(f[WARN] image not found: {img_path}) continue try: w int(root.find(size/width).text) h int(root.find(size/height).text) except AttributeError: w, h Image.open(img_path).size out_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) xmin float(obj.find(bndbox/xmin).text) ymin float(obj.find(bndbox/ymin).text) xmax float(obj.find(bndbox/xmax).text) ymax float(obj.find(bndbox/ymax).text) xmin, xmax max(xmin, 0), min(xmax, w) ymin, ymax max(ymin, 0), min(ymax, h) if xmax xmin or ymax ymin: print(f[WARN] invalid box in {xml_file}: {name}) continue x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(out_lines))这段脚本里有个容易被忽略的细节我优先从 size 节点读宽高读不到时再回退到 PIL 读图。VOC 标注里的 size 和图片真实尺寸不一致的情况在公开数据集中不少如果不做这个回退转换出来的归一化坐标全是错的模型训练时 loss 会异常低但推理出来的框全部偏移。第二个坑是 xmin、xmax 没有裁剪部分标注框会超出图片边界归一化后 width 大于 1YOLOV5 训练时会报 “assertion” 或者边界框损失发散。第三个坑是类别名不在 class_names 里时直接忽略而不是报错这样能避免单个脏标注拖垮整个训练流程。第四个坑是 txt 里如果没有标注对象YOLOV5 允许空文件训练但很多版本会跳过该图片导致实际训练样本变少。参数说明class_names 是类别列表顺序就是最终模型的类别 ID 顺序。这个列表要一直保持到后续 data.yaml 里不能中途调整顺序。xml_dir 和 img_dir 可以是同一目录也可以分开我这里分开传是为了兼容 CCTSDB 这类图片和标注分目录存放的数据集。2.3 划分 train/val 并生成 data.yaml毕设里最容易丢分的环节数据和标注整理好后接下来要做的不是立刻训练而是先把目录结构和配置文件安排好。YOLOV5 对 datasets 目录的默认约定是 images 和 labels 分开而 labels 里没有 jpg 文件。我习惯用下面的目录结构datasets/ tsign/ images/ train/ img_001.jpg val/ img_002.jpg labels/ train/ img_001.txt val/ img_002.txt这个结构的好处是后续做增量训练时图片和标注不会混在一起也不容易让 YOLOV5 的缓存机制误判文件类型。划分数据集时不要用 random.shuffle 之后按百分比硬切。交通标志数据集里往往有同一时间段连续拍摄的帧硬切会把同一场景的相似图片同时分进 train 和 val验证集指标虚高。我一般先按图片前缀分组再按组划分比如连续帧的视频帧全部落在同一边。接着是 data.yaml这是 YOLOV5 自定义训练的核心配置内容不多但每个字段都直接影响训练结果。path: C:/workspace/datasets/tsign train: images/train val: images/val nc: 3 names: 0: speed_limit 1: warning 2: stoppath 字段在 Windows 下建议写绝对路径且使用斜杠而不是反斜杠。很多同学在这里写相对路径结果训练时因为当前工作目录不同而导致找不到图片报错信息又不太直白最终浪费一整天排查。train 和 val 是相对于 path 的目录注意这里只写 images 下的子目录YOLOV5 会自动去同级 labels 目录找标注。nc 是类别总数names 下面每个类别的名称尽量用英文小写避免中文类别名在控制台或 tensorboard 里出现编码乱码。还有一个细节是训练前删除缓存文件。YOLOV5 第一次读取数据时会生成.npy 缓存如果后续你重新生成了标注 txt旧的缓存会把更新后的标注过滤掉导致模型怎么训练都看不到新数据。我每次整理完数据集后都会执行一次清理命令find datasets/tsign -name .npy -delete。这个动作看似多余但实际是交通标志这种多轮迭代项目的后悔药。3. 从零跑通 YOLOV5 训练交通标志环境、权重与超参3.1 Python 环境与 YOLOV5 源码准备版本锁定比什么都重要先交代一个背景YOLOV5 是近几年目标检测领域应用最广的开源项目之一网络结构、训练策略和后处理代码都是完整可用的开源实现。做毕设时不需要自己重写模型直接把源码下载到本地按 requirements 安装依赖。需要注意 Python 版本常见的合适范围是 3.8 到 3.10太新的 Python 版本会碰上 torch 不兼容的问题。git clone https://github.com/ultralytics/yolov5 cd yolov5 python -m venv venv venv/Scripts/activate pip install -r requirements.txt虚拟环境这步建议不要省。交通标志训练过程中要频繁试不同的数据集和超参数依赖冲突是常见翻车点。如果你之前装过其他深度学习框架直接用全局环境很容易出现 torch 版本不对或者 numpy 版本冲突。requirements.txt 里没有固定所有包的上限所以装完依赖后顺手看一眼 torch 和 CUDA 的匹配关系命令是 python -c import torch; print(torch.version, torch.cuda.is_available())。CPU 机器也能训练但交通标志的小目标比较多CPU 训练一个 100 轮的模型可能要十几个小时有条件还是用 GPU。Git 克隆的是源码但实际训练时会自动下载预训练权重 yolov5s.pt国内网络下可能很慢。常见做法是手动下载后放到 yolov5 根目录的 weights 文件夹。预训练权重的作用是提供 COCO 数据集上学到的特征提取能力训练交通标志时有了它模型收敛速度和最终精度都会明显更好。如果你完全从随机权重开始训练mAP 通常低 5 到 8 个点而且训练轮数要多一倍。3.2 训练命令与关键参数img、batch、hyp 的取舍数据和环境就绪后训练命令如下。这里我以 yolov5s 模型为例因为它速度精度均衡适合绝大多数毕设环境。python train.py \ --data data/tsign.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/tsign \ --name exp1这个命令里几个参数值得细说。--img 640 是输入图片分辨率交通标志本身是小目标在 640 分辨率下很多标志只有十几个像素。如果显存够用我建议把 img 改成 960 甚至 1280模型对小标志的召回率会显著提升缺点是训练时间和显存占用都会上升。batch 16 是显存不够时的折中值如果你的卡是 8G 显存batch 16 加 img 640 是安全的如果是 4G 显存就降到 8。epochs 100 是常见基线我实际跑下来交通标志数据量在 5000 张左右时70 轮以后 val mAP 就基本不再涨了100 轮只是为了留出足够的收敛窗口。隐藏参数里最值得动的是 hyp。YOLOV5 默认的超参数文件是 data/hyps/hyp.scratch-low.yaml里面包含学习率、数据增强强度、损失权重等几十项。在没有明显欠拟合或过拟合的情况下默认值就是最优解不要为了显得自己“调过参”去乱改。真正要动的是 fl_gamma这是 focal loss 的 gamma 参数专门应对正负样本不平衡。交通标志数据集中负样本背景占比极大我把 fl_gamma 从默认 0.0 调整到 1.5能让模型更关注那些难分类的小标志代价是训练速度稍慢。结合热门的“yolov5 超参数”玩法我还会在训练时加一条命令看超参数变化曲线方便判断模型是否已经收敛tensorboard --logdir runs/tsignTensorBoard 里重点看三个曲线Box Loss、Objectness Loss 和 mAP_0.5。如果训练到后段 Box Loss 还在明显下降说明数据集容量还可以容纳更高容量模型如果 mAP_0.5 在验证集上出现平台期甚至下滑说明过拟合已经开始了。3.3 训练结果与模型文件解读best.pt 和 last.pt 的差别YOLOV5 训练结束后runs/tsign/exp1/weights 目录下会生成 best.pt 和 last.pt 两个权重文件。best.pt 是验证集上 mAP 最高的模型last.pt 是最后一轮的模型。做毕设时使用 best.pt 是默认选择但有一个例外如果训练过程不稳定best.pt 可能是某次验证碰巧跳到高点的模型而 last.pt 反映的是稳定状态。判断方法是同时跑两遍验证集或者查看 results.csv 里 best_epoch 前后的 mAP 波动幅度。训练完成后生成的结果是一整套评估文件包括混淆矩阵 confusionMatrix.png、PR 曲线、F1 曲线和验证集图片。这个文件夹本身就是毕设展示素材。在答辩里你不需要展示源码细节而是用那几张 PR 曲线和混淆矩阵说明“我的模型在各种类别上的误检率是多少”。这也是很多高分毕设的共同特征不只是会跑训练命令而是能把评估结果讲成一个完整故事。混淆矩阵里的对角线数值如果低于 0.8就要回头检查该类别训练样本数量样本数少于 200 的类别基本别指望高召回率。4. 训练后模型的使用与交付从批量推理到导出部署4.1 用训练好的 best.pt 跑批量推理并落地结果模型训练完成后最常见的使用方式是直接调用 detect.py 对测试视频或图片做推理。命令如下python detect.py \ --weights runs/tsign/exp1/weights/best.pt \ --source test_video.mp4 \ --conf-thres 0.25 \ --save-txt \ --save-conf \ --project runs/detect \ --name tsign_test这里 --conf-thres 0.25 是置信度阈值交通标志的遮挡场景比较常见阈值设得太高会漏检。--save-txt 会把每个目标的类别、置信度、坐标写入 txt 文件这也是后续做检测精度评估的基础。--save-conf 让输出的 txt 里带上置信度方便你在统计结果时按不同阈值重算指标。如果不想每次推理都用命令行常见做法是把模型封装到一个 Python 推理脚本里这样后续做 web 展示或者刚需的 GUI 界面都方便。下面的脚本是精简版调用的是 torch.hub 的 YOLOV5 自定义模型加载接口输入一张图输出检测框和标签。import torch from PIL import Image model torch.hub.load(ultralytics/yolov5, custom, pathruns/tsign/exp1/weights/best.pt, force_reloadTrue) model.conf 0.25 model.iou 0.45 model.max_det 50 img Image.open(test_frames/frame_0012.jpg) results model(img, size640) results.print() results.show() boxes results.xyxy[0].numpy() for b in boxes: x1, y1, x2, y2, conf, cls b print(f{model.names[int(cls)]} {conf:.3f} f{int(x1)} {int(y1)} {int(x2)} {int(y2)})这段脚本里的 force_reloadTrue 在网络好的情况下每次都会重新下载依赖权重如果你已经本地训练好了模型建议把它改成 False避免每次重启进程都等半小时。model.conf 和 model.iou 分别是置信度阈值和 NMS 的 IoU 阈值做交通标志识别时我通常把 IoU 设成 0.45太低会保留大量重叠框太高会把两个离得近的话标志合并成一个框。model.max_det 限制单张图最多输出 50 个目标主要是防止路牌文字被模型误识别时输出一堆无意义的小框。4.2 导出 ONNX 并跑一遍 C/移动端的部署思路毕设里如果提到部署ONNX 导出几乎是必讲项。YOLOV5 的 export.py 会把 PyTorch 权重转成 ONNX 格式命令如下python export.py \ --weights runs/tsign/exp1/weights/best.pt \ --include onnx \ --opset 12opset 12 是老少咸宜的版本既不会被新版运行库拒绝功能上也够用。导出完成后会生成 best.onnx你可以直接用 ONNX Runtime 在 Python 里验证一致性对比 PyTorch 和 ONNX 的推理结果。两者的输出可能有小数点后第四位的误差这是正常的因为 ONNX 的 grid 采样和上采样算子在不同实现里会有细微差异不用零误差对齐。移动端的部署可以用 ONNX 转 NCNN 或 TensorRT但在毕设答辩中你只需要说清楚“ONNX 作为中间表示可以进一步转换到边缘设备”就够了。真正在项目里跑更建议先把 ONNX Runtime 的推理搞清楚毕竟 Android 或嵌入式设备的部署工程量是另一门课不是这个毕设的核心。我自己的习惯是导出完 ONNX 后再跑一次同样的测试图片看两边的目标框数量是否一致。如果数量不一致排查方向集中在输入图像的预处理上YOLOV5 训练时用的是 RGB 图detect.py 里会做 letterbox 填充推理脚本里也必须做同样的 letterbox。忘掉这一步是 ONNX 推理结果在边角位置目标检测全部失败的头号原因。5. 交通标志训练避坑指南5 条能救命的血泪经验5.1 训练集 mAP 高验证集 mAP 崩盘现象是训练 50 轮后 train mAP 已经到 0.96但 val mAP 还停留在 0.7 附近。原因是数据集划分不随机很多连续帧图片被同时分进了训练集和验证集导致模型实际上见过验证集图片。解决方法是按场景分组划分或者直接使用 YOLOV5 自带的 val 逻辑保持图片与视频帧来源一致时要先去重。如果数据量足够更稳妥的做法是按时间段划分前 70% 时间段的帧做训练后 30% 做验证。5.2 训练 loss 正常但检测框全部偏移现象是 loss 曲线漂亮mAP 也不低但可视化结果里框的位置偏左上或右下。原因大多是归一化坐标计算错误。检查手段是随便打开一个 label txt确认 x_center 是否在 0 到 1 之间width 是否与图片尺寸比例相符。我见过一个典型错误把 xmin 和 ymin 当作中心点直接归一化模型永远学不到正确位置。解决方法是回到 2.2 的转换脚本对照 VOC 原始标注重新检查三张图的中间结果。5.3 小目标交通标志漏检率居高不下现象是限速标志在远处只有 20 像素宽时模型完全无感知。原因是输入分辨率太低且模型下采样倍数让特征图上的小目标信息丢失。YOLOV5 是 P3 到 P5 的三层特征图20 像素的目标在 P3 层只占 10 个像素左右特征细节很少。解决方法是把 --img 从 640 升到 960 甚至 1280如果显存不够就减小 batch。另一个可选方案是使用 SAHI 这类切片推理工具把大图切成小块分别检测但推理时间会成倍增加。5.4 Windows 下训练中断报错 FileNotFoundError现象是明明图片路径存在但训练到一半报找不到 labels 缓存文件。原因是 YOLOV5 在 datasets 路径中写入 cache 文件时的路径拼接与 Windows 反斜杠冲突尤其是 path 字段写了相对路径时更容易触发。解决方法是把 data.yaml 里的 path 写成绝对路径且全部用正斜杠并在训练前删掉 labels 目录里所有 *.cache 文件。另外一个相关性非常高的问题是中文用户名Windows 用户名带中文时PyTorch 的 DataLoader 偶发崩溃临时方案是给项目换一个纯英文路径。5.5 夜间场景训练完白天效果还行夜间直接失效现象是验证集 mAP 0.85晚上拿手机一拍标志旁边没有杆子的反光就检测不到。原因是训练集以白天为主模型学到的是亮度和色调分布的浅层特征而不是标志的形状和颜色特征。解决方法是加入夜间增强策略最基本的是把训练集里的部分图片做亮度降低、对比度提高、色温偏蓝处理再复制进训练目录。更为系统化的方式是修改 hyp 文件里的 HSV 增强参数让模型在训练过程中主动适应低照度。这一步放到下一章细说。6. 让模型更进一步夜间场景调优和评估报告的做法交通标志识别这个题目的评分点里“夜间场景泛化”是非常容易拉开差距的一处。公开数据集很少包含夜间图片自采数据的夜间图也更难收集。我一般会在训练时做三步增强第一步在 hyp 文件里把 hsv_h 从 0.015 调高到 0.02hsv_s 从 0.7 调高到 0.9第二步用数据扩充脚本把 20% 的训练图做随机亮度扰动模拟夜间路灯和车灯的明暗交替第三步在验证集里专门留出 100 张夜间图片用来观察模型的掉点幅度。这三步做下来夜间 mAP 通常能从 0.4 涨到 0.6 左右。另一个被很多同学忽略的实用技巧是给模型加测试时增强。YOLOV5 的 detect.py 自带 --augment 参数推理时会做多尺度预测对远处小标志的漏检有不错改善。代价是单帧推理时间从 0.02 秒涨到 0.1 秒左右对静态图片评估完全可接受但如果要做实时视频这个方案就不太适合。最后说说评估报告怎么写。答辩时评委最认可的不是“我调参调得多辛苦”而是一张张可复现的曲线。我会把 runs 目录下的 results.csv 导出成图表把混淆矩阵和 PR 曲线粘贴到论文实验章节再单独写一节“失败案例分析”挑 3 到 5 张模型漏检的图分析是遮挡问题、小目标问题还是夜间光照问题。坦白说主动展示失败案例反而能让答辩更有说服力因为这说明你真的跑过足够多的真实数据而不是只在公开数据集上刷了点好看的数字。做这个项目到今天我最大的教训是永远不要跳过数据集清洗直接开训。YOLOV5 本身太成熟了网络结构几乎不需要改动数据才是这个毕业设计的胜负手。希望这篇笔记能帮你少走我走过的弯路。本文还有配套的精品资源点击获取
返回列表