简介:针对交通标志识别任务,提供一套基于YOLOV5的完整工程,包含6105张已标注数据集、训练与推理源码及模型权重文件。代码经本地编译调试,配置好环境即可直接运行,尤其适合计算机专业毕业设计、课程设计或深度学习实战练习。资源共1568个文件,以Python脚本为主,辅以模型配置yaml、预训练权重pt、标注图片jpg及训练日志等,压缩包大小282.71MB,目录结构清晰便于按需调用。目前已有208人学习下载。项目中包含训练过程记录与多个阶段的权重文件,方便对比不同迭代效果;配套的类别配置和数据结构说明,可帮助快速完成迁移学习与结果可视化,为论文实验或答辩演示提供充分支撑。
1. 一套 YOLOv5 交通标志识别项目:6105 张标注数据与可运行代码的真实边界
搞计算机视觉的毕业设计,绕不开 YOLOv5 交通标志识别这个经典题目;交通标志从整车目标检测里切出来,十来个类别,却把目标检测最磨人的小目标问题全暴露了。这套带 6105 张已标注数据集的 YOLOv5 交通标志识别项目,正好是针对这个场景准备的完整代码包,环境配好就能直接训练。它适合两类人:一类是毕设选了图像识别方向、需要快速拿到可复现、可答辩的工程代码;另一类是刚开始学 CV、想借一套真实标注数据把“数据集 → 训练 → 检测”整条链跑通的人。项目基于 YOLOv5 的 yolov5s 主干,配合交通标志专用 data.yaml 和预先处理好的 images/labels 目录结构。
当然,“配置完环境就能跑”不代表你什么都可以不看,数据格式、训练参数、检测阈值这三处看不明白,模型照样训不出来。下面按环境、数据、训练、避坑、落地五步拆开。
2. YOLOv5 环境配置:从 conda 建环境到 PyTorch 选型,按这个顺序装不会错
对这套项目来说,环境问题的优先级高于代码问题。绝大多数“训练报错”其实都发生在环境层面:torch 装成了 CPU 版、依赖包版本被默认行为改了、pycocotools 编译失败。所以我会把环境配置放在第一步,而且只讲一个能稳定复现的顺序。
2.1 版本选型:为什么是 Python 3.8 与 PyTorch 1.8
YOLOv5 官方对 Python 3.7 到 3.9 都支持,但我一般会推荐 3.8。原因很实际:numpy 和 pycocotools 在 3.8 下遇到 wheel 兼容问题的概率最低,网上大量现成的报错记录也都基于这个版本。PyTorch 方面,YOLOv5 的老版本代码里大量默认参数是按 1.8/1.9 调的,这个区间里 1.8.0 的 cu111 版属于最不出错的一档,和 1.9.0 相比没有明显的检测精度差异。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Python | 3.8 | 与依赖包兼容性最好,用 conda 直接创建 |
| PyTorch | 1.8.0 / 1.9.0 | cu111 构建,对应 CUDA 11.1 驱动 |
| torchvision | 0.9.0 / 0.10.0 | 必须与 torch 同源安装,不能混版本 |
| 显卡驱动的 CUDA 支持 | 11.1 及以上 | 驱动向下兼容,不需要单独装 CUDA Toolkit |
| 显卡显存 | 4GB 以上 | 没有 N 卡也能跑,只是训练时间会拉长很多 |
这里有一个容易误解的点:装了 cu111 版的 PyTorch,不等于你必须手动安装 CUDA 11.1。只要显卡驱动支持相应版本,PyTorch 会把运行时需要的那部分 CUDA 组件一起带过来。如果你的机器是纯 CPU 环境,也可以把 torch 换成 CPU 版,训练流程能正常跑,只是速度慢到没法接受,通常不建议拿 CPU 版本训练这套数据集。
2.2 conda 建环境与安装顺序
我的操作顺序从来都是固定的:先建虚拟环境,再装 PyTorch,最后装项目依赖。顺序乱掉的典型后果是:先装 requirements.txt、再补 torch,numpy 等核心库会被来回替换版本,最后 import 时弹出莫名其妙的冲突。
conda create -n yolov5 python=3.8 -y conda activate yolov5创建名为 yolov5 的 Python 3.8 虚拟环境,这一步其实就是把系统 Python 和项目 Python 隔离。很多初学者图省事直接在 base 环境里装,等第二个项目要用 PyTorch 2.x 时,两个项目的 numpy、OpenCV 版本就会互相打架,到时候清理起来比重新建环境还费劲。
激活环境后,命令行前缀会变成(yolov5)。如果前缀没出现,说明 activate 没生效,Windows 上常见原因是没执行过 conda init,重启终端再试一次即可。
pip install torch==1.8.0 torchvision==0.9.0 --extra-index-url https://download.pytorch.org/whl/cu111这条命令里的--extra-index-url是指定 PyTorch 官方 wheel 源。如果不加,pip 默认从 PyPI 拉取,很可能装成 CPU 版;一旦装成 CPU 版,torch.cuda.is_available()永远是 False,训练代码不报错但性能惨不忍睹。装完这一步,建议顺手打印一下版本确认两个包来自同一个构建:
python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__)"如果打印出的 torchvision 是 CPU 版本,而 torch 是 cu111,就是版本对不齐,重新把两个包写在一起安装一遍即可。这个问题我在帮别人排查环境时见过很多次,基本都是分开装导致的。
然后回到项目根目录,装 YOLOv5 本身的依赖:
cd yolov5-master pip install -r requirements.txtrequirements.txt 里已经写好了 YOLOv5 需要的包和版本区间,不用自己逐个挑。中途如果有包因为网络超时失败,重跑同一条命令即可,pip 会跳过已安装项,只补装剩余部分。
2.3 验证环境是否真正就绪
环境装完不能直接开训,先做一个 30 秒的连通性验证。第一确认 torch 能看到 GPU,第二确认 YOLOv5 能正常加载预训练权重并完成一次推理。
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"期望输出是1.8.0和True。如果你看到 False,不要往下走,先解决 GPU 不可用问题,否则后面训练一个 epoch 的时间会让人崩溃。接下来跑一次官方示例图:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg --img 640第一次运行会自动下载 yolov5s.pt 权重。跑完后打开runs/detect/exp目录下的输出图,能看到行人、公交车都有检测框,说明环境、权重、源码三者已经打通。如果这一步直接报错,优先检查权重是否下载完整,再回过头看 requirements 是否装干净。
注意:detect 每次运行都会生成新的 exp 序号目录,不会覆盖旧结果,适合保留多组对比输出。
3. 数据集格式与划分:理解 6105 张图里的 images/labels 再重拆
拿到这套项目后,第一件事不是看训练代码,而是打开数据集目录,把 images 和 labels 的对应关系读明白。标注好的数据不是拿到手就能直接用,文件名匹配、坐标格式、类别顺序,任何一个地方出错,训练时都不会立刻报错,只会让 mAP 悄悄掉下去。
3.1 先对照目录结构
完整项目里的数据集通常是这样组织的:
datasets/traffic/ ├── images/ │ ├── train/ │ │ ├── img_00001.jpg │ │ └── ... │ └── val/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_00001.txt │ │ └── ... │ └── val/ │ └── ... └── traffic.yaml这里有一个常见误区:图片在 images/train 下,对应的标签必须在 labels/train 下,并且文件名相同、后缀不同。如果标签放错了子目录,train.py 不会报“找不到标签”,而是静默跳过这张图片,等价于你丢了一部分训练样本。
3.2 标签文件里的五个数字
打开任意一个 txt 标注文件,内容长这样:
0 0.300781 0.254883 0.044047 0.042969 1 0.682424 0.925781 0.032422 0.037109 2 0.507812 0.710938 0.019531 0.021484每一行对应一个目标框。第一个数字是类别 id,从 0 开始;后面四个值依次是 x_center、y_center、width、height。四个数值都用图片宽高做了归一化,范围在 0 到 1 之间。交通标志在这套数据集里普遍偏小,所以框的宽度通常只有 0.02 到 0.05,这是小目标检测的典型特征,也决定了后面训练时不要轻易把输入分辨率降到 320。
第一列类别 id 必须与 data.yaml 中 names 列表的索引对齐。如果 txt 里写的是 3,而 names 第三位是 crosswalk,模型训练时就把这个框当成行人横道标志处理。类别不对齐不会报错,只会让验证阶段的混淆矩阵彻底乱掉,这个细节值得多花两分钟核对。
3.3 先做一次数据体检
即使是标注好的数据集,也未必百分百干净。缺标签、空标签、坐标越界是最常见的三类问题,训练前先用脚本把它们找出来:
import os from pathlib import Path img_dir = Path("datasets/traffic/images/train") label_dir = Path("datasets/traffic/labels/train") total = 0 missing = 0 empty = 0 invalid = 0 for img_path in sorted(img_dir.glob("*.jpg")): total += 1 label_path = label_dir / (img_path.stem + ".txt") if not label_path.exists(): missing += 1 continue lines = label_path.read_text().strip().splitlines() if len(lines) == 0: empty += 1 continue for line in lines: parts = line.split() # 必须正好五个字段:class x y w h if len(parts) != 5: invalid += 1 break try: x, y, w, h = float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if x < 0 or y < 0 or w > 1 or h > 1 or w > 0.9: invalid += 1 break except ValueError: invalid += 1 break print(f"total={total}, missing={missing}, empty={empty}, invalid={invalid}")脚本逻辑很直接:图片存在但同名 txt 不存在,记为 missing;txt 存在但没有任何行,记为 empty;坐标格式不对或归一化范围明显越界的,记为 invalid。如果三个指标都是 0,可以放心进入训练环节;如果有脏数据,先清理再继续,不要让异常样本干扰 loss 计算。
3.4 重新划分 train/val 的脚本
如果拿到的项目里数据集合并成了一个总目录,就需要自己拆分成 train 和 val。常用的划分比例是 9:1,用固定随机种子保证每次划分结果一致:
import os import random from pathlib import Path root = Path("datasets/traffic") images = sorted((root / "images").glob("*.jpg")) labels = sorted((root / "labels").glob("*.txt")) assert len(images) == len(labels), "图片与标签数量不一致" random.seed(42) random.shuffle(images) split = int(len(images) * 0.9) train_imgs = images[:split] val_imgs = images[split:] for split_name, img_list in [("train", train_imgs), ("val", val_imgs)]: img_dir = root / "images" / split_name label_dir = root / "labels" / split_name img_dir.mkdir(parents=True, exist_ok=True) label_dir.mkdir(parents=True, exist_ok=True) for img in img_list: label = root / "labels" / (img.stem + ".txt") if not label.exists(): continue os.replace(str(img), str(img_dir / img.name)) os.replace(str(label), str(label_dir / label.name))我之所以先断言图片和标签数量一致再打乱,是防止划分后的子集里出现大量匹配不上的文件。用os.replace而不是 copy,是为避免同一张图同时出现在两个目录中被重复读取。固定random.seed(42)的做法是常见的可复现手段,下次重新执行脚本能得到完全相同的划分。
还有一点容易被忽略:如果某个类别的目标很少,9:1 随机划分可能把这类样本全部分到训练集,验证集里完全看不到。保险做法是统计每个类别在 txt 中的出现次数,保证 val 至少包含每个类别一例;如果类别不均衡严重,就得考虑按类别分层抽样。
4. 跑通训练:data.yaml、超参数、预训练权重一次讲清
训练阶段要做的事其实就三件:确认配置文件、选对预训练权重、给出合理的训练参数。这套项目已经自带了交通标志专用的 data.yaml,但路径和类别名还是要自己核对一遍,直接开训经常会在第 3 个 epoch 后才发现数据没对上。
4.1 data.yaml 的四个关键字段
train: datasets/traffic/images/train val: datasets/traffic/images/val nc: 9 names: ['speed_limit', 'no_entry', 'stop', 'crosswalk', 'no_parking', 'do_not_enter', 'right_only', 'left_only', 'yield']train 和 val 的路径是相对 YOLOv5 项目根目录写的,不是相对 datasets 目录,也不是绝对路径。如果你把项目文件夹换过位置,绝对路径就会直接失效。names 里的类别名建议全部用英文,中文类别名在部分系统上会出显示乱码,不影响训练但影响日志排查。nc 必须与 names 列表长度一致,不一致时 train.py 会直接抛异常,这个还算友好。
如果你不确定类别顺序,打开一个 labels 下的 txt,看第一列最大值是几,再去和 data.yaml 的 names 对齐。我自己碰上过标注文件从 0 到 8、而 data.yaml 里写了 10 个类别的情况,训练不报错,但 mAP 始终上不去,浪费了一整天。
4.2 训练命令与参数选择
推荐起点是 yolov5s 权重,它在精度和速度之间最平衡。交通标志背景相对简单,不需要一上来就用 yolov5l 这种大模型。
python train.py \ --data datasets/traffic.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 120 \ --patience 20 \ --cache ram \ --project runs/train \ --name traffic_v1| 参数 | 值 | 作用 |
|---|---|---|
| data | datasets/traffic.yaml | 数据集配置入口 |
| weights | yolov5s.pt | 预训练权重,推荐从 s 开始 |
| img | 640 | 训练输入边长,交通标志小目标建议保持 640 |
| batch | 16 | 显存占用,4G 显卡改 8 |
| epochs | 120 | 训练轮数 |
| patience | 20 | 连续 20 轮验证无提升就早停 |
| cache | ram | 把图片缓存进内存,减少磁盘 IO |
参数里最值得解释的是 img。交通标志在整张图中的像素占比很小,如果为了省显存把 img 降到 320,小目标的特征会在下采样过程中被稀释,最终表现为漏检。显存不够时优先降 batch,而不是降分辨率。batch 减半后收敛速度会变慢,可以顺带把 epochs 加到 150:
python train.py --data datasets/traffic.yaml --weights yolov5s.pt --img 640 --batch 8 --epochs 150如果你的显卡显存只有 4G,batch 8 能跑通,但训练时间会明显变长;再不够就换--cache disk,用磁盘缓存换内存占用。
4.3 训练中断恢复
训练过程中会因为断电、系统休眠、显存溢出被 kill,这不代表前功尽弃。YOLOv5 每次保存 last.pt,恢复训练用一条命令:
python train.py --resume runs/train/traffic_v1/weights/last.pt它会自动读取训练状态,包括当前的 epoch、优化器参数、学习率调度器位置。我一般习惯把 project 和 name 固定下来,这样中途断掉时能准确找到 last.pt 路径,不用翻来翻去。
4.4 训练日志怎么看
训练开始后每轮终端会打印 Precision、Recall、mAP_0.5 等指标。主要看 val/mAP_0.5 有没有随 epoch 稳步上升。如果前 30 轮 mAP 一直低于 0.1,先别急着怀疑模型,排查顺序是:data.yaml 的 nc 和 names 是否对齐、数据集目录路径是否正确、labels 里是否有空文件。这三个问题不解决,换任何模型都白搭。
5. 避坑:训练和验证阶段我最常处理的四个翻车现场
这部分每一条都是真实遇到过的,现象、原因、解决方式一起写,省得你一个个去搜错误码。
5.1 现象:训练一启动就报 FileNotFoundError
训练开始后约 3 秒报FileNotFoundError: datasets/traffic/images/train。原因基本是 data.yaml 里用了绝对路径,而整个项目文件夹在下载后移动过位置;或者是 Linux 下路径大小写不一致,Windows 不区分大小写但在 Ubuntu 上会直接失败。
解决方式是先把 data.yaml 里的路径改成相对 YOLOv5 项目根目录的写法,然后确认目录真实存在:
ls datasets/traffic/images/train没有输出就是路径写错了,先手动把数据集目录放到项目根目录下,再改 yaml。
5.2 现象:训练 loss 变成 NaN
train 日志里 loss 在前几个 epoch 直接变成 nan,之后 mAP 一直为 0。常见原因有三个:一是显存不够时进程被系统杀掉但日志表现为 NaN;二是 learning rate 超出模型承受范围;三是 labels 里存在空 txt 或全 0 坐标,导致 loss 计算出现异常。
解决的顺序很明确:先把空标签和脏数据清掉,再检查学习率。default hyp 文件里的 lr0 是 0.01,如果自定义过 hyp 可以换成更保守的配置:
python train.py --data datasets/traffic.yaml --weights yolov5s.pt --img 640 --batch 8 --epochs 150 --hyp data/hyps/hyp.scratch-low.yamlhyp.scratch-low.yaml里的学习率更低,适合数据集样本质量参差的情况。如果换成 low 之后 loss 恢复正常,说明你之前用的学习率偏高了。
5.3 现象:验证集 mAP 很高,但实际图片里小标志全部漏检
验证 mAP 能到 0.85,可拿一张随手拍的照片跑 detect,远处的限速标志完全没有框。原因在 detect.py 的默认参数:--conf-thres默认 0.25,小目标在低分辨率下置信度天然偏低,超过 0.25 的很少;--iou-thres默认 0.45 也可能让密集小框互相抑制。
解决方式是检测阶段降低置信度阈值:
python detect.py --weights runs/train/traffic_v1/weights/best.pt --source test.jpg --conf-thres 0.05 --iou-thres 0.45 --img 6400.05 会把大量低置信度框也放出来,如果误检太多再逐步回调到 0.1。部署场景里可以做二次筛选,而不是先提高阈值把真目标滤掉。
5.4 现象:OpenCV 读不进中文路径的图片
Windows 下图片或视频文件名带中文时,detect.py 会被 cv2.imread 卡住,报类似imread_的底层错误。原因是 OpenCV 的 C++ 接口对中文路径支持不完善,不是 YOLOv5 本身的问题。
解决方式是用 numpy 读入再转成 BGR 格式,绕开 cv2.imread 的路径解析:
import cv2 import numpy as np data = np.fromfile("测试图片.jpg", dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR)这是一段通用小工具,我习惯把它封装成read_image.py,之后所有需要读图的地方都走这个包装函数。遇到批量文件带中文名时,能省掉大量手动改名的功夫。
6. 模型真要落地还得会这招:摄像头推理与 ONNX 导出
训练完的模型如果只停留在验证集指标上,离“能演示”还差一步。交通标志识别的实际演示通常有两种场景:用摄像头实时识别,或对一段视频批量标注。YOLOv5 的 detect.py 把这两个入口都留好了,关键是参数怎么配。
python detect.py --weights runs/train/traffic_v1/weights/best.pt --source 0 --conf-thres 0.4 --img 640--source 0表示读取本机摄像头。直播场景和测试场景的取舍不一样,摄像头上可以把阈值调高到 0.4 来抑制误检,因为画面是连续帧,偶尔漏一帧没关系。如果是视频文件,把--source 0换成视频路径即可,对长视频建议配合--vid-stride跳帧处理。
如果要在没有 PyTorch 的机器上部署,或者想进一步提速,可以把权重导出成 ONNX:
python export.py --weights runs/train/traffic_v1/weights/best.pt --include onnx --opset 11导出后用 ONNX Runtime 推理几乎不需要修改数据的预处理逻辑,但推理速度和部署体积都会明显改善。三种方式的取舍可以简单对比如下:
| 方式 | 依赖 | 速度 | 适用场景 |
|---|---|---|---|
| detect.py 原生推理 | PyTorch | 最慢 | 调试模型 |
| ONNX Runtime | onnxruntime | 中等 | 跨平台部署 |
| TensorRT | N 卡 + TensorRT | 最快 | 边缘设备 |
我的习惯是每次训练完先跑一遍测试集的图片,再把 conf-thres 调到 0.05 验证真实场景视频,确认没有系统性漏检后,才把部署阈值调回到 0.4。从那以后我每次拿到这类 YOLOv5 项目,都会强制把“环境、数据、阈值”这三个环节走一遍,再谈优化。希望帮到你。
本文还有配套的精品资源,点击获取