尧图精选

YOLOv5道路交通标志识别实战:从数据集构建到训练部署全流程

🕒 发布时间:2026/10/2 10:04:40 📁 来源:尧图网络
简介这份资源是一套基于YOLOv5的道路交通标志识别完整项目主要面向毕业设计、期末大作业及课程设计等场景也适合对目标检测感兴趣的初学者参考。项目包含Python源码与配套数据集代码中附有注释从数据处理、模型训练到推理识别均有覆盖方便本地部署后直接运行。资源包共266个文件压缩后大小约423MB文件类型涵盖py核心代码、pt模型权重、yaml配置文件、jpg/jpeg图像样本、sh脚本、csv训练记录及Dockerfile等结构与用途较为清晰。已有439人浏览学习。内容包含训练日志、results.csv指标变化、Docker部署文件与部分配置文件便于了解训练过程和环境搭建方式。对希望快速完成高分成品项目的学生而言这套资源提供了一条可复用的完整实现路径可在短时间内上手并用于演示或答辩。1. 从毕业设计题目到可运行系统yolov5道路交通标志识别要交付的不只是源码打开资源站搜“基于yolov5道路交通标志识别程序源码数据集毕业设计”下载包里基本都是同一套东西yolov5源码、一堆标志图片、标注文件、一份说明文档。很多同学以为解压就能跑结果卡在环境配置、数据集转换和训练参数上。这个题目真正考验的是把yolov5源码、道路交通标志数据集、训练脚本和演示界面串成一条可复现的工程链路。道路交通标志识别和普通目标检测最大的区别在于标志属于典型小目标一个1920×1080画面里标志可能只有40×40像素再加上夜间反光、遮挡和类别不均衡很容易出现“训练mAP高、现场识别差”的情况。这个方案适合正在做毕业设计、需要快速产出可运行系统的学生也适合想验证yolov5训练自己数据集全流程的工程师。2. 建数据集与目录结构从COCO2017到yolov5训练自己的数据集2.1 不要直接拿COCO2017当训练集交通标志数据集怎么选很多教程一上来就让人下载COCO2017理由是yolov5官方预训练权重在COCO上训练过。但COCO2017数据集结构是面向80类通用目标的train2017里有118K张图、val2017有5K张图标注存放在annotations/instances_train2017.json等文件里。这套结构里确实有traffic_sign类别但实例数量少而且标志类别只占很小一部分。直接用COCO训练交通标志识别等于让模型在大量无关背景里找少量小物体收敛慢、误检也多。更合适的做法是换用专用数据集。常见的有TT100K清华大学-腾讯公开的交通标志数据集、CCTSDB长沙理工的交通标志数据集、GTSRB德国交通标志数据集。TT100K标注了街景图片里的标志实例类别多但部分类别样本极少CCTSDB是国内场景类别相对集中GTSRB虽然主要用于分类但也可以转成检测数据来扩充不同光照下的样本。下载时尽量去学校主页或论文主页找原始包资源站打包经常出现图片被压缩、标注缺失、类别名被改成数字的问题。我一般建议毕业设计选一个数据集做主体不要贪多。比如用TT100K里的停止、限速、禁止通行等5到10个常用类别再补几十张自己手机拍的路口照片。这样标注量控制在1000张左右训练时间和答辩讲稿都好写。如果直接用官方COCO做迁移反而要处理类不平衡和标注过滤工作量会大很多。2.2 把VOC/JSON标注转成YOLO格式转换脚本与参数说明yolov5训练自己的数据集要求每张图片对应一个同名txt文件每行格式是class_id x_center y_center width height坐标是归一化到0到1的浮点数x_center和y_center是框中心点width和height是框的宽高。这个格式和VOC的xmin、ymin、xmax、ymax不一样和COCO的bbox也不一样。下面这段代码把VOC XML标注转成YOLO txtimport os import glob import xml.etree.ElementTree as ET # 类别顺序必须和后续data.yaml里的names保持一致 CLASSES [spd_30, spd_60, stop, crosswalk] def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) with open(os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)), w) as f: for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 防止坐标越界标注框偶尔会超出图片边界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w min(max(w, 0), 1) h min(max(h, 0), 1) f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) os.makedirs(labels, exist_okTrue) xml_files glob.glob(xml/*.xml) for xml_file in xml_files: xml_to_yolo(xml_file, labels) print(converted, len(xml_files), files)这段代码的核心逻辑是先读图片真实宽高再把VOC的绝对坐标换算成相对坐标。CLASSES列表的顺序决定每个类别的数字id这个顺序在后续data.yaml里不能再变。代码里做了0到1的钳制但遇到x2小于x1这种标注错误时钳制没用需要额外报错。需要注意VOC XML里size子节点必须有宽高有些公开数据集漏写了size节点会导致img_w拿不到值。转换前最好先统计一下XML里有多少个文件缺size字段缺了就直接删掉对应图片避免训练到一半报错。2.3 目录结构、数据划分和标签自检yolov5约定的数据集目录结构一般长这样datasets/ ├── traffic_sign/ │ ├── images/ │ │ ├── train/ │ │ │ ├── img_001.jpg │ │ │ └── ... │ │ └── val/ │ │ └── ... │ ├── labels/ │ │ ├── train/ │ │ │ ├── img_001.txt │ │ │ └── ... │ │ └── val/ │ │ └── ... │ └── data.yaml图片和标签要严格同名只是扩展名不同。yolov5依赖文件名匹配图片和标签如果从网上下载的数据集文件被重命名过最好用脚本统一改成img_00001.jpg这类定长编号避免文件名里有空格或中文。下面是数据划分脚本把all目录里的图片和标签按15%随机分成验证集import os import random import shutil random.seed(42) src_images datasets/traffic_sign/images/all src_labels datasets/traffic_sign/labels/all train_img_dir datasets/traffic_sign/images/train train_lbl_dir datasets/traffic_sign/labels/train val_img_dir datasets/traffic_sign/images/val val_lbl_dir datasets/traffic_sign/labels/val for d in [train_img_dir, train_lbl_dir, val_img_dir, val_lbl_dir]: os.makedirs(d, exist_okTrue) img_files [f for f in os.listdir(src_images) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(img_files) val_count max(int(len(img_files) * 0.15), 1) val_files set(img_files[:val_count]) for img_name in img_files: stem os.path.splitext(img_name)[0] label_src os.path.join(src_labels, stem .txt) if not os.path.exists(label_src): print(fwarning: missing label for {img_name}) continue max_len max(len(img_files) - val_count, 0) # 简单进度提示 if img_name in val_files: shutil.move(os.path.join(src_images, img_name), os.path.join(val_img_dir, img_name)) shutil.move(label_src, os.path.join(val_lbl_dir, stem .txt)) else: shutil.move(os.path.join(src_images, img_name), os.path.join(train_img_dir, img_name)) shutil.move(label_src, os.path.join(train_lbl_dir, stem .txt)) print(fmoved {img_name}, remaining {len(img_files) - 1}) # 这里只作示意脚本固定随机种子保证复现。划分前先检查标签是否存在避免训练时出现“找不到标签”的报错。移动完成后在datasets/traffic_sign/下建data.yamlpath: datasets/traffic_sign train: images/train val: images/val nc: 4 names: [spd_30, spd_60, stop, crosswalk]path可以写相对路径也可以写绝对路径如果在Windows和Linux之间反复切换建议直接用绝对路径反斜杠问题会少很多。nc必须和names列表长度一致names里的顺序必须和转换脚本里CLASSES的顺序一致。这里如果顺序乱了训练不会报错但预测出来的类别id会对应到错误的标识。COCO2017数据集结构使用的是instances JSON类别id存在categories字段里和yolov5的txt格式不同。如果硬要从COCO转需要过滤出交通标志类别并重新映射id过程不复杂但容易出错。对毕业设计来说直接用交通标志专用数据集再转YOLO格式效率高得多也方便自己控制类别。3. 用yolov5源码跑通训练超参数、模型选型与训练命令3.1 yolov5环境配置与源码获取yolov5环境配置最常见的坑是torch版本和CUDA不匹配。官方requirements.txt里给的是最新torch如果你的显卡驱动比较旧pip装出来的torch可能不识别CUDA。我一般先用conda建独立环境conda create -n yolo python3.9 -y conda activate yolo pip install torch1.13.1cu117 torchvision0.14.1cu117 --index-url https://download.pytorch.org/whl/cu117 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt然后验证torch能不能正常调用显卡python -c import torch; print(torch.cuda.is_available())如果是True再跑python train.py --help确认yolov5源码能正常加载。如果环境里没有git直接去GitHub下载zip包也是一样的后面所有命令都在解压后的yolov5目录下执行。需要注意yolov5版本不同训练命令里参数名略有差异比如老版本用--cfg指定模型配置新版本改成在yaml里写模型配置。写代码时尽量锁定一个版本毕业设计不需要追新。3.2 修改数据配置与模型配置数据配置在data.yaml里已经写好了接下来是模型配置。yolov5默认提供yolov5n、yolov5s、yolov5m、yolov5l、yolov5x五档模型后缀越小模型越轻量。交通标志类别少yolov5s够用如果推理设备是CPU或老显卡选yolov5n。复制一份模型配置把nc改成自己的类别数cp models/yolov5s.yaml models/yolov5s_traffic.yaml然后修改models/yolov5s_traffic.yaml核心部分大概是nc: 4 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10,13, 16,30, 33,23] - [30,61, 62,45, 59,119] - [116,90, 156,198, 373,326]nc必须和data.yaml的nc一致。depth_multiple和width_multiple控制网络层数和通道数这是yolov5s与yolov5m的区别毕业设计先不动。anchors是预设的anchor尺寸默认从COCO聚类得到。训练自己数据集时yolov5会尝试重新聚类如果想手动干预训练命令里可以加--noautoanchor但除非你已经看过聚类结果否则不建议一开始就关。3.3 超参数与训练参数怎么调才不玄学训练命令是整套方案里最核心的一步python train.py \ --data datasets/traffic_sign/data.yaml \ --cfg models/yolov5s_traffic.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --name traffic_sign_run \ --device 0--data指向数据集配置--cfg指向模型配置--weights使用官方COCO预训练权重这样能明显加快收敛。--device 0指定第一张显卡如果是CPU训练就不加这个参数速度会慢很多。--imgsz 640是网络输入尺寸不是原图尺寸。交通标志在街景里往往偏小把imgsz提高到1280能改善小目标检测但训练显存和时间都会涨需要一个平衡。常见参数建议如下表参数建议值说明batch-size8到16显存不足时优先降到4epochs100到150数据量少时100轮足够imgsz640或1280小目标明显时用1280workers4到8OOM时可以设0排查optimizerSGD默认即可稳定lr00.01迁移学习常驻默认值mosaic1.0小目标增强有效过度裁切会引入脏样本超参数文件在data/hyps/hyp.scratch-low.yaml。毕业设计阶段没必要把所有超参数都改一遍只关注lr0、lrf、mosaic这几个就行。lr0是初始学习率迁移学习用0.01很稳lrf是最终学习率与初始学习率的比值默认0.2我习惯改成0.1让训练后期更细腻mosaic是马赛克增强对小目标有帮助但如果你的标注框很小mosaic裁切后可能只剩半个物体可以降到0.5。3.4 训练输出与效果评估训练结束后结果在runs/train/traffic_sign_run/下。best.pt是验证集mAP最高的权重last.pt是最后一次epoch的权重部署时优先用best.pt。results.png里有loss曲线和mAP曲线答辩时可以直接放这张图说明收敛过程。光看最终mAP不够要检查验证集loss是否跟着下降。如果train loss一直在降但val loss降不下去说明过拟合优先补数据增强或换小模型。如果mAP0.5只有0.2左右不要盲目加epoch先回退去看标签可能是类别顺序错了也可能是标注框过小yolov5把大量小框当噪声滤掉了。训练日志里每一轮会打印box_obj、cls、obj几个loss分量。如果cls占比异常高说明类别学习存在问题检查data.yaml的names顺序。如果obj一直不降大概率是anchor和物体尺寸不匹配。遇到这种情况先看runs/train/traffic_sign_run/anchors.png再决定是否手动调整anchor。4. 从模型到演示系统推理、导出与界面跑通4.1 用detect.py跑图片和视频推理训练完成后的常规操作是用detect.py验证效果。命令写法如下python detect.py \ --weights runs/train/traffic_sign_run/weights/best.pt \ --source data/images/street.jpg \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name traffic_demo--source可以是图片、文件夹、视频文件也可以是摄像头设备号0。如果现场演示没有摄像头用一段路口视频是最稳妥的选择帧率不会因为IO变慢。--conf-thres是置信度阈值0.25是默认值如果检测结果里漏框多可以降到0.15但误检会增加。--iou-thres是NMS的IoU阈值0.45常规不用动。--save-txt会把每个目标的框坐标和类别存成txt这个比较好用后续做红灯统计、标志数量统计都从txt里读。跑完在runs/detect/traffic_demo/下查看标注图片。如果发现检测框位置正确但置信度普遍低于0.3说明训练数据里的标志样本太小或者训练时imgsz不够大。此时可以先试imgsz1280重新训练数据增强也适当加强。4.2 导出ONNX与部署权衡毕业设计如果只做训练和detect.py演示工作量偏单薄。更常见的做法是导出ONNX再单独写推理程序。导出命令非常简单python export.py --weights runs/train/traffic_sign_run/weights/best.pt --include onnx --imgsz 640导出后会在权重同目录生成best.onnx。ONNX模型可以用onnxruntime加载不需要再安装完整的yolov5训练环境pip install onnxruntime-gpu python -c import onnxruntime as ort; print(ort.get_available_providers())这里要注意onnxruntime-gpu同样有CUDA版本匹配问题如果训练机的CUDA版本和onnxruntime依赖不一致运行时会出现“CUDA execution provider”加载失败。不想折腾的话就用CPU版onnxruntime检测一张640×640的图片在普通台式机上只要几百毫秒毕业设计演示完全够用。导出ONNX只是第一步最后一步是用PyTorch直接加载权重写推理逻辑因为PyTorch调用对大多数学生更熟悉也省去NMS后处理的麻烦。树莓派或嵌入式设备上部署时再考虑ONNX或OpenVINO模型可以换成yolov5n。4.3 给答辩和用户看的轻量识别界面很多毕业设计要求有界面常见的做法是Flask做个网页上传图片就能显示检测框。核心推理函数可以这样写import cv2 import torch def run_inference(model, img_path, conf0.25): img cv2.imread(img_path) results model(img, confconf) boxes results.xyxy[0].cpu().numpy() # 每行: x1, y1, x2, y2, conf, cls_id for b in boxes: cls_id int(b[5]) label f{model.names[cls_id]} {b[4]:.2f} cv2.rectangle(img, (int(b[0]), int(b[1])), (int(b[2]), int(b[3])), (0, 255, 0), 2) cv2.putText(img, label, (int(b[0]), int(b[1]) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) return img这里的results.xyxy[0]返回的是像素坐标直接画框就行不需要再归一化。model.names来自训练时的data.yaml所以如果names是中文OpenCV的putText画出来会乱码。稳妥做法是names里存英文或拼音展示时再映射到中文。Flask部分只需要把函数包成一个路由接收上传图片返回标注后的图片。前端不需要复杂一张表格加一个img标签就能满足答辩演示。重点是把推理逻辑和训练过程讲清楚界面是加分项不是核心。5. 避坑与排查毕业设计里最常见的5个翻车现场5.1 训练一开始loss就是nan现象训练第一轮loss直接显示nan然后一直不恢复训练基本作废。翻车原因有两个高频点一是标签txt里出现了负坐标或宽高为0二是类别id超过了nc-1导致loss计算越界。很多公开数据集的标注本身不干净转换脚本没有过滤是主因。解决训练前扫描一遍所有标签文件把宽高小于等于0的行删掉把坐标异常的样本挑出来重标。也可以写个几行脚本检查每行字段数量正常情况下每行必须是5个值类别id、中心点x、中心点y、宽、高。import os for split in [train, val]: label_dir fdatasets/traffic_sign/labels/{split} if not os.path.exists(label_dir): continue for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fp: for line in fp: parts line.split() if len(parts) ! 5 or float(parts[3]) 0 or float(parts[4]) 0: print(bad label, split, f, line.rstrip())这条检查做完再跑短epoch的小批量训练确认loss能降再上全量。5.2 显存不足只调batch-size没用现象6G显存跑batch-size16直接OOM改成8还是OOM换batch-size4也只是晚崩溃几分钟。实际上显存占用大头除了batch-size还有输入尺寸和模型深度。--imgsz 640比--imgsz 416的显存占用增加接近2倍yolov5s虽然是轻量模型但梯度回传时的中间激活值在batch-size较大时仍然可能爆显存。解决先固定imgsz640把batch-size降到4再不行就换yolov5n模型配置。训练时可以同时设--workers 0虽然数据加载慢一点但能排除数据预取导致的额外显存占用。如果毕业设计只有一台8G甚至6G显存的笔记本建议直接用yolov5n训练时间和显存压力都会小很多。5.3 mAP高但现场识别不准小目标anchor没有重聚类现象验证集mAP0.5有0.8但拿一张真实路口原图进去交通标志全没框出来把图片放大后模型又能正确识别。原因很典型默认anchor是从COCO数据集聚类出来的COCO里大中目标多交通标志在街景中占比小模型在训练时会重新聚类anchor但数据里小目标数量不够聚类结果仍然偏向中等尺度。解决训练结束后打开runs/train/traffic_sign_run/anchors.png对比红蓝点分布。如果标注框尺寸明显小于默认anchor第一组可以把models/yolov5s_traffic.yaml里的anchors改成更小的一组比如[4,5, 8,10, 12,18]同时把训练imgsz提高到1280。验证时也要用同样的imgsz不然模型尺度不匹配推理效果会打折扣。5.4 中文标签画成乱码现象自写推理脚本里用cv2.putText绘制“停止标志”输出图片上全是问号或者什么都不显示。OpenCV自带的putText只支持英文字体和数字不支持中文。模型预测的label来自names列表如果names直接写中文训练本身没问题但绘图阶段会翻车。解决最简单的方法是在data.yaml里使用英文或拼音例如stop、speed_limit_60绘图时再做一个字典映射到中文。如果必须在图片上显示中文用PIL加载中文字体比如Windows下的C:/Windows/Fonts/msyh.ttc把文字渲染到临时图像上再贴回原图。答辩演示中英文标签也是可以被接受的别在这个点上浪费时间。5.5 torch.cuda.is_available()返回False现象环境配置完成后训练时打印警告“CUDA unavailable”只能用CPU慢慢跑。原因大多是pip安装torch时默认装了CPU版本或者torch版本与显卡驱动不匹配。很多人看到requirements.txt里有torch就直接pip install结果装成了不带CUDA的wheel。解决先卸载已有torch和torchvision再按PyTorch官网指引安装对应CUDA的wheel。以CUDA 11.7为例pip uninstall -y torch torchvision pip install torch1.13.1cu117 torchvision0.14.1cu117 --index-url https://download.pytorch.org/whl/cu117装完再验证一次torch.cuda.is_available()如果还是False检查显卡驱动版本是否支持目标CUDA。这一步是所有后续训练的地基值得花半小时踩平。6. 进阶玩法把超参数搜索和模型轻量化写进毕设6.1 用yolov5自带的超参数进化脚本跑一轮yolov5训练脚本内置了超参数进化功能通过--evolve参数搜索一组mAP更高的超参数。在数据量不大时可以先跑一个小代次搜索当作亮点python train.py \ --data datasets/traffic_sign/data.yaml \ --cfg models/yolov5s_traffic.yaml \ --weights yolov5s.pt \ --epochs 20 \ --batch-size 8 \ --evolve 50 \ --cache--evolve 50表示搜索50组超参数组合每组用20个epoch评估。这个命令在普通笔记本上可能要跑几个小时毕业设计如果不追求极致效果直接用默认hyp文件就够了。想手动调的话我最常用的是把lrf改成0.1让学习率在训练后期衰减更缓慢对小数据集更友好。6.2 把yolov5s换成yolov5n或导出TensorRT如果答辩现场要演示摄像头实时识别而机器没有独立显卡yolov5n是最稳妥的选择。训练命令不需要大改把--weights yolov5n.pt和--cfg models/yolov5n_traffic.yaml替换掉即可。yolov5n的参数只有yolov5s的三分之一不到CPU推理速度能快一倍代价是精度会低一些。如果手里有NVIDIA显卡可以导出TensorRT引擎进一步加速python export.py --weights runs/train/traffic_sign_run/weights/best.pt --include engine --imgsz 640 --device 0TensorRT的整合成本比ONNX高不少如果只是答辩演示ONNX加onnxruntime已经够了。我的习惯是拿到一个新的数据集先把标签检查脚本跑一遍再用十张图片做个冒烟训练确认loss正常后才会跑全量。这套流程帮我避开了很多次标注文件翻车。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →