简介:面向计算机相关专业毕业设计场景,这套YOLOv5交通标志识别项目包含完整可运行的Python源码与6105张已标注数据集,覆盖数据预处理、模型训练、验证与推理全流程。项目源自评审98分的高分毕业设计,源码经过本地编译与严格调试,配置好环境即可直接运行,适合正在准备毕设、课程设计或期末大作业的学生,也适合需要深度学习实战练习的开发者。压缩包内共1568个文件,大小282.71MB,以Python脚本为主(779个py),并包含模型权重(pt)、YAML配置、样本图片(jpg/png)以及运行所需的依赖库(dll/pyd),还提供了TensorBoard训练日志,便于观察训练曲线。目前已有208人学习下载,数据集中涵盖不同场景与光照条件下的交通标志,标注格式贴合YOLO训练要求,代码结构清晰,便于二次开发与功能扩展。
1. 6720张交通标志图的真相:别被“配置完就能运行”带偏
拿到过不少打着“配置完环境就能运行”旗号的YOLOv5交通标志识别项目,我的第一反应不是兴奋,而是保持警惕。口号越是轻巧,环境依赖、数据集格式和路径命名里的暗坑越多。这个标题指向的是一个真正完整的东西:一个开箱可训练的YOLOv5工程,外加6105张已经标注好的交通标志图片。它的价值不在模型多先进——YOLOv5本身已经是常规武器,而在数据集的标注质量和整个工程的组织是否经得起新手从零跑通。适合的人群很明确:做毕设、课程设计,或者想在本地验证一下从数据到推理全流程的初学者。注意一个容易被忽略的事实:所谓的“配置完就能运行”,通常意味着环境是CentOS或者Ubuntu那一套默认Python均带了GPU,而Windows用户往往要在torch版本和CUDA版本上先折腾半天。所以,这个项目真正的第一道门槛,不是模型,是环境。
2. 搭建训练环境:从conda建虚拟环境到跑通自检脚本
2.1 为什么必须用虚拟环境:你之前的环境八成已经“烂”了
很多初学者拿到YOLOv5源码后的第一个动作是在base环境里直接pip install -r requirements.txt,结果把系统自带的Python搞成一团乱麻,后面装什么包都报冲突。我做深度学习项目有一个铁律:只用conda或venv虚拟环境,原因有三。其一,YOLOv5官方要求的torch版本和你的CUDA驱动版本强绑定,公司或个人的现有环境里很可能跑着另一个版本的torch,往上叠必然崩溃。其二,YOLOv5对opencv-python、pillow这类基础图像库有特定版本要求,全局改版本会波及你之前跑通的其它项目。其三,这里是6105张图片的规模虽然不大,但训练过程会安装一些编译依赖,比如pycocotools,必须在隔离环境里做。所以无论你机器上目前有多干净的Python环境,都建议用conda先建一个独立的。这一步不是废话,是后面所有排错能成立的前提。
conda create -n yolov5_traffic python=3.8 -y conda activate yolov5_traffic cd /path/to/yolov5_traffic_6105 # 进入项目根目录 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里解释一下命令的逻辑:创建python 3.8的干净环境是为了匹配YOLOv5仓库里许多依赖的兼容性范围,尤其一些编译型包在3.10上经常报缺少C++编译器的错;激活环境后必须cd到项目根目录再安装依赖,否则requirements里写的相对路径会失效。使用清华源是因为部分依赖包体积不小,直接走官方PyPI在部分网络条件下容易中断。requirements.txt里的核心是一个精确的torch版本和对应的torchvision版本,你不需要手动装torch,这个文件会帮你定死。装完后,验证一下关键包是否到位:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"如果输出里torch.cuda.is_available()是False,说明当前torch是CPU版或者CUDA驱动版本不匹配,这会直接导致后续训练速度极其缓慢甚至报错。CPU版torch在安装时往往会因为你用了清华源而悄悄装上。遇到这种情况,建议卸载后按你的CUDA Driver版本重新安装对应的cu版本torch。常见的做法是去官网用pip install torch==1.12.0+cu113 torchvision==0.13.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html拉取对应版本。这里就能看出标题里说的“配置完环境就能运行”真正的代价是什么:环境配置占整个项目工作量的六成以上,数据集和代码本身反而是省心的部分。
2.2 验证数据集完整性和目录结构:跑通前的最后一道保险
环境装好只是第一步。YOLOv5对数据集的目录结构要求极其严格,它的训练脚本会硬性读取images和labels两个子目录,并且图片和标注文件要严格同名同前缀。很多声称“高分完整”的项目,拿到手后你会在数据目录里发现有的图片没有对应标注、标注文件的行数异常、类别ID超出了你data.yaml里定义的类别数。这些隐患不排查,训练一启动就中途报错。我习惯在第一次训练前先跑一个自检脚本,把数据集的完整性量化出来。这一步建议先看目录树,再跑脚本校验。
tree -d dataset/执行后你应该看到一个相对标准的输入结构:dataset/train/images、dataset/train/labels、dataset/val/images、dataset/val/labels,如果是单目录版本则通常是dataset/images和dataset/labels。然后跑下面的Python脚本检查标注文件里有没有类别越界和空标注:
# check_labels.py import os from collections import Counter label_dir = "dataset/labels" class_counter = Counter() empty_labels = [] for root, dirs, files in os.walk(label_dir): for f in files: if not f.endswith(".txt"): continue path = os.path.join(root, f) with open(path, "r") as fp: lines = fp.readlines() if len(lines) == 0: empty_labels.append(path) for line in lines: parts = line.strip().split() if len(parts) != 5: print(f"格式错误: {path} -> {line}") cls_id = int(float(parts[0])) class_counter[cls_id] += 1 print("类别分布:", class_counter) print("空标注文件数:", len(empty_labels))这个脚本的逻辑不复杂但很实用:遍历labels目录下所有.txt标注,统计每个类别ID的出现次数,同时检测空标注文件和格式错误。正常交通标志数据集的类别ID应该是一段连续的整数,比如0~12之间。如果出现某个ID是47这种数值,说明你拿到的标注集可能是从COCO等其它数据集拼凑出来的,data.yaml里的类别名和实际标注对应不上。空标注文件如果较多,训练时loss计算会变得不稳定,最好将这些对应的图片从训练集里剔除。这一步做完,才算是真正具备了“开始训练”的资格,跑通环境自检脚本通常耗时不到一分钟,别跳过。
3. 6105张数据集的组织方式:标注格式、类别含义和data.yaml改造
3.1 先判断你的标注是YOLO格式还是VOC格式
一个声称标注好的数据集,最常见的是YOLO格式的txt标注,因为YOLOv5官方train.py只认这种格式。但也不排除有部分项目提供的是VOC格式的xml,需要额外做一次格式转换。判断方法很直接:打开一张图片同一目录下的同名txt文件,看每行内容是不是“一个整数+四个浮点数”。YOLO格式的标注文件每行对应一个目标,格式是class_id x_center y_center width height,其中坐标和宽高都是相对图片宽高的归一化数值,取值在0到1之间。如果是XML标签文件里的<object>节点,那就是VOC格式。这两种格式对后续data.yaml的配置没有本质影响,但训练脚本的读取方式完全不同。如果是VOC格式,你需要先写脚本转成YOLO txt。转换时最关键的坑是坐标归一化:VOC的xmin、ymin、xmax、ymax是像素值,转换成YOLO格式时必须统一除以图片的真实宽高,而不是训练脚本里默认的640。否则训练时目标框的位置就是错的,模型学出来的特征也全部偏移。
# voc_to_yolo.py import xml.etree.ElementTree as ET import os def convert(size, box): dw = 1. / size[0] dh = 1. / size[1] x = (box[0] + box[1]) / 2.0 y = (box[2] + box[3]) / 2.0 w = box[1] - box[0] h = box[3] - box[2] return x * dw, y * dh, w * dw, h * dh xml_dir = "Annotations" txt_dir = "labels" os.makedirs(txt_dir, exist_ok=True) 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() size = root.find("size") width = int(size.find("width").text) height = int(size.find("height").text) with open(os.path.join(txt_dir, xml_file.replace(".xml", ".txt")), "w") as f: for obj in root.iter("object"): cls = obj.find("name").text if cls not in class_mapping: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) bbox = (xmin, xmin, ymin, ymax) # 注意顺序 x, y, w, h = convert((width, height), bbox) f.write(f"{class_mapping[cls]} {x} {y} {w} {h}\n")这里要注意一个常见的坐标顺序陷阱:YOLO格式里的坐标顺序是x_center y_center width height,而VOC里的bndbox子节点顺序是xmin ymin xmax ymax。上面代码在构造bbox元组时特意调整了顺序,配合convert函数里box[0]和box[1]作为x方向、box[2]和box[3]作为y方向的约定。如果你直接把VOC的四个值原样塞进去,会导致x和y的坐标计算错位,检测框全部乱套。另外class_mapping需要你在代码里按自己的数据集类别名称到ID建立字典,比如{'stop': 0, 'speedlimit': 1, 'yield': 2}。类别ID不要求VOC文件里原本的排列顺序,只需要保持一致即可。这类转换脚本是这个项目里最容易被忽略、却又最影响结果的部分,凡是坐标换算的坑,十有八九都出在长宽比和归一化分母上。
3.2 动手配置data.yaml:类别数必须和标注牢牢对齐
无论你拿到的是YOLO txt还是VOC转过来的标注,最终训练前都要改配置文件data.yaml。这个文件里的nc(类别数量)必须和你标注文件里出现的最大类别ID+1保持一致,同时names列表里的顺序必须对应每个类别的ID。如果某个类别ID是5但你names列表里只有4个名字,训练初始化时不会被报错,但推理时预测结果全部偏到错误类别上,这是最阴损的无声错误。
# data.yaml train: dataset/train/images val: dataset/val/images nc: 10 # 必须等于 labels 里出现的最大 class_id + 1 names: ['speed_limit_30', 'speed_limit_60', 'stop', 'yield', 'no_entry', 'pedestrian_crossing', 'traffic_light', 'bicycle_crossing', 'warning', 'other']data.yaml的绝对路径问题值得单独说。官方train.py解析这个文件时,如果train和val字段用的是相对路径,它是以你当前终端的工作目录为基准去拼接的,而不是以data.yaml所在目录为基准。所以建议要么写绝对路径,要么统一在项目根目录下启动训练命令。还有一个常见做法是把dataset目录软链接到项目根下,这样相对路径永远有效。很多人把下载的6105张图放在D:\downloads\目录,却在项目根目录下跑train.py,导致文件找不到,报AssertionError: train: No images in ...。这类路径问题几乎占了训练报错的三分之一。因此在训练前,我强烈建议先执行一条验证命令,列出train目录下的图片数量,确认它和你标注文件数量对得上:
ls dataset/train/images | wc -l ls dataset/train/labels | wc -l如果两个数字相差超过3%,那说明有一部分图片没有标注或者标注文件没有对应图片,这时候要回退到上一节的check脚本去定位具体缺失项。别指望训练脚本帮你兜底,它的报错信息只会停留在“No labels in dataset/train/labels”这种粗粒度层面,不会告诉你具体哪个文件失配。跑了这一步,你的数据准备工作才算真正结束。
4. 训练全流程跑通:启动命令、关键参数与loss曲线读法
4.1 启动训练的最少命令:参数含义比跑通更值钱
数据目录就绪、data.yaml改完后,训练命令本身反而简单。YOLOv5官方仓库提供了train.py,传参项非常丰富,但新手最容易犯的错误是把所有参数都给上。实际上,一个可复现的最小训练命令只需五个参数:--data、--weights、--img、--batch-size、--epochs。其余的都可以用默认值。下面给出的是一个在消费级显卡上能跑得动的起步命令:
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --project runs/train \ --name traffic_sign_exp1逐项解释一下:--weights yolov5s.pt是加载官方在COCO上预训练好的YOLOv5s权重,这是迁移学习的标准做法,让模型从已经学到的基础特征出发去适配交通标志这个新任务;如果你不指定这个参数而用--weights '',模型会从随机初始化开始训练,收敛速度极慢,准确率上限也明显偏低。--img 640是训练时图片统一缩放到640x640像素,这是一个精度和速度的平衡点,考虑到交通标志往往是小目标,640是下限,有条件可以试736甚至832。--batch-size 16取决于显存,6GB显存用16基本合理,8GB可以试32,显存不够时优先减小batch而不是减小img。--name参数不是必须的,但它决定实验输出的目录名,方便后续对比不同版本的结果。--project指定了一个集中存放训练结果的目录,建议固定这个值,不然每个实验散落在不同层级的runs/目录下,过两周你自己都分不清哪个是哪个。
4.2 训练中间过程看什么:三张图和一组日志决定要不要中断
训练启动之后,终端会滚动输出epoch进度、GPU利用率、loss值和mAP指标。很多新手盯着loss下降就高兴,但其实在没有验证集的情况下,loss下降只能说明模型在过拟合训练集。关键是看runs/train/traffic_sign_exp1/目录下的训练曲线图,其中最有判断价值的三个文件是results.png、val_batch0_pred.jpg和train_batch0.jpg。results.png汇总了box_loss、cls_loss、obj_loss和mAP曲线,曲线应该在前30个epoch内快速下降,之后进入平缓震荡期。如果lr曲线一开始突然飙升再下降,这是默认的余弦退火策略在起作用,不用担心。真正的翻车信号是训练集loss一路走低,但验证集mAP在某个epoch后开始掉头向下,这基本是过拟合了,此时看val_batch0_pred.jpg里的预测框会发现大量重叠或重复检测的框。
tensorboard --logdir runs/train/traffic_sign_exp1 --port 6006如果你不习惯看静态图,也可以启动TensorBoard实时监控。打开浏览器后重点看box_loss和obj_loss两条曲线,如果它们在epoch 20后就变成一条水平直线,说明学习率已经衰减到很低的水平,再训练下去收益很小;如果loss曲线出现周期性锯齿,很可能是batch-size偏小而学习率偏大。中途想调低学习率时不用停训练,YOLOv5支持在每个epoch之间手动修改opt.yaml里的lr0,但需要注意训练脚本读取的是启动时快照,想改lr只能重新启动加--resume参数。这个技巧我在尝到教训后一直沿用:刚训练时用略高的lr0=0.01,到了epoch 60左右发现mAP停滞,停掉后用--weights runs/train/traffic_sign_exp1/weights/last.pt --resume --data data.yaml --lr0 0.001接着跑,效果通常比一次性跑完几百个epoch更好。
4.3 中断训练与断点续训:后悔药的正确用法
训练跑了一半显存被别的进程占用、或者停电关机,这是家常便饭。YOLOv5会在每个epoch结束自动保存last.pt和best.pt,其中last.pt就是给你续训用的断点。续训命令极其简单:
python train.py \ --weights runs/train/traffic_sign_exp1/weights/last.pt \ --data data.yaml \ --epochs 50 \ --resume注意这里的--epochs 50不是指还要训练50个epoch,而是指总epoch数目标为50。如果上一次已经跑完了30个epoch,resume时会自动从第31个epoch继续,直至达到50个epoch总数。很多人在这里误写了一个更大的数,导致模型被额外训练了一倍轮次并产生过拟合。另外要确保上次训练和续训时用同一个--project和--name,否则resume会因为找不到opt.yaml而中断。在续训前,我还会检查一下runs/train/traffic_sign_exp1/opt.yaml中的batch-size,确认它依旧小于当前显存容量。这个文件里保存了上一次训练的全部超参数,包括img大小、mosaic增强开关、类别数等。只要data.yaml没有被改过,续训整体是安全的。
5. 环境与训练高频故障排查:按现象倒查原因的5条实战记录
5.1 现象:import torch报错“Illegal instruction (core dumped)”
这个错误在CPU版本的torch上最常见,尤其在天冷或老旧的笔记本上。原因大致是当前机器的CPU指令集不支持新版本torch编译时启用的AVX512等高级指令。解决方案不是换Python版本,而是换一个编译目标较低的torch版本。我一般会卸载当前torch,然后安装torch==1.10.0的CPU版本,这类版本对老CPU的兼容性好很多。如果你坚持要跑CUDA,检查一下显卡驱动是否支持CUDA 11以上的runtime。对于标题里这种以毕设或课程设计为目标的场景,CPU跑YOLOv5s也不是不可以,只是训练100个epoch在普通笔记本上可能要40小时以上,建议用云GPU或者直接调整epoch到30。
5.2 现象:训练到第10个epoch突然报CUDA out of memory
这往往不是显存一开始就爆,而是随着训练进行,mosaic增强在随机拼图时产生的图片尺寸大于预设值,或验证集批次全部压入显存导致峰值上涨。解决办法最优先是减小batch-size,从16改到8;再不行就关掉mosaic增强,在train.py的参数里加--mosaic 0。另外,YOLOv5默认会缓存图片到内存以加速读取,如果你的内存容量小于8GB,加--no cache参数可以缓解。还有一个老手常用的技巧是把--img 640往下调到512,这会显著降低显存占用。如果这些都做完了还是爆,那基本是你的显卡显存低于2GB,建议直接换平台跑训练。
5.3 现象:训练结束时best.pt的mAP很高,但推理图片时几乎框不到目标
这个故障最迷惑人,也是那种“我明明跑通了为什么没效果”的典型。原因有两大类。一是数据泄露,也就是训练集、验证集里含有几乎相同的图片,导致mAP虚高,但真实场景测出来完全不行。检测方法:从训练集和验证集各取少量图片,计算感知哈希,寻找相似度高于0.95的图片对。二是训练时用的图片尺寸是640,但推理时检测小目标特别吃力,因为交通标志在整体画面中的像素占比很小。原因往往不在模型,而在于推理时没有开启--augment或没有设置更小的--conf-thres。用下面的命令在同一张图上排查:
python detect.py --source test_img.jpg --weights runs/train/traffic_sign_exp1/weights/best.pt --conf-thres 0.2 --img 640如果0.2的阈值下能框出目标,而默认0.25下框不出,说明你的模型对目标置信度本来就偏低,可以考虑修改数据增强策略或者增加小目标样本的重复采样频率,而不是盲目调模型结构。
5.4 现象:pip install -r requirements.txt 时opencv-python编译安装失败
这个在Windows上极为常见,本质是opencv-python新版本的wheel文件与你的Python版本或系统架构不兼容。翻车的具体表现是下载到一半开始从源码编译,然后报g++: fatal error之类的编译错误。解决路径有两条:第一条是直接安装旧版本opencv-python==4.5.4.58,这个版本对Python 3.8和Windows都提供预编译wheel;第二条是装opencv-python-headless版本替代,它不包含GUI模块,但对检测类任务完全够用,而且安装体积更小。这里要特别说明,不要盲目追求最新版opencv,YOLOv5的工具链在一些新版本opencv上反而会出现cv2.dnn读取权重异常的问题。
5.5 现象:运行detect.py后没有任何输出,也不报错,卡死不动
这个问题通常和模型加载无关,而是推理时默认的--source参数指向了固定的图片目录,但该目录下实际没有图片,YOLOv5会一直挂在文件监控循环里等待新图片。看起来像程序休眠了。解决方法是给detect.py的--source参数指定明确的图片路径或视频路径,避免使用默认目录。另一个可能性是Windows防火墙弹出了Python的入站连接提示,但窗口被隐藏了,程序在尝试上传结果到某个可视化的服务时阻塞。这时候去任务管理器里看Python进程是否还在占用CPU,如果占用为零,直接杀掉进程后重跑。这个现象虽然不常出现,但一旦碰上非常耗时间,需要有意识地往文件路径和防火墙两个方向排查。
6. 把模型用到真实场景里:单图推理、视频检测与数据复用的扩展思路
项目跑通只是起点,真正让自己能验收的是把训练好的best.pt放到实际场景里做推理。最直接的是单张图片测试:
python detect.py --source test.jpg --weights runs/train/traffic_sign_exp1/weights/best.pt --conf-thres 0.3 --save-txt加上--save-txt会把每个检测框的类别ID和归一化坐标写入txt文件,方便你做后处理。视频检测同理,只需要把--source换成视频文件路径,YOLOv5会自动逐帧推理并输出带标注框的新视频。这里有个小技巧,视频检测时可以把--img设为640,但把--conf-thres适当调低到0.2,因为视频连续帧之间存在信息冗余,偶尔漏掉一帧,视觉上并不突兀,但保留更多低置信目标可以防止重要的远距离小标志完全丢失。如果视频里有大量抖动导致的误检,可以加一个--iou-thres 0.4,降低重叠框的输出。
关于复用和扩展,一个常见且价值很高的方向是把自己收集的交通标志图片补进训练集。做法是先跑detect.py生成预标注,再人工修正错误标注,放进原数据集对应目录后重新训练。这样一来,项目就从一个“跑通的作业”变成了一个可持续迭代的个人数据集。我自己经历过一次惨痛教训:第一版模型只输出了19个类别的标志,到了实际路口测试,发现把一个“禁止驶入”误判成了“停车让行”,原因就是数据集的类别定义里没有对圆形与八角形的几何差异做充分的数据平衡。后来补了300张八角形负样本类别的图片,模型误判率才明显降下来。
另一个值得动手的验证是混淆矩阵:训练结束后查看runs/train/traffic_sign_exp1/confusion_matrix.png,如果两个高频类别之间存在大量互相误检,说明它们的图标在视觉上比较接近,需要单独扩充这两个类别的数据,而不是盲目调整超参数。这类基于可视化结果的判断,比盯着mAP数字管用得多。
最终我的习惯是:每次做完实验,把训练命令、data.yaml内容和confusion_matrix截图存到一个记录文件里,连同最佳权重一起归档。因为两个月后你再看自己的项目,最常问的一句话不是“模型多强”,而是“这个实验当时是怎么配的”。这个习惯帮我省了无数次重训的冤枉路,也把那次误判事故变成了后来补数据的契机。希望这个思路对你有用。
本文还有配套的精品资源,点击获取