☰
基于YOLOV5的交通标志识别实战:从数据集到部署全流程
2026/9/28 2:13:33 网站建设 项目流程

简介:这份资源面向计算机、人工智能相关专业的本科生与研究生,提供一套可直接用于毕业设计、期末大作业或课程设计的交通标志识别检测完整方案,基于YOLOv5深度学习框架实现,适合具备Python基础、希望快速完成高分项目的学习者。压缩包共266个文件,约423.33MB,包含53个py源码文件、59个yaml配置、55张jpg与26张png图像样本、10个pt权重文件,以及csv训练日志、sh脚本、Dockerfile部署文件等,覆盖数据、训练、推理与部署全流程。目前已有105人学习下载。代码注释详尽,新手也能看懂,项目经过严格调试,下载后简单部署即可运行,界面美观、功能完善,可直接作为毕设或课程设计提交,具有较高的实际应用与参考价值。

1. 从一张糊掉的限速牌说起:YOLOV5 交通标志识别到底能做成什么样

城市道路上的限速牌、禁止掉头、注意行人,这些标志在 60 公里时速下留给摄像头的有效像素可能不到 40×40。我拿手机在副驾随手拍过一段,回放时发现逆光下的限速 80 几乎糊成一团,人眼都要盯两秒才认得出。这就是交通标志识别(Traffic Sign Recognition, TSR)真正难的地方——不是分类本身,而是小目标、光照剧变、遮挡和运动模糊叠在一起。

用 Python 基于 YOLOV5 做交通标志识别,是这几年毕业设计和工程落地里性价比很高的一条路线。YOLOV5 把检测框和类别一次输出,推理速度快,单卡 1080Ti 就能跑到实时;Python 生态让数据清洗、增强、评估全流程都能脚本化。这套「数据集 + 代码 + 模型」的组合,适合三类人:要交高分毕业设计的学生、想快速验证车载感知原型的工程师、以及需要把交通标志检测嵌进已有视频分析流水线的开发者。

但我要先把预期说清楚:网上那些「训练完 mAP 99%」的截图,多半是在干净数据集上过拟合的结果。真实路测里,模型对远距离小标志的召回率会掉一大截。这篇文章不讲虚的,从数据组织、环境配置、训练参数到部署推理,把每一步能复现的命令和参数都摆出来,也把翻车的地方提前标出来。

2. 数据集怎么组织才不返工:从原始标注到 YOLOV5 可读格式

2.1 交通标志数据集的类别体系与标注规范

交通标志数据集的质量,八成决定了最终模型的上限。常见的公开数据有德国 GTSRB(分类为主)、TT100K(清华发布,含检测框)、以及国内一些标注平台产出的自定义数据。TT100K 覆盖 200 多类,但类别极不均衡,很多类只有几十张;如果直接拿来训,长尾类别基本学不动。

我的做法是先做类别合并。把「限速 60」「限速 80」这种细分类保留,但把出现次数少于 100 的稀有类合并成「其他标志」或直接剔除。类别数控制在 15 到 30 之间,是精度和可训练性的平衡点。标注格式统一用 YOLO 的 txt:每行class_id x_center y_center width height,坐标全部归一化到 0 到 1。

下面这段脚本用来检查标注文件里有没有越界坐标和空文件,这是训练前必做的一步:

import os import glob def check_labels(label_dir, img_dir): bad_files = [] empty_files = [] for txt_path in glob.glob(os.path.join(label_dir, "*.txt")): name = os.path.basename(txt_path) if os.path.getsize(txt_path) == 0: empty_files.append(name) continue with open(txt_path, "r") as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad_files.append((name, line_no, "字段数不对")) continue cls_id = int(parts[0]) coords = [float(x) for x in parts[1:]] # 归一化坐标必须落在 0~1,越界说明标注框跑出图像 if any(c < 0 or c > 1 for c in coords): bad_files.append((name, line_no, f"坐标越界: {coords}")) # 宽高为 0 是无效框 if coords[2] <= 0 or coords[3] <= 0: bad_files.append((name, line_no, "宽高非正")) print(f"空标注文件: {len(empty_files)}") print(f"异常标注: {len(bad_files)}") for item in bad_files[:20]: print(item) return bad_files check_labels("./labels/train", "./images/train")

