简介面向毕业设计、期末大作业与课程设计的YOLOv5交通标志识别检测项目完整覆盖交通标志数据集、源码与训练好的模型权重代码内附详细注释新手也能按注释理解并顺利运行。压缩包共266个文件、约423.31MB以Python脚本、yaml配置、pt权重和jpg/png图像为主同时包含xml标注、csv训练记录、shell脚本及Dockerfile等可支撑数据准备、模型训练、验证与部署全流程目录结构清晰便于按模块快速检索。项目界面简洁、操作直观内置完整训练与推理流程训练评估结果一目了然并带有日志与结果记录方便复盘指标变化。代码经过严格调试下载后配置好环境即可直接使用适合作为毕业设计或期末大作业的高分完整方案。已有112人学习下载需要快速落地目标检测项目的开发者可直接参考使用。1. 为什么YOLOv5做交通标志识别是毕业设计里性价比最高的检测选题交通标志识别检测是目标检测里的经典场景目标尺寸小、类别语义明确、背景干扰相对可控但光照、遮挡、视角变化又让它“看起来简单、做好很难”。正因为这种特性它既不会因为任务太简单导致论文无话可写也不会因为难度过高让大作业烂尾非常适合作为毕业设计或课程大作业的主线。而模型选型上YOLOv5是当前平衡得最好的选择代码仓库稳定、网上可查的教程和踩坑记录最多、超参数可调空间足够大并且对显卡要求不高低配机器也能训练。下面这份笔记是我实际带项目跑通的一条完整路径——数据整理、环境配置、训练调参、问题排查、演示系统每一步都有可以直接复制的命令和代码也会点出那些最容易让人反复折腾的细节。2. 交通标志数据集选哪个CCTSDB、TT100K与YOLO格式转换2.1 数据集选型CCTSDB干净、TT100K类别多GTSDB只适合做辅助很多同学第一步就卡在数据上不知道用哪个数据集下载下来又发现格式对不上。先给结论如果你只求快速把整套流程跑通并拿到一个演示效果不错的模型选 CCTSDB如果你想在答辩时多展示“多类别识别”工作量选 TT100K但只挑其中十几个高频类别来训GTSDB 不要作为主力训练集。CCTSDB 是国内高校维护的交通标志数据集标注格式是 Pascal VOC 的 xml类别分为 prohibitory、mandatory、warning、other 四个。它最大的优点是类别少、标注相对一致四分类任务不需要做复杂的类别平衡一般显卡训练几十个 epoch 就能出效果非常适合在毕业设计前期建立信心。我通常把 CCTSDB 作为默认选项先把整套训练和评估流程跑通后续还有时间再考虑扩类别。TT100K 是清华提供的交通标志数据集标注文件是 JSON类别总数非常多。直接拿全部类别训练是多数人翻车的根源——上百个类别中大量类别只有几十个框模型根本学不过来。我的做法是筛选出样本量足够多的十几个类别比如限速 60、限速 80、禁止驶入、禁止停车、人行横道等重新映射编号。这样既有“多类别”的展示亮点又不会让少数类拉低整体 mAP。GTSDB 是国外经典交通标志数据集标志设计和国内差异明显直接混入训练会引入标注语义冲突。它唯一的用途是在测试阶段做一次域泛化验证或者作为额外的负样本来源。2.2 把VOC的xml转成YOLO的txt一个直接可跑的转换脚本YOLOv5 不认 xml 也不认 json它只认和图片同名的 txt 标注文件每行内容为类别编号、归一化中心点 x、归一化中心点 y、归一化宽、归一化高。CCTSDB 拿到手后第一件事就是把 xml 批量转成这种格式。import xml.etree.ElementTree as ET from pathlib import Path # 类别映射必须和后续 data yaml 里的 names 保持一致 CLASS_MAP {prohibitory: 0, mandatory: 1, warning: 2, other: 3} def convert_one_xml(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) w x2 - x1 h y2 - y1 if w 2 or h 2: continue cx x1 w / 2.0 cy y1 h / 2.0 lines.append( f{CLASS_MAP[name]} f{cx / img_w:.6f} {cy / img_h:.6f} f{w / img_w:.6f} {h / img_h:.6f}\n ) return lines def batch_convert(xml_dir, out_dir): Path(out_dir).mkdir(parentsTrue, exist_okTrue) for xml_file in sorted(Path(xml_dir).glob(*.xml)): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines convert_one_xml(xml_file, img_w, img_h) if not lines: continue out_file Path(out_dir) / (xml_file.stem .txt) out_file.write_text(.join(lines), encodingutf-8) if __name__ __main__: batch_convert(CCTSDB/Annotations, CCTSDB/labels)这段脚本的关键点有两个。一是坐标归一化必须用 xml 里的size/width和size/height如果某些 xml 缺失 size 字段就改用cv2.imread读取对应图片的宽高不能拍脑袋用固定值否则框会整体偏移。二是过滤掉了宽或高小于 2 像素的标注这类标注要么是标注手误要么是边缘截断的残影留进训练集会干扰损失计算。转换完成后打开一个 txt 看一眼确认每个坐标值都在 0 到 1 之间类别编号范围正确。这一步只要花两分钟能避免训练时出现“no labels in xxx.jpg”这类让人摸不着头脑的警告。2.3 划分train/val与类别分布验证漏一步后面就要返工标注转好之后下一个常规做法是先看一眼类别分布再划分训练集和验证集。YOLOv5 要求的目录结构是CCTSDB/ images/ train/ val/ labels/ train/ val/划分时不建议用纯随机 shuffle 后按比例切因为同一批图片往往来自相似的拍摄路段纯随机很容易把某类样本集中到 train 或 val造成验证结果忽高忽低。更稳妥的做法是直接按文件做分层随机抽样保证每个类别在 train 和 val 里的比例接近。import random from pathlib import Path random.seed(42) train_ratio 0.8 image_files list(Path(CCTSDB/images).glob(*.jpg)) random.shuffle(image_files) train_files image_files[:int(len(image_files) * train_ratio)] valid_files image_files[int(len(image_files) * train_ratio):] for split, files in [(train, train_files), (val, valid_files)]: img_dir Path(fCCTSDB/images/{split}) lbl_dir Path(fCCTSDB/labels/{split}) img_dir.mkdir(parentsTrue, exist_okTrue) lbl_dir.mkdir(parentsTrue, exist_okTrue) for img_path in files: lbl_path Path(CCTSDB/labels) / (img_path.stem .txt) if lbl_path.exists(): img_path.rename(img_dir / img_path.name) lbl_path.rename(lbl_dir / lbl_path.name)这个脚本把图片按 8:2 划到 train 和 val 两个目录同时移动对应的标注文件。运行完后我建议用简单命令统计一下每个类别的框数量做到心里有数。比如训练集里 prohibitory 有 3000 个框warning 只有 600 个框那训练时就要考虑是否对 warning 做复制粘贴增强或者调高该类别的 loss 权重。数据准备阶段还有一个容易被忽略的点验证集图片尽量不要和训练集来自同一个视频片段的不同帧。交通标志数据集中常有连拍帧相邻帧几乎一模一样这种情况会高估模型性能。如果发现验证集里大量图片和训练集高度相似正确的做法是手动把连续帧归为同一组整组划分。3. yolov5 训练自己的数据集环境配置、训练命令与五个必调参数3.1 conda环境配置Python、CUDA与torch的版本搭配环境配置是整套流程里翻车率最高的环节超过一半的时间会耗在“装好之后 import torch 报错”上。我的习惯是用 conda 创建独立环境保持 Python 版本干净。conda create -n yolov5 python3.9 -y conda activate yolov5 pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install -r yolov5/requirements.txt这里有两个容易踩坑的点。一是--index-url指定了 cu117表示 torch 是针对 CUDA 11.7 编译的如果你的显卡驱动是更新的 CUDA 12.x通常也能兼容运行但如果驱动版本太低则可能出现CUDA driver version is insufficient。装之前先执行nvidia-smi看右上角的 CUDA Version再决定用 cu117 还是 cu118。二是 YOLOv5 的 requirements.txt 里包含 opencv-python 和 matplotlib 等依赖个别版本组合下opencv-python会存在兼容问题如果后续推理时 cv2 报错就单独执行pip install opencv-python4.8.0.76降级处理。没有 NVIDIA 显卡的机器也可以装 CPU 版pip install torch torchvision --index-url https://download.pytorch.org/whl/cpuCPU 训练一张 640×640 的图大约比 GPU 慢几十倍不是完全不能跑但建议只用来做推理演示和最后的验证。3.2 最小训练命令用预训练权重先跑通流程环境配好后先把数据 yaml 准备好。在 YOLOv5 仓库根目录下新建一个cctsdb.yamlpath: ../CCTSDB train: images/train val: images/val nc: 4 names: [prohibitory, mandatory, warning, other]然后执行训练命令cd yolov5 python train.py \ --data cctsdb.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --hyp hyp.scratch-low.yaml \ --cache第一训练如果不能直接取下预训练权重可前往官方仓库的 releases 页面手动下载。--weights yolov5s.pt表示在 COCO 预训练权重基础上微调这比随机初始化从头训练效果稳定得多收敛也快。交通标志虽然和 COCO 类别不重叠但 COCO 上学到的边缘纹理特征对迁移有益。如果完全不用预训练权重相同的 epoch 下 mAP 通常会低 15 到 20 个点这是血泪经验不要省这一步。--cache参数会把图片一次性加载进 RAM训练时省去反复读取磁盘的等待。如果内存只有 16G建议取消--cache或者改用--cache ram的中间方案。第一次训练会先花几分钟建立缓存文件之后启动速度会快很多。训练开始后每完成一个 epoch 会在终端打印训练 loss、验证 mAP、各类别 AP 等指标。此时不急着调参先让训练跑完观察曲线趋势。3.3 影响效果的五个必调参数imgsz、batch、epochs、hyp与workersYOLOv5 的训练入口参数很多但对交通标志这个具体任务真正值得花心思调的只有五个其他保持默认即可。第一个是--imgsz。交通标志尺寸小640 的输入下一个 32×32 像素的标志占输入图的比例已经很小模型下采样五次后特征基本消失。如果显卡显存允许建议直接上 960 或 1280。把 imgsz 从 640 提到 1280小目标的 recall 往往能提升二到三成代价是显存占用和训练时间翻倍。显存不够时优先保证 imgsz 不降把 batch 调小更实惠。第二个是--batch-size。它和显存大小直接相关常规做法是显存 8G 用 1616G 用 32。batch 越小梯度波动越大训练后期 loss 曲线容易震荡。但交通标志数据集规模不大batch 16 和 32 的最终 mAP 差距很小不必过分追求大 batch。第三个是--epochs。交通标志任务通常不需要像 COCO 那样训练几百轮数据集小且目标结构简单80 到 120 轮一般就收敛了。判断依据是训练日志里的验证 mAP 曲线如果最后 20 轮 mAP 没有明显上升说明已经收敛提前停止即可。第四个是--hyp指定的超参数文件。YOLOv5 自带的hyp.scratch-low.yaml是增强强度较低的配置适合小目标任务因为过强的 mosaic 和旋转会让小标志变形严重。如果你用的是高分辨率大图也可以试hyp.scratch-med.yaml。第五个是--workers。Windows 系统下 workers 默认值大于 0 时dataloader 在迭代过程中容易报DataLoader worker (pid(s)) exited unexpectedly这属于老生常谈的问题。解决方法很简单训练命令里显式加--workers 0代价是数据加载变慢但对总体训练时长影响有限。3.4 显存只有4G甚至没有GPU低配机器上的三个降级方案毕业设计环境五花八门有人只有一台 4G 显存的笔记本。这种配置跑默认的 yolov5s 640 batch 16 几乎必然显存溢出。处理优先级是这样的先换--weights yolov5n.pt把模型从 s 降到 n参数量差约三倍再把--imgsz降到 480最后把 batch 降到 4。python train.py \ --data cctsdb.yaml \ --weights yolov5n.pt \ --epochs 100 \ --batch-size 4 \ --imgsz 480 \ --workers 0如果连 NVIDIA 显卡都没有只能走 CPU 训练路线。这时建议把图片尺寸再降到 320并且接受训练时间以小时甚至天为单位。我见过同学用 CPU 跑 100 轮前后花了两天多最终模型 mAP 约 0.65虽然不高但作为流程演示也够用。另一种更省事的方案是先去免费算力平台跑平台一般提供了常见的深度学习镜像把数据集打包上传后直接训练再下载 best.pt 回本地做推理演示。这样既绕开本地硬件限制还能顺便把训练曲线截图留作答辩材料。4. 训练与推理高频问题排查五个翻车现场的排查记录4.1 现象loss正常下降但val mAP卡在0.3上不去训练日志里损失从 0.05 一路降到了 0.02但验证集 mAP0.5 始终在 0.3 附近这类现象在校验集划分不当时最常见。原因往往不是模型问题而是验证集和训练集分布严重不一致或者验证集里大量困难样本夜晚、逆光、密集遮挡占比过高导致模型在训练集上拟合很好却无法泛化到验证集。解决步骤先统计训练集和验证集每类的框数量比例正常情况下两者的类别分布应接近。如果验证集的 warning 类占了 60% 而训练集只占 20%说明是划分问题回到第 2.3 节重新分层抽样。其次查看 results.png 里的 F1 曲线如果 F1 最高点出现在置信度 0.1 附近说明模型本身有判别能力只是阈值设太高。这种情况需要在推理阶段把 conf_thres 降到 0.15 而不是重新训练。4.2 现象CUDA out of memorybatch调小到4仍然报错显存溢出常见原因不只是 batch 太大--cache也会占额外显存因为缓存图片到内存后dataloader 的 pin_memory 机制会锁页。如果 batch 已经很小还报错先看是不是开了 cache把它关掉再试。另一个原因是显存碎片化训练前在脚本开头加一句torch.cuda.empty_cache()能释放一部分残留缓存。最后检查是否同时开了多个进程占用显存比如上一个训练任务还没结束就启动新任务nvidia-smi看一下进程列表就知道。4.3 现象大标志检测正常小标志全部漏检这是交通标志任务里最典型的痛点。YOLOv5 对输入图做了五次下采样最终特征图只有输入尺寸的三十二分之一。一个 640 输入下 20×20 像素的标志在最大特征图上只占不到一个格子模型很难在锚点上产生有效预测。解决思路有两条一是训练和推理时统一把 imgsz 提到 1280让 20×20 的目标在 1280 输入下变成 40×40特征明显增强二是对测试图片做切块推理把大图切成若干 640 瓦片每个瓦片独立检测后再把框映射回原图坐标。后者效果往往更好代价是推理时间成倍增加适合对单张图片做精细分析做实时视频流不划算。4.4 现象推理时检测框抖动同一张图两次结果不一致训练好的模型在单张图上推理应该是确定性的但如果你开着--augment做测试时增强模型会对输入做随机翻转和缩放结果自然每次都不一样。推理脚本里不要加 augment。视频流里的抖动通常另有原因标志在画面边缘时只有部分进入画面模型预测框在完整和残缺之间摇摆置信度也忽高忽低。常规做法是加一个简单的帧间平滑把最近 N 帧的同类框坐标做加权平均或者直接换用带跟踪的推理脚本对每个检测框分配 ID。毕业设计演示时为了画面稳定我一般会把置信度阈值从 0.25 提到 0.4抖动会明显减少。4.5 现象导出ONNX后用OpenCV DNN推理结果和PyTorch对不上这类模型部署场景里最常见的偏差来源是预处理不一致。PyTorch 推理时YOLOv5 会把图片缩放到模型输入尺寸并进行 BGR 到 RGB 的通道转换和像素归一化而 OpenCV DNN 读 ONNX 模型时默认直接按 Blob 方式输入通道顺序仍为 BGR归一化范围也可能不同。结果就是置信度偏低框的位置轻微偏移。解决的常规做法是在 OpenCV 推理前手动做同样的预处理把图片从 BGR 转成 RGB、除以 255 归一化、按 letterbox 方式缩放。每次改完代码后固定拿同一张图分别跑 PyTorch 和 OpenCV对比框坐标差异是否在几个像素内用这个办法对齐后再去做整个部署流程。5. 把模型做成能演示的成品推理脚本、界面与答辩前的验证5.1 一条命令跑通单图推理并换成中文标签训练完成后best.pt 位于runs/train/exp/weights/best.pt。先用 YOLOv5 自带的 detect.py 验证效果python detect.py \ --weights runs/train/exp/weights/best.pt \ --source test_road.jpg \ --conf-thres 0.30 \ --iou-thres 0.45 \ --imgsz 640 \ --data cctsdb.yaml输出的标注图会保存到runs/detect/exp/。如果要显示中文类别名需要在推理脚本里把类名映射替换掉。YOLOv5 默认从 data yaml 读取 names所以直接在 cctsdb.yaml 里把 names 改为[禁令, 指令, 警告, 其他]即可。注意 OpenCV 的 putText 不支持直接绘制中文需要先用 PIL 把中文画到图上再转回 numpy 数组或者干脆直接用英文类别名答辩时可口头解释对应关系这个不是技术扣分点。5.2 三十分钟搭一个可视化界面Streamlit 是毕设演示的稳妥选择毕业设计展示环节导师希望看到不是黑框终端而是能上传图片、显示检测结果的界面。我推荐 Streamlit因为它不需要写前端代码部署也简单。import streamlit as st import numpy as np from PIL import Image import torch model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) model.conf 0.30 model.iou 0.45 st.title(交通标志识别系统) uploaded st.file_uploader(请上传道路图片, type[jpg, jpeg, png]) if uploaded is not None: img Image.open(uploaded).convert(RGB) results model(img, size640) rendered results.render()[0] st.image(rendered, caption识别结果, use_column_widthTrue) st.write(results.pandas().xyxy[0])运行方式是在命令行执行streamlit run app.py浏览器自动打开页面。这段代码只有十几行但能把“上传图片→模型推理→展示结果→导出表格”整条链路演示清楚。唯一需要注意的是torch.hub.load首次运行会联网拉取代码答辩前先在本地跑一次生成缓存避免现场断网时失败。另外如果演示的机器没有 GPU推理时把尺寸固定为 320 或 480CPU 也能保持每秒两帧以上。5.3 答辩前跑一次全量验证别拿几张效果图碰运气只看一张测试图的检测效果很容易产生幸存者偏差。答辩前三天我建议直接写一个脚本遍历整个验证集输出每个类别的 recall 和整体 mAP这样既能在答辩时展示“模型具备量化评估”也能提前发现哪些类别效果差针对性地更换演示图片。import torch from pathlib import Path model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) model.conf 0.25 model.iou 0.45 val_dir Path(CCTSDB/images/val) class_stats {} for img_path in sorted(val_dir.glob(*.jpg)): results model(str(img_path), size640) pred results.pandas().xyxy[0] # 读取对应标注的类别统计真正例、漏检、误检 label_path Path(CCTSDB/labels/val) / (img_path.stem .txt) gt_classes [] if label_path.exists(): with open(label_path) as f: for line in f: gt_classes.append(int(line.split()[0])) pred_classes pred[class].tolist() # 此处省略逐类统计的细节核心是记录 TP / FP / FN这段代码的统计逻辑可以按自己需求实现得精细但重点不在代码本身而在于提前暴露问题。如果发现 warning 类的 recall 只有 0.3那答辩现场就别选 warning 类的测试图。这不是作弊而是合理选择演示样本毕竟你要展示的是系统的整体能力而不是某个短板的尴尬。6. 答辩前值得做的两个小动作切图推理和阈值微调模型训练完后通常还有一到两周时间才答辩这两个优化方向性价比最高不需要重新训练只改推理策略就能明显提升演示效果。第一个是切图推理。交通标志在手机随手拍或路测视频截图里往往只占很小一块整图送入模型容易漏检。用一个简单的滑动窗口把图片裁成 640 的瓦片每个瓦片推理后再把框坐标加回原图偏移量最后对重叠区域做 NMS 合并。这样处理一张 1920×1080 的图小标志的召回率通常能提升三分之一以上缺点是推理时间变长本地 CPU 可能要两三秒。所以只对演示图片使用不要做成实时视频流方案。第二个是重新校准置信度阈值。YOLOv5 默认 conf-thres 是 0.25但这只对均衡数据集合适。交通标志任务中如果验证集上误检率偏高把阈值从 0.25 提到 0.4 往往能过滤掉大量背景误报同时召回损失很小。反过来如果漏检明显阈值降到 0.15 也能救回一部分置信度低但真实存在的框。在答辩前我始终会做的一件事是遍历验证集按 0.05 间隔扫一遍阈值找一个 F1 最高的点作为演示固定参数。这个 F1 曲线图还能直接截到答辩 PPT 里说明你做了模型评估属于加分项。这套流程总结下来就是数据选 CCTSDB 或 TT100K 子集按 YOLO 格式整理训练时用预训练权重重点调 imgsz遇到问题先查数据分布和预处理不要急着换模型架构最后把阈值和推理方式调整到最适合演示的状态。按这个顺序走下来毕业设计的完整交付物——数据集、代码、模型、界面、评估曲线——都能在预期时间内落位。希望帮到你。本文还有配套的精品资源点击获取