简介:基于YOLOv5的道路交通标志识别程序,是一套包含完整源码与配套数据集的毕业设计项目,面向需要完成课程设计、期末大作业或毕业设计的高校学生,可用于交通标志检测、识别与模型训练等场景。资源包共266个文件,压缩后约423MB,其中以Python脚本(py)、数据集图片(jpg/jpeg)、模型配置文件(yaml)及预训练权重(pt)为核心,另有训练日志、结果统计csv、环境配置Dockerfile等辅助文件,便于快速搭建运行环境并进行推理测试。目前已有439人学习下载,属于导师认可的高分项目,源码附带详细注释,从数据准备、模型训练到交通标志识别均有清晰实现逻辑,适合新手参考与二次开发。使用者下载解压后按说明部署,即可复用完整识别流程,也可基于现有数据集与权重调参优化,为课程答辩或项目展示提供扎实支撑。
1. yolov5道路交通标志识别:源码加数据集拿过来就能跑的毕业设计组合
十个选交通标志识别的毕业设计,有八个不是倒在算法理解上,而是倒在跑通之前的环境、数据格式和训练命令上。基于yolov5的道路交通标志识别程序源码+数据集,恰好把最磨人的两件事——yolov5源码组织和标志标注数据集——预先准备好了,你拿到手要做的是把环境配好、把格式理顺、把训练跑起来。这份资源解决的是“环境怎么配、数据怎么喂、训练怎么写”这类落地问题,适合需要交期末大作业或毕设的学生,也适合想快速验证yolov5在标志检测上效果的从业者。下面我按“先跑通、再调参、后验证”的顺序拆,全程都是可以直接抄的步骤。
2. 环境配置与数据组织:Python、CUDA、数据集yaml一次配到位
交通标志识别项目第一步永远是环境。yolov5对版本敏感,Python、PyTorch、CUDA三者版本不匹配,轻则警告重则直接崩溃。先clone官方yolov5仓库,然后用conda建一个独立环境,不要图省事往base环境里塞依赖。
2.1 环境搭配:CUDA、PyTorch与Python三方版本别硬来
我一般会先确认显卡驱动支持哪一版CUDA,再决定torch版本。下面这套组合跑yolov5训练和推理都稳定:
git clone https://github.com/ultralytics/yolov5.git cd yolov5 conda create -n traffic python=3.8 -y conda activate traffic pip install -r requirements.txt逻辑说明:yolov5仓库自带requirements.txt,里面锁定了torch、torchvision、numpy、opencv-python等核心依赖的版本范围。用conda创建独立环境的核心目的是隔离——交通标志数据集和别的项目混在一起时,最常翻车的就是numpy版本被另一个包改掉,然后训练时loss开始出诡异数值。
参数说明:python=3.8是兼容性最稳的版本,3.9和3.10也能跑,但遇到老版本onnx或某些opencv轮子时会踩编译坑。requirements.txt默认安装的是CPU版或GPU版取决于你机器上是否预装了CUDA版torch;如果显卡驱动没问题,建议先手动装GPU版再装requirements,避免pip自动给你装成CPU版导致训练速度慢几十倍。
2.2 数据目录结构:images与labels一一对应是底线
yolov5的数据组织方式很直接:图片和标注文件分两个顶层目录,下面按train和val再分一层。这种文件系统级结构和COCO的annotations.json完全不同。coco2017数据集结构里那种“一个json管所有标注”的方式,在yolov5里不适用,它认的是同名txt文件。
datasets/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── img_002.jpg │ └── val/ │ └── img_003.jpg └── labels/ ├── train/ │ ├── img_001.txt │ └── img_002.txt └── val/ └── img_003.txt每条标注记录一行,格式是:类别id + 中心点x坐标 + 中心点y坐标 + 框宽 + 框高。这五个值之间用空格分开。
这里最关键的边界条件是:图片文件名(含前缀部分)必须和txt文件名完全一致。比如img_001.jpg对应的标注文件必须是img_001.txt,后缀不同都找不到对应标签。yolov5在训练时如果发现某张图没有同名txt,会直接跳过并打Warning,很多mAP异常就是文件对不上造成的。
2.3 数据集yaml:train、val路径与类别名最容易写错
数据组织好后,需要在yolov5目录下建一个yaml文件描述数据集信息。这个文件里的路径写错了,训练时会出现AssertionError: Train set not found之类的报错。
# traffic.yaml train: /home/user/datasets/images/train val: /home/user/datasets/images/val nc: 3 names: 0: warning 1: prohibitory 2: mandatory逻辑说明:train和val指向的是images目录而不是labels目录,yolov5会自动去同级labels目录找对应txt。如果你把这两行错写成labels路径,训练时一样会报找不到图片。
参数说明:nc是类别总数,和names列表长度必须一致。names的顺序极其重要——这个顺序决定了类别id的编号,比如warning在字典里排第0位,那转换标注时warning类别id必须写成0,写成1就会造成训练时类别错乱,这是血泪经验。
提示:路径能用绝对路径就别用相对路径,尤其当你习惯在别的目录下执行train.py时,相对路径很容易指向错误位置。
3. 标注格式转换:VOC XML转YOLO txt的脚本与关键参数
很多道路交通标志公开数据集的下载形态是VOC格式,每张图配一个XML文件。yolov5不认XML,认txt,所以第一步是把标注格式全部转过来。这一步做不对,后面训练、评估全是白费。
3.1 YOLO的txt标注格式:四个浮点数背后是归一化坐标
VOC的XML里记录的是左上角xmin, ymin和右下角xmax, ymax的绝对像素值。YOLO格式要的是归一化之后的中心点坐标和宽高。换算公式是:
cx = (xmin + xmax) / 2 / image_width cy = (ymin + ymax) / 2 / image_height w = (xmax - xmin) / image_width h = (ymax - ymin) / image_height所有的值都除以图片宽高,结果是0到1之间的小数。这样不管原始图片是1080p还是720p,标注都不受影响。这组值如果大于1或小于0,yolov5训练时大概率不报错,但推理出来框的位置全是歪的。
3.2 VOC转YOLO脚本:XML读取与坐标换算一条龙
下面这个脚本是我常用的转换模板,直接用xml.etree.ElementTree解析XML,不需要额外安装库:
import xml.etree.ElementTree as ET import os class_mapping = {'warning': 0, 'prohibitory': 1, 'mandatory': 2} def xml_to_yolo(xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_mapping: continue box = obj.find('bndbox') xmin = int(box.find('xmin').text) ymin = int(box.find('ymin').text) xmax = int(box.find('xmax').text) ymax = int(box.find('ymax').text) cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_mapping[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': xml_dir = 'datasets/annotations' out_dir = 'datasets/labels' os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): xml_path = os.path.join(xml_dir, xml_file) out_path = os.path.join(out_dir, xml_file.replace('.xml', '.txt')) xml_to_yolo(xml_path, out_path)逻辑说明:脚本按顺序做三件事——读取图片宽高、遍历每个object、计算归一化坐标写入txt。class_mapping字典是脚本的大脑,它决定每个标志类别对应数字几。
参数说明:class_mapping里的键必须和XML文件里<name>标签的字符串完全一致,区分大小写,比如XML里写的是Warning而你字典里是warning,这条标注就会被直接跳过。输出格式{:.6f}保留6位小数,训练精度完全够用,不要用整数去存0.5这种值。
3.3 训练集与验证集划分:文件名错位是mAP异常的元凶
标注转完后,需要把图片和对应的txt按比例拆成train和val两个集合。最容易犯的错是:图片复制过去了,txt没跟着复制,导致验证集图片全部“无标签”。
import os import random random.seed(42) img_src = 'datasets/images/all' lbl_src = 'datasets/labels/all' ratio = 0.8 imgs = [f for f in os.listdir(img_src) if f.lower().endswith(('.jpg', '.png'))] random.shuffle(imgs) split_idx = int(len(imgs) * ratio) for i, img in enumerate(imgs): txt = os.path.splitext(img)[0] + '.txt' if i < split_idx: os.rename(os.path.join(img_src, img), os.path.join('datasets/images/train', img)) os.rename(os.path.join(lbl_src, txt), os.path.join('datasets/labels/train', txt)) else: os.rename(os.path.join(img_src, img), os.path.join('datasets/images/val', img)) os.rename(os.path.join(lbl_src, txt), os.path.join('datasets/labels/val', txt))逻辑说明:用rename而不是copy,避免同一份数据占两倍磁盘空间。random.seed(42)固定随机数种子,保证每次运行划分结果完全一样,方便复现训练结果。
参数说明:ratio=0.8表示80%进训练集、20%进验证集。如果你的数据集特别小,比如只有几百张图,建议把比例调到0.9,验证集只留10%,否则训练数据不足会导致模型欠拟合。另外,注意同一条路上连续帧的图片不要因为shuffle而一边一半——如果数据集是视频抽帧来的,建议按时间顺序截断划分,而不是纯随机,不然验证集和测试集过度相似,评估结果虚高。
4. 训练命令与超参数:让模型在道路交通标志上收敛的关键设置
环境和数据都就绪后,训练就是一个调参过程。yolov5训练自己的数据集,核心是把train.py的参数吃透。
4.1 train.py命令拆解:六个关键参数一次讲清
python train.py \ --data traffic.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640逻辑说明:yolov5的训练入口就一个train.py脚本,--data指向你建的traffic.yaml,--weights是预训练权重。选yolov5s.pt而不是从头训练,是因为预训练权重在COCO上已经学好了通用特征,交通标志这类小目标能直接受益于底层卷积特征,收敛快很多。
参数说明:--data是数据集配置文件的路径,强烈建议写绝对路径,因为在别的目录下执行train.py时,相对路径很可能解析错误。--img 640表示训练输入分辨率,yolov5会自适应调整。交通标志普遍是中远距离拍摄的小目标,分辨率太低(比如416)会明显掉精度,但如果显存有压力,降级到480也能接受。--epochs从100起步,如果到80轮左右mAP已经不再涨,可以提前停掉。
4.2 超参数怎么调:模型尺寸、batch-size与学习率的取舍
模型尺寸直接决定速度和精度的平衡点,这也是搜yolov5超参数时最常看到的内容。yolov5提供了n/s/m/l/x五个档位,交通标志识别我一般选s或m。
| 模型 | 参数量 | 推理速度 | 适用场景 |
|---|---|---|---|
| yolov5s | 约7.2M | 快 | 数据量小、显存有限、要赶进度 |
| yolov5m | 约21.2M | 中等 | 数据量大、追求更高准确率 |
batch-size设置逻辑很简单:显存够就大,显存不够就小。16是一个很稳的起点;如果训练时报CUDA out of memory,降到8再试,不要直接开--cache把整个数据集塞进显存——那种操作很容易让显存直接打满。学习率方面,yolov5默认lr0=0.01,这个值在交通标志这种小目标数据集上经常偏大,表现为loss震荡不下降。我一般会在自定义数据集上直接加--lr0 0.001,换取更稳定的收敛曲线。
优化器默认是SGD,数据量大的时候SGD泛化能力更强。但如果你的标志数据集只有几百张,用SGD会训练得很慢,换成Adam收敛更快。
4.3 训练日志里的信号:results.csv告诉你的三个信息
训练开始后,输出目录runs/train/exp下会实时更新文件,其中最关键的是results.csv。你可以用Excel或pandas打开,里面每一列对应一个指标。
epochs, train/box_loss, train/cls_loss, train/obj_loss, metrics/precision, metrics/recall, metrics/mAP50, metrics/mAP50-95看训练是否正常,主要盯三个信号:box_loss应该持续下降;mAP50应该在20轮之后开始明显爬升;precision和recall同时走高说明模型没走偏。如果mAP50已经连续30轮不涨,直接Ctrl+C停掉训练,用最后一次保存的权重,别浪费时间去等满100轮。
注意:训练结束后的
best.pt和last.pt含义完全不同。best.pt是验证集上mAP最高那一轮的权重,last.pt是最后一轮的权重。部署和答辩演示永远用best.pt,不要用last.pt。
5. 避坑手册:交通标志训练里最常见的五类翻车现场
以下五条踩坑记录都来自实际跑yolov5交通标志项目的经验,覆盖训练、数据、推理三个阶段。每一条都是现象、原因、解决三个环节对起来的,遇到问题直接对号入座。
5.1 训练阶段的翻车:loss为nan与显存OOM
踩坑1:训练从一开始loss就显示nan,权重文件完全不可用。
现象:第一个epoch跑完后,终端输出box_loss: nan,之后每一轮都是nan。
原因:学习率太大,模型参数直接发散;或者标注文件中出现了非法值,比如某个txt里有一行是1 nan nan nan nan。yolov5不会主动校验数据内容,非法值会一路传播到loss。
解决:先清洗标注数据,执行grep -r "nan\|Inf" datasets/labels/检查所有txt;然后训练时显式加--lr0 0.001,把学习率降一个量级再试。
踩坑2:训练中途报CUDA out of memory,进程直接崩溃。
现象:前几个epoch正常,跑到第10轮左右突然OOM退出。
原因:batch-size和img组合起来超过了显存上限,或者开启了--cache把整个数据集加载进显存,叠加之下显存爆炸。
解决:把batch-size从16降到8,如果还炸就降img到480。顺便检查系统里有没有其他进程占用显存,比如另一个没关的jupyter内核,nvidia-smi一眼就能看出来。
5.2 数据与标注的翻车:mAP为0和类别id错位
踩坑3:训练loss曲线很漂亮,但是验证集mAP一直是0。
现象:train-loss稳步下降,可metrics/mAP50始终是0,precision和recall全是0。
原因:验证集图片没有对应txt标签。划分数据集时图片移动了,标签文件没跟着移动。yolov5会对没有标注文件的图片跳过训练和验证,所以验证集里全是没标签的图,指标自然全是0。
解决:检查datasets/labels/val目录,看里面有没有和验证集图片同名的txt。如果没有,执行划分脚本时把图片和txt配对移动;如果有但名字对不上,检查图片扩展名是不是jpg和png混用导致txt文件名错位。
踩坑4:所有交通标志都被预测成同一个类别。
现象:检测框位置基本正确,但不管警告标志还是禁令标志,全部输出为同一个类别。
原因:VOC转YOLO时class id写错了。很多公开数据集标注的类别字段直接就是warning、prohibitory这类字符串,但你转txt时忘了做映射,把所有类别都写成了同一个数字,或者没有按yaml里names的顺序去排。
解决:回头检查生成的txt第一列数字,看看分布是否覆盖了0到nc-1的全部范围。确保class_mapping里每个类别对应唯一的数字,且这个数字和traffic.yaml里names的索引完全一致。
5.3 推理阶段的翻车:conf-thres把目标全滤掉了
踩坑5:用best.pt跑detect.py,输出目录空空的,一张图一个框都没画。
现象:推理命令执行成功,输出文件生成了,但图片上没有任何检测框,也没有txt结果。
原因:conf-thres默认值是0.25,也就是说系统只保留置信度高于25%的框。交通标志这类小目标在推理时置信度普遍偏低,0.25这个门槛会把大多数目标直接滤掉。
解决:显式降低置信度阈值:
python detect.py --weights runs/train/exp/weights/best.pt --source data/test --conf-thres 0.1降到0.1之后,漏检情况会明显改善。如果0.1还是有很多目标没框出来,那要怀疑是训练数据本身不足,而不是阈值问题,回去看训练集里该类标志的样本数量。
6. 推理验证与模型导出:用detect.py做冒烟测试,再提前导出ONNX和TorchScript
训练收敛后,第一步不是急着写报告,而是先用detect.py跑一遍冒烟测试,确认权重加载正常、检测结果可用。这步能帮你避开“答辩前才发现模型根本跑不出框”这种尴尬。
python detect.py \ --weights runs/train/exp/weights/best.pt \ --source data/test \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt \ --save-conf逐项说明:--source支持图片目录、单张图片、视频文件和摄像头ID,抽帧图片最常用;--conf-thres刚才讲过,0.4在测试集上算一个均衡值,如果漏检就降到0.1;--iou-thres控制NMS的去重力度,交通标志重叠少,保持0.45即可;--save-txt把每张图的检测结果按yolov5格式输出成txt,答辩时统计各类别的检测数量很方便;--save-conf在txt里附带置信度数值,可以用来画PR曲线。
detect.py跑通后,下一步建议顺手导出ONNX和TorchScript。这个动作不做,你的模型就被锁死在Python+PyTorch环境里。导出后,后续接TensorRT或OpenVINO部署,或者用一个简单的C++推理程序,都有退路。
python export.py --weights runs/train/exp/weights/best.pt --include onnx torchscript导出完,在runs/train/exp/weights目录下会出现best.onnx和best.torchscript两个文件。TorchScript可以直接用torch.jit.load加载,不需要训练依赖;ONNX则可以跑在各种边缘设备上。
有一回我赶进度,训练完直接拿best.pt去跑摄像头实况,结果在光线比较暗的地方什么都检测不到,第一反应是模型废了。后来才发现,不是模型的问题,是默认阈值0.25把低置信度目标全滤掉了。从那以后,我每次推理都会先拿一张训练集图片做冒烟测试——确认权重普通加载、框和类别对得上,再去调阈值、换场景。这个习惯帮我省掉了无数次无效debug,希望也能帮到你。
本文还有配套的精品资源,点击获取