逻辑说明:遍历所有 txt,先查空文件(空文件会让该图变成纯背景,少量可以,多了会拉低召回),再逐行校验字段数和坐标范围。参数上,coords[2]和coords[3]是宽高,必须为正;坐标越界通常是标注工具导出时没做裁剪。跑完这个脚本,异常文件要么修要么删,别带着脏数据进训练。

2.2 划分训练集验证集与 data.yaml 的写法

数据集目录按 YOLOV5 约定组织:images/train、images/val、labels/train、labels/val平行存放,文件名一一对应。划分比例我一般用 8:2,但如果某类样本特别少,要做分层抽样,保证验证集里每类都有几张,否则验证指标会失真。

data.yaml是训练入口的配置,写错路径是最常见的低级错误:

# data.yaml path: /home/user/traffic_sign # 数据集根目录 train: images/train # 相对 path 的训练图目录 val: images/val # 相对 path 的验证图目录 nc: 20 # 类别数,必须和标注里的 class_id 最大值+1 一致 names: # 类别名,顺序必须和 class_id 对应 0: speed_limit_60 1: speed_limit_80 2: no_left_turn 3: pedestrian_crossing # ... 其余类别

参数说明:nc写错会导致训练时索引越界或类别错位,这是血泪教训——曾经把 nc 写成 21 但实际只有 20 类,训练不报错,但最后一类永远学不到。names的顺序必须和标注时用的 class_id 严格对应,改类别名不改顺序没事,改顺序等于把标签全打乱。

提示:划分完数据集后,用train和val的图片数量、标注框数量做一次交叉核对,两边类别分布差异过大就重新分层。

3. 环境配置与 YOLOV5 训练:从 conda 到第一个收敛的模型

3.1 conda 环境与 YOLOV5 依赖安装的完整命令

环境这块翻车最多。YOLOV5 对 PyTorch 和 CUDA 版本敏感,版本不匹配会报一堆看不懂的错。我一般用 conda 建独立环境,避免污染系统 Python:

# 创建 Python 3.9 环境,3.9 对 YOLOV5 兼容性最稳 conda create -n yolov5_ts python=3.9 -y conda activate yolov5_ts # 安装 PyTorch,CUDA 11.8 对应大多数 30/40 系显卡 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 克隆 YOLOV5 源码并安装依赖 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt

逻辑说明:先建环境再装框架,顺序不能反。--index-url指定 PyTorch 官方 wheel 源,避免 pip 默认源拉到 CPU 版本。装完用下面这行验证 GPU 是否可用:

python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

输出True加显卡型号才算成功。如果输出False,八成是 CUDA 版本和驱动不匹配,先nvidia-smi看驱动支持的 CUDA 上限,再回退 PyTorch 版本。这一步没有后悔药,环境不对后面全白搭。

3.2 训练命令与关键超参数怎么设

YOLOV5 的训练入口是train.py,最简命令如下:

python train.py \ --data data/traffic_sign.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/train \ --name ts_exp1

参数逐个说:--weights yolov5s.pt用预训练权重做迁移学习,小数据集千万别从零训,收敛慢且容易过拟合;--img 640是输入分辨率,交通标志是小目标,可以提到 960 甚至 1280,但显存占用翻倍;--batch 16在 8G 显存下比较稳,显存够可以加到 32;--epochs 100配合早停,实际往往 60 到 80 轮就收敛了。

超参数里最值得调的是学习率和锚框。YOLOV5 默认用--hyp data/hyps/hyp.scratch-low.yaml,小数据集建议换成hyp.scratch-low.yaml并手动降lr0到 0.005 左右。锚框方面,交通标志的宽高比和 COCO 通用目标差异大,用下面命令重新聚类:

