简介:YOLOv5交通标志识别检测项目是一套面向毕业设计、课程设计与期末大作业的完整资源,包含数据集、源码和预训练模型,帮助开发者快速掌握目标检测项目全流程,避免从零搭建环境的繁琐。资源共266个文件,以Python源码、yaml配置文件、pt模型权重及jpg/jpeg图片数据集为主,另包含训练日志、结果统计csv、Dockerfile等支撑文件,压缩包整体约423MB,目录结构划分清晰,方便按模块训练、评估与部署。目前已有252人学习下载,项目经严格调试确保可运行,代码附有详细注释,新手也能轻松复现。系统功能完善、界面美观、操作简单,整合数据预处理、模型训练、实时识别与结果导出等环节,个人手打98分、导师认可度高,既可直接用于毕设答辩,也可作为课程设计的高分范例和二次开发基础。
1. 从数据集到部署,YOLOV5交通标志识别检测的完整落地路径
每年毕业季,交通标志识别都是目标检测方向的热门选题,但绝大多数开源项目只给你一套能跑的代码,数据清洗、标注格式转换、超参数调整和模型评估这些真正决定论文和工作量质量的环节,往往被一笔带过。交通标志识别检测和通用目标检测最大的区别在于:目标尺寸跨度极大、类别间外观高度相似、实拍场景光照复杂,直接用默认配置训练很难拿到可观指标。本文从数据集组织、标注格式转换、YOLOV5训练调优到模型部署,给出一条经过验证的完整路径,适合正在做毕设或准备参加竞赛的开发者,也适合想系统掌握YOLOV5训练全流程的工程师参考。重点会放在数据准备和超参数选择上,这两块是拉开分数差距的关键。
2. 交通标志识别数据集构建:从公开源到YOLOV5格式的转换全流程
2.1 公开数据集选型与适用场景分析
交通标志识别领域有几类公开数据集,选型会直接影响模型最终效果。TT100K(Tsinghua-Tencent 100K)是国内使用最广泛的交通标志数据集,包含上万张腾讯街景图像,标注了上百个类别,适合中文交通场景和毕设场景;德国GTSRB/GTSDB数据集规模大、标注质量高,但类别和国内标志差异明显,适合做预训练或算法验证;LISA数据集面向美国交通标志,遮挡和光照变化丰富,适合做鲁棒性验证;此外还有KITTI这类包含交通标志的自动驾驶数据集,不过主要面向多任务场景。
选择数据集的第一个判断标准是任务匹配度,如果毕设面向国内场景,TT100K是首选。第二个标准是类别体系,TT100K原版有45类以上,很多类别样本量不足100,直接训练会带来严重的类别不均衡问题,常见做法是筛选出样本量充足的类别,合并相近类别,比如把不同限速标志归为speed_limit一类,把禁止类标志归为prohibition。第三个标准是标注格式,这些公开数据集原始标注各不相同,需要统一转换为YOLOV5默认使用的YOLO格式,也就是每个图像对应一个txt文件,每一行是 class_id x_center y_center width height,坐标全部归一化到0到1之间。
对于没有现成数据集、需要自采数据的场景,常见做法是先用手机或行车记录仪采集视频,抽帧后使用LabelImg或LabelStudio进行人工标注。自采数据的优势是场景可控,能覆盖学校周边、城市快速路、乡道等具体环境,但标注成本高,一个包含5000张图的数据集至少需要两天的人力投入。所以更推荐的路径是:公开数据集做主体,自采数据做补充,用自采数据专门覆盖公开数据集中缺失或稀少的类别。
2.2 标注格式转换与数据划分的Python实现
2.2.1 TT100K标注转YOLO格式的核心代码
拿到的TT100K原始标注是JSON格式,每个图像对应一个标注对象,标注信息包含类别、边界框坐标和是否遮挡、是否包含在远距离组等属性。转换脚本的核心逻辑是读入JSON、筛选可用类别、过滤小目标、计算归一化坐标并写入txt文件。
import json import os import shutil from pathlib import Path # 只保留样本量充足的类别,并做类别合并映射 CLASS_MAP = { "i2": "prohibition", "i4": "prohibition", "i5": "prohibition", "p3": "warning", "p5": "warning", "p6": "warning", "ph4": "guide", "ph5": "guide", "pl100": "speed_limit", "pl120": "speed_limit", "pl40": "speed_limit", "pl50": "speed_limit", "pl60": "speed_limit", "pl80": "speed_limit", "pl30": "speed_limit", "pl20": "speed_limit", "pl5": "speed_limit", "pl15": "speed_limit", "i8": "prohibition", "i9": "prohibition", "i10": "prohibition" } def convert_tt100k_to_yolo(json_path, img_dir, out_dir, min_size=20): """将TT100K JSON标注转换为YOLO格式txt文件""" with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) # 建立类别名到数字id的映射 categories = list(set(CLASS_MAP.values())) cat2id = {c: i for i, c in enumerate(categories)} for img_name, annos in data["imgs"].items(): img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue txt_path = os.path.join(out_dir, "labels", Path(img_name).stem + ".txt") # 读取图片尺寸用于归一化 from PIL import Image w, h = Image.open(img_path).size lines = [] for obj in annos["objects"]: category = CLASS_MAP.get(obj["category"]) if category is None: continue xmin, ymin, xmax, ymax = obj["bbox"] # 过滤过小目标,避免噪声标签 if (xmax - xmin) < min_size or (ymax - ymin) < min_size: continue # 归一化到[0,1]区间 x_center = (xmin + xmax) / 2 / w y_center = (ymin + ymax) / 2 / h width = (xmax - xmin) / w height = (ymax - ymin) / h lines.append(f"{cat2id[category]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") if lines: os.makedirs(os.path.dirname(txt_path), exist_ok=True) with open(txt_path, "w") as f: f.write("\n".join(lines))这段代码有几个关键参数需要说明。min_size用来过滤小于20像素的边界框,交通标志在街景图中经常只有十几像素大小,这些样本人眼都难以辨认,直接用来训练会干扰特征提取。类别映射CLASS_MAP是决定模型能力的核心,合并相近类别能显著提升单类样本量,对最终的mAP影响远大于模型结构的选择。坐标归一化必须基于原图尺寸计算,如果图像被resize过,需要在转换前同步修改尺寸信息。
2.2.2 训练集/验证集划分与目录结构组织
YOLOV5训练时会读取数据集配置文件中的train和val路径,标准的目录结构是images和labels两个平级目录,各自下面再按train/val分目录。数据划分要保证类别分布一致,不能简单随机切分,否则某些小样本类别可能全部落在训练集或验证集中。
import random from pathlib import Path import shutil def split_dataset(img_dir, label_dir, train_ratio=0.85, seed=42): """按类别分布进行分层划分""" random.seed(seed) images = list(Path(img_dir).glob("*.jpg")) # 按标签文件中的第一个数字(类别id)分组 from collections import defaultdict class_groups = defaultdict(list) for img in images: label_file = Path(label_dir) / (img.stem + ".txt") if not label_file.exists(): continue with open(label_file) as f: first_line = f.readline().strip() if first_line: cls_id = first_line.split()[0] class_groups[cls_id].append(img) train_imgs, val_imgs = [], [] for cls_id, items in class_groups.items(): random.shuffle(items) split_idx = int(len(items) * train_ratio) train_imgs.extend(items[:split_idx]) val_imgs.extend(items[split_idx:]) # 复制到目标目录 def move_files(img_list, split_name): for img in img_list: dest_img = Path("dataset/images") / split_name / img.name dest_label = Path("dataset/labels") / split_name / (img.stem + ".txt") shutil.copy(img, dest_img) shutil.copy(Path(label_dir) / (img.stem + ".txt"), dest_label) move_files(train_imgs, "train") move_files(val_imgs, "val") print(f"Train: {len(train_imgs)}, Val: {len(val_imgs)}")这个分层划分逻辑保证了每个类别在训练集和验证集中的比例一致,避免出现过拟合到某一类的情况。seed=42可以复现实验,论文中需要注明随机种子。划分比例train_ratio=0.85适用于中小规模数据集,如果数据量小于2000张,建议提高到0.9,毕竟验证集本身样本少会导致评估指标方差大。
2.3 数据增强策略:Mosaic与自动增强的取舍
YOLOV5内置了Mosaic、MixUp、HSV扰动、随机翻转等多种增强策略。Mosaic把4张图拼接成一张,相当于一个batch内同时看到4个不同场景,对提升小目标检测和跨场景泛化效果明显。对于交通标志这种小目标密集的场景,Mosaic是必须开启的。
但Mosaic在交通标志场景有两个副作用需要留意。第一是拼接边界处的标志可能被切断,导致标注框与图像内容不对齐,默认的裁剪概率在毕设数据上建议调低,可以在数据配置文件里单独设置。第二是Mosaic对归一化坐标的精度要求更高,如果原始标注框本身有偏差,增强后会放大误差。
HSV扰动对交通标志这种颜色语义强烈的目标很有帮助,因为实际场景中同一个标志在晨昏、逆光、阴雨下的色相和饱和度变化明显。建议hsv_h=0.015、hsv_s=0.7、hsv_v=0.5,比默认值略高。翻转策略中,左右翻转对大部分标志是安全的,但限速标志中的数字会被镜像,如果类别区分依赖数字内容,建议关闭翻转或只对非数字类使用。
3. YOLOV5模型选型与训练配置:从权重文件到命令行参数的逐项拆解
3.1 模型规模选择:s/m/l/x与输入分辨率的关系
YOLOV5有n/s/m/l/x五个版本,区别主要在深度倍数和宽度倍数。交通标志识别对实时性要求通常低于自动驾驶,但对小目标召回率要求高,所以一般选yolov5s或yolov5m作为基线。yolov5s在COCO上mAP约37,模型体积约14MB,推理速度快;yolov5m体积约40MB,精度提升约3个百分点,但训练时间翻倍。
选型还要看显存。输入分辨率设为640时,yolov5s的batch_size为32大约需要8GB显存,yolov5m需要12GB左右。如果只有6GB显存,建议yolov5s加batch_size=16,或者开启梯度累积。输入分辨率对交通标志的检测效果影响很大,默认640可以满足大多数场景,但TT100K中有大量小于32像素的标志,把输入分辨率提升到960或1280能显著改善小目标召回,代价是训练时间增加到原来的2到3倍。
最新版的YOLOV5也提供了--weights参数支持加载不同预训练权重。COCO预训练权重对通用特征有很好的初始化作用,交通标志虽然是新类别,但底层纹理、边缘、颜色特征可以迁移,强烈建议使用COCO预训练权重而不是随机初始化训练。如果数据集中类别和COCO差异较大,可设置--freeze-layers微调前面几层,但其实现有YOLOV5在全部层参与训练时收敛也很快,一般不需要冻结层。
3.2 自定义数据集的yaml配置与目录映射
创建数据集配置文件traffic_sign.yaml,放在yolov5/data/目录下:
# 数据集的根目录路径,推荐写绝对路径,避免相对路径带来的问题 path: /home/user/projects/traffic_sign_dataset train: images/train val: images/val # 类别数量与类别名称列表,顺序必须与标注txt中的数字id一致 nc: 4 names: 0: speed_limit 1: prohibition 2: warning 3: guide这个文件里最容易出错的是path字段使用相对路径时,YOLOV5会基于当前工作目录去拼路径,如果在其他目录下启动训练就会报错。建议直接用绝对路径。nc必须和标注文件中的最大类别id+1相等,如果标注中出现了id=4但nc写了4,训练会在数据加载阶段直接报错或静默忽略。names的顺序不需要和任何外部规范一致,但一旦开始训练就不要修改,否则模型输出的类别含义会全部错位。
3.3 训练命令的关键参数解读与推荐配置
训练启动命令是:
python train.py \ --data traffic_sign.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --cache ram \ --device 0 \ --name traffic_sign_run1每个参数的选择理由如下。--img 640是精度与训练速度的平衡点,如果验证集中出现大量mAP@0.5为0但人工检查其实检测到了的情况,说明小目标丢失,可以提升到960。--batch-size 16在8GB显存下是比较安全的配置,如果显存溢出,优先降batch而不是降分辨率。--cache ram把图像一次性读入内存,避免每个epoch都从磁盘读取,能缩短30%左右的训练时间,代价是占用约等于数据集大小的内存。--epochs 100对交通标志数据集通常足够,如果训练30个epoch后验证集mAP还在明显上升,续跑即可。
3.4 超参数文件中的必调项:学习率、锚框与损失权重
YOLOV5的超参数文件hyp.scratch-low.yaml里有几十个参数,但真正需要手工调整的只有几个。lr0初始学习率默认0.01,配合batch-size=16通常没问题,但如果batch-size降到8,梯度估计噪声增大,建议lr0=0.005。lrf是最终学习率与初始学习率的比值,默认0.01表示余弦退火最终降到0.0001。
锚框计算是交通标志场景容易忽略的一环。YOLOV5默认锚框基于COCO数据集计算,COCO中的目标普遍偏大,而交通标志是小目标,直接使用默认锚框会导致回归头需要更大的偏移量才能拟合真实框。训练时添加--noautoanchor可跳过自动计算,但更推荐让YOLOV5在训练前自动用k-means在训练集上重算锚框,这个逻辑在utils/autoanchor.py中,训练时会打印新的锚框值,并提示anchor与目标尺寸的匹配度。配合锚框还需要关注--multi-scale参数,开启后输入尺寸在0.5到1.5倍之间随机变化,对小目标检测有正则化效果,但训练时间会增长。
4. 训练过程监控与精度调优:解决过拟合和小目标漏检的实战手法
4.1 训练日志与tensorboard监控的关键指标解读
训练启动后会输出每个epoch的loss、精度、召回率和mAP。需要重点关注三个值:box_loss代表边界框回归损失,持续不降说明定位能力弱;obj_loss代表置信度损失,交通标志类别少但背景复杂,这个值通常会偏高;cls_loss代表分类损失,如果这个值在后期仍高于box_loss,说明类别间特征区分度不足。
使用tensorboard查看更直观:
tensorboard --logdir runs监控时看几个关键信号。第一个是验证集mAP@0.5是否持续上升但mAP@0.5:0.95停滞,说明预测框定位还不够精细;第二个是训练集loss和验证集loss的gap是否超过30%,超过说明过拟合,需要增强数据或减少epoch;第三个是每个类别的AP直方图,如果某个类别AP显著低于均值,说明该类样本量不足或特征与其他类别混淆。
4.2 针对小目标的三个有效手段:SAHI切片、锚框重算与多尺度训练
交通标志检测的典型痛点是mAP@0.5可以到0.8以上,但mAP@0.5:0.95只有0.3左右,原因就是小目标占比高。实测有效的手段有三个。
第一个是SAHI(Slicing Aided Hyper Inference),推理时把原图切成多个有重叠的切片,每个切片单独检测再合并结果。这种方式对小目标检测提升非常明显,TT100K上采用SAHI后mAP@0.5:0.95能提升8到12个百分点,代价是推理时间翻倍。
from sahi.model import Yolov5DetectionModel from sahi.predict import get_sliced_prediction model = Yolov5DetectionModel( model_path="runs/train/traffic_sign_run1/weights/best.pt", confidence_threshold=0.3, device="cuda:0" ) result = get_sliced_prediction( image_path="test_image.jpg", detection_model=model, slice_height=320, slice_width=320, overlap_height_ratio=0.2, overlap_width_ratio=0.2 )slice_height和slice_width需要根据目标尺寸设定,交通标志如果大多小于64像素,切片尺寸设为320效果较好;overlap_ratio用于避免目标被切片边界切断,0.2是经验值。切片数量越多,推理速度越慢,所以SAHI更适合离线评估或对速度不敏感的预测场景。
第二个手段是重新计算锚框。在train.py训练时如果看到打印的anchors与实际目标尺寸匹配度不高,可以手动运行python utils/autoanchor.py --data traffic_sign.yaml --img 640查看建议锚框,然后在yaml的anchors字段中替换默认值,这相当于给回归头一个更好的初值,能加速收敛。
第三个手段是开启多尺度训练:
python train.py --data traffic_sign.yaml --weights yolov5s.pt --img 640 --multi-scale多尺度训练本质是数据增强的扩展,让模型在不同输入尺寸下都能保持检测能力,对小目标鲁棒性提升明显。如果训练时间紧张,可以只在前30个epoch开启,后面关闭继续精调和稳定收敛。
4.3 早期停止与最优权重保存策略
YOLOV5默认保存last.pt和best.pt两个权重,best.pt是基于验证集mAP@0.5:0.95的最高分保存的。实际操作中,训练后期可能会出现验证集AP微降但实际图像预测效果反而更好的情况,这是因为mAP指标与主观感受存在偏差。我的做法是保存最后10个epoch的权重,用一段固定的测试视频或图片集侧试,选出视觉效果最好的版本作为最终模型。
早期停止可避免过拟合。在train.py中,patience参数控制连续多少个epoch验证集指标不提升就停止训练,默认100对毕设时间预算太长,建议设为30。如果训练在第40个epoch因为过拟合开始下降,这时回退到best.pt并降低学习率续跑20个epoch往往能再提升1到2个点的mAP。
4.4 常见训练报错与显存、路径问题的排错清单
训练过程中最常见的报错是CUDA out of memory。解决顺序是:先降batch-size到8,再降--img到512,最后考虑开启--gradient-accumulation梯度累积。梯度累积的语义是用多个小batch模拟大batch,比如--batch-size 8 --gradient-accumulation 4等效于32的batch,但每个batch内的BatchNorm统计仍然基于8张图,所以累积不会完全等同真32。交通标志数据集目标普遍较小,BatchNorm统计量受影响有限,可以接受。
路径类报错集中在FileNotFoundError和AssertionError: train: No labels in ...。前者通常是因为yaml中path写错或labels目录结构不对;后者是因为图片有对应文件但labels目录为空,或标注txt的格式不正确。检查思路是随机挑一张图,人工查看其对应的txt内容:
cat dataset/labels/train/xxx.txt如果txt内容为空文件,说明标注在转换时被过滤掉了,检查过滤条件里的最小目标尺寸;如果txt内容是10 0.5 0.5 0.1 0.1这样的格式,类别id10超出nc范围,检查类别映射。另外要注意Windows和Linux的路径分隔符差异,同一个yaml在Windows训练时用\,在Linux上要用/,建议统一使用pathlib处理。
5. 模型部署与推理脚本:从best.pt到实际检测的完整链路
5.1 使用detect.py进行批量推理与结果输出
训练完成后,最直接的使用方式是通过YOLOV5自带的detect.py脚本。
python detect.py \ --weights runs/train/traffic_sign_run1/weights/best.pt \ --source test_images/ \ --img 640 \ --conf-thres 0.3 \ --iou-thres 0.45 \ --save-txt \ --save-conf \ --project runs/detect \ --name test_output--conf-thres是置信度阈值,交通标志检测建议设在0.25到0.3之间,因为小目标在降采样过程中特征弱,置信度普遍偏低,设太高会漏检。--iou-thres是NMS的IoU阈值,0.45是默认值,当标志密集排列时,例如连续多个限速标志,可以降到0.4减少相邻框被抑制的情况。--save-txt会同时输出检测结果的标注txt文件,方便后续评估或生成可视化报告。
推理结果会保存为带标注框的图片和对应txt文件,txt每行是class_id x_center y_center width height conf,这个格式可以直接作为伪标签进行半监督学习迭代。
5.2 模型导出与推理加速:ONNX与CPU部署
训练得到的best.pt是PyTorch格式,在嵌入式设备或CPU环境下直接推理效率不够。常见做法是先导出为ONNX格式,再用ONNX Runtime推理。
python export.py \ --weights runs/train/traffic_sign_run1/weights/best.pt \ --include onnx \ --img 640 \ --opset 12导出后可以用下面的脚本做ONNX推理:
import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name img = cv2.imread("test.jpg") img_resized = cv2.resize(img, (640, 640)) blob = cv2.dnn.blobFromImage(img_resized, 1/255.0, (640, 640), swapRB=True) # 输入形状为 [1,3,640,640] outputs = session.run(None, {input_name: blob})ONNX模型不包含训练时的预处理逻辑,所以输入图像的归一化和通道顺序必须自己处理。cv2.dnn.blobFromImage的swapRB参数是因为OpenCV默认BGR顺序,而YOLOV5训练时用的是RGB。解码输出时,ONNX的输出形状是[1, 25200, 5+nc],25200是三个尺度特征图上锚框的总数,95是85(5+80类)在COCO预训练模型中的值,如果从yolov5s.pt从头训练4类,最后维度就是9,写解码逻辑时需要按实际类别数修改。
ONNX导出后可用onnxruntime-gpu或tensorrt进一步加速。TensorRT导出需要--include engine,生成过程会做层融合和量化,推理速度比ONNX Runtime快数倍,适合部署到jetson nano这类边缘设备。如果使用--half开启FP16半精度,速度还能继续提升,但需要注意个别算子可能不支持半精度,导出后要实测验证输出与FP32差距是否在可接受范围内。
5.3 基于训练结果的增量扩展:新增类别与转向检测
完成一个基础版本的交通标志识别系统后,通常会被要求增加夜间场景识别或新增类别。新增类别时不需要重新训练全部数据,推荐做法是保留已有模型权重,在原有权重基础上用新类别数据继续微调训练。将原有数据集中新类别的标注补上,修改yaml中的nc和names,直接运行--weights best.pt继续训练即可。这里有一个细节:原有权重输出层的维度与新配置不匹配,所以YOLOV5会自动裁剪掉最后一层重新初始化。这种增量训练比从COCO权重冷启动快得多,而且旧类别知识不会遗忘太严重。
夜间场景的增强可以在推理阶段处理而不是重训模型。推理前用autoencoder或CLAHE做光照归一化,再把增强图送入模型。实测在TT100K夜间子集上,仅做CLAHE增强就能让AP提升3到5个百分点。如果追求极致效果,可以采用昼夜域自适应,用训练好的白天模型在少量夜间数据上做半监督teacher-student蒸馏,这已经超出毕设范畴,但作为延伸方向非常契合高分答辩的加分点。
本文还有配套的精品资源,点击获取