python utils/autoanchor.py --data data/traffic_sign.yaml --img 640

它会根据你的标注框重新算 k-means 锚框,替换掉默认的 COCO 锚框,对小目标召回提升明显。这一步很多人跳过,结果就是模型对大标志还行,对远处小标志几乎瞎。

3.3 训练过程监控与指标解读

训练启动后,runs/train/ts_exp1下会生成results.csv和各类曲线图。重点盯三个指标:metrics/mAP_0.5、metrics/mAP_0.5:0.95、以及val/box_loss。mAP_0.5 到 0.85 以上算可用,0.5:0.95 能到 0.6 就不错。如果 box_loss 一直不降,检查标注框是不是普遍偏大或偏小。

用 TensorBoard 看曲线更直观:

tensorboard --logdir runs/train

浏览器打开对应端口,看train/box_loss和val/box_loss的差距。两者差距持续拉大就是过拟合,该加数据增强或提前停。YOLOV5 内置了 mosaic、mixup 增强,交通标志场景下 mosaic 很有用,但 mixup 对小目标可能把标志糊没,可以调低mixup权重。

4. 推理部署与效果验证:模型训完只是开始

4.1 用 detect.py 跑通单图与视频推理

训练完的权重在runs/train/ts_exp1/weights/best.pt。单图推理:

python detect.py \ --weights runs/train/ts_exp1/weights/best.pt \ --source test_images/ \ --img 640 \ --conf-thres 0.4 \ --iou-thres 0.45 \ --device 0

参数说明:--conf-thres 0.4是置信度阈值,交通标志场景宁可漏检也别误检,可以设到 0.5;--iou-thres 0.45控制 NMS 合并重叠框的力度,标志密集时调低到 0.4 避免相邻标志被合并。视频推理把--source换成视频路径即可,输出默认在runs/detect/exp。

4.2 用验证集算 mAP 与混淆矩阵定位问题

光看推理图不够,要用val.py出量化指标:

python val.py \ --weights runs/train/ts_exp1/weights/best.pt \ --data data/traffic_sign.yaml \ --img 640 \ --task val \ --save-json

跑完会输出每类的 P、R、mAP,并生成混淆矩阵。混淆矩阵是排查利器:如果「限速 60」大量被识别成「限速 80」,说明这两类特征太像,要么加数据,要么在类别定义上合并。如果某类召回极低,先看该类验证集样本数,少于 50 张的基本学不好。

4.3 导出 ONNX 与推理加速

要嵌到 C++ 或边缘设备,导出 ONNX:

python export.py \ --weights runs/train/ts_exp1/weights/best.pt \ --include onnx \ --img 640 \ --opset 12

--opset 12兼容性较好,导出后用 onnxruntime 验证输出是否一致。注意导出时的--img必须和训练一致,否则锚框对不上,检测框会全乱。这一步在部署时踩过坑:训练 640 导出 320,结果框位置整体偏移,排查了半天才发现是分辨率不匹配。

5. 避坑与排查:那些让模型「看起来能跑其实没用」的细节

5.1 训练 loss 正常但 mAP 极低

现象:box_loss 稳步下降,但 mAP_0.5 一直在 0.1 附近。原因通常是data.yaml里nc和实际类别数不一致,或者names顺序和标注 class_id 错位。解决:用脚本统计所有标注里出现过的 class_id,确认最大值加一等于 nc,再核对 names 顺序。

5.2 小目标标志几乎检测不到

现象:近处大标志能检出,远处小标志全漏。原因是默认锚框偏大,且输入分辨率不够。解决:跑autoanchor.py重新聚类锚框,把--img提到 960,并在hyp里调低anchor_t阈值让更多小框参与匹配。

5.3 验证集指标高但实拍图效果差

现象:val mAP 0.9,自己拍的视频里错检漏检一堆。原因是训练集和实拍场景域差异大——训练集多是晴天正拍,实拍有逆光、雨雾、运动模糊。解决:补充实拍数据进训练集,加--augment做测试时增强,或在推理前做直方图均衡化预处理。

5.4 显存溢出(CUDA out of memory)

现象:训练中途报 OOM。原因是 batch 或 img 太大。解决:降 batch 到 8,或开--multi-scale让输入尺寸动态变化,或改用 yolov5s 而非 yolov5m/l。梯度累积也能缓解,但 YOLOV5 原生支持有限,简单点直接降 batch。

5.5 推理速度达不到实时

现象:单帧推理超过 100ms。原因是模型太大或没开半精度。解决:换 yolov5s,推理加--half开 FP16,导出 TensorRT 引擎。实测 yolov5s 在 1080Ti 上 FP16 能到 5ms 左右一帧,足够实时。

6. 把模型塞进真实流水线:一个可复用的推理封装技巧

训完模型只是半成品,真正要用起来得封装成可调用的接口。我一般写一个类,把加载、预处理、推理、后处理、画框全包进去,这样换模型或换输入源都不用改上层代码。下面这个封装基于 ultralytics 的YOLOv5接口,实测在批量图片和视频流上都能用:

import cv2 import torch import numpy as np class TrafficSignDetector: def __init__(self, weights, img_size=640, conf=0.4, iou=0.45, device='0'): # 加载本地权重,避免每次推理重新读盘 self.model = torch.hub.load('./yolov5', 'custom', path=weights, source='local') self.model.conf = conf self.model.iou = iou self.model.img_size = img_size self.model.to(f'cuda:{device}' if device != 'cpu' else 'cpu') self.model.eval() def preprocess(self, img_bgr): # YOLOV5 内部接受 BGR,无需转 RGB,但尺寸要记录用于还原 self.orig_h, self.orig_w = img_bgr.shape[:2] return img_bgr def infer(self, img_bgr): img = self.preprocess(img_bgr) results = self.model(img) # pandas 格式便于按列取用 df = results.pandas().xyxy[0] detections = [] for _, row in df.iterrows(): detections.append({ 'class': row['name'], 'conf': float(row['confidence']), 'bbox': [int(row['xmin']), int(row['ymin']), int(row['xmax']), int(row['ymax'])] }) return detections def draw(self, img_bgr, detections): for det in detections: x1, y1, x2, y2 = det['bbox'] label = f"{det['class']} {det['conf']:.2f}" cv2.rectangle(img_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img_bgr, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return img_bgr # 使用示例 detector = TrafficSignDetector('runs/train/ts_exp1/weights/best.pt') frame = cv2.imread('test.jpg') dets = detector.infer(frame) vis = detector.draw(frame, dets) cv2.imwrite('result.jpg', vis)

逻辑说明:__init__里用torch.hub.load加载本地 YOLOV5 仓库和自定义权重,source='local'避免联网拉取。infer返回结构化列表而不是原始张量,方便上层做业务判断,比如只关心「限速」类就过滤class字段。draw单独拆出来,是因为有些场景只要检测结果不要画框,比如对接告警系统。

参数上,conf和iou在类初始化时设好,批量处理时不用每帧传。img_size要和训练一致,否则精度掉。这个封装在 1080P 视频上单帧约 8ms(yolov5s + FP16),批量处理 1000 张图大概 12 秒。

一个我踩过的坑:torch.hub.load第一次调用会缓存模型结构,如果中途换了权重文件路径但没重启进程,可能加载的还是旧权重。稳妥做法是每次换模型都新建 detector 实例,别复用。

最后说个习惯。我现在每训完一个模型,都会拿手机去路口拍 20 张不同光照的实拍图单独测一遍,不看 mAP,就看漏检和误检的具体案例。指标好看不代表能用,实拍图才是照妖镜。这套流程从数据到部署走下来,快的话两三天能出第一版可用模型,慢的话卡在数据清洗上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询