ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv5交通标志检测:从数据集到部署的全流程实战解析

YOLOv5交通标志检测:从数据集到部署的全流程实战解析 简介基于YOLOv5的交通标志物检测完整项目主要面向正在准备课程设计、期末大作业的计算机专业学生以及希望上手目标检测实战的深度学习学习者。项目包含全部开发源码、已经训练完成的权重模型与完整的训练测试数据环境依赖配置好之后可以立即运行也支持继续训练和调优。整套代码在本机环境中经过严格调试稳定性有保障。资源包内共有两百六十六个文件除了核心的Python脚本还提供YAML参数配置文件、JPG和PNG格式的样本图片、PT格式的预训练权重、Shell运行脚本、XML标注文件、CSV训练记录以及Dockerfile等可满足从数据准备、模型训练到效果评估的完整流程。压缩包整体大小约四百二十三兆字节目录组织清晰方便按功能模块查找使用。目前该资源已有三百六十二人学习下载作为高分的期末项目其设计思路和实现方式对于希望快速搭建交通标志检测系统的同学具有很高的参考价值可以直接复现结果也可以在此基础上进行迁移与改进。1. 为什么交通标志检测的打包项目几乎都选YOLOv5拿到一个名为“YOLOv5交通标志物检测源码训练好的模型全部数据”的压缩包第一反应不应该是解压后直接跑detect.py而是先理解这套组合拳里每一块承担什么角色。交通标志检测天然适合从开源检测框架起步标志类别固定、目标尺寸偏小、场景光照变化大对模型的数据组织能力和训练迭代效率要求很高。在这类任务上YOLOv5的生态完整度比多数同期模型更占优势——它自带数据增强策略、锚框自适应计算和成熟的训练管线从标注数据到产出best.pt权重整个闭环不至于让新手卡在某个中间环节。本文按“数据组织 → 训练 → 推理 → 精度调优 → 场景落地”的顺序把这类项目背后真正需要你掌握的技术点逐一讲透。适用人群是准备做毕设、课设或者想快速验证交通场景检测方案的开发者有经验的工程师也可以直接跳到第 3 章看参数边界和踩坑点。2. 全部数据怎么组织YOLOv5 交通标志数据集的目录与标注格式拿到“全部数据”后最常见的翻车现场不是模型训练不起来而是Dataset not found或AssertionError: Label class x exceeds nc。要避免这个问题得先把数据目录和标注格式对齐到 YOLOv5 的约定上。实际项目里数据集来源五花八门可能是 GTSRB 转出来的可能是 TT100K 筛出来的也可能是自己用标注工具画出来的但交付给你的训练包目录结构通常逃不出下面这套模板。2.1 YOLOv5 训练所需的数据目录结构YOLOv5 通过datasets前缀定位图片和标签代码你的数据目录需要严格区分images与labels且两个子目录下必须用相同的主文件名一一对应。一个标准的交通标志数据集目录如下datasets/ ├─ traffic/ │ ├─ images/ │ │ ├─ train/ │ │ │ ├─ 00001.jpg │ │ │ ├─ 00002.jpg │ │ ├─ val/ │ │ │ ├─ 01001.jpg │ ├─ labels/ │ │ ├─ train/ │ │ │ ├─ 00001.txt │ │ │ ├─ 00002.txt │ │ ├─ val/ │ │ │ └─ 01001.txt │ └─ traffic.yamltraffic.yaml是数据集的“总入口”训练和推理都会先读它。内容通常是这样path: ../datasets/traffic # 数据集根目录路径 train: images/train # 训练图片相对 path 的路径 val: images/val # 验证图片相对 path 的路径 nc: 5 # 类别总数 names: [speed_limit, no_entry, warning, yield, stop] # 按编号 0-4 排列很多打包项目里其实没有path这一项用得是老版本写法train: ../datasets/traffic/images/train。区别在于新版 YOLOv5 要求path与train分开写老写法在新版本上会提示路径拼接错误。拿到任何数据集第一步打开 yaml 文件检查train、val指向的实际路径是否存在。注意val目录是训练的“试金石”至少放 200 张图且分布要接近真实场景。有些精简数据集把绝大多数样本塞进trainval只有三五十张训练结束时 loss 曲线看着正常泛化到新图片上却一塌糊涂。这个锅通常不在模型而在验证集规模。2.2 标注格式与类别编号VOC XML 转 YOLO TXT 的常见做法YOLOv5 的标签文件是纯文本每行代表一个检测框五个数字依次是class_id、x_center、y_center、width、height。注意这四个位置值全部是相对图片宽高的归一化比例不是像素值。比如一张 640×480 的图片里有一个停车标志框的像素坐标为左上角 (100, 200)、右下角 (300, 400)那么对应的一行标签是4 0.3125 0.625 0.3125 0.41667计算过程中心 X 为 (100300)/2200200/6400.3125中心 Y 为 (200400)/2300300/4800.625宽为 200200/6400.3125高为 200200/480≈0.41667。很多公开数据集的标注是 Pascal VOC 格式也就是 XML 文件把它转成 YOLO TXT 是这类打包项目里必然要处理的一步。常见做法是写一个 Python 脚本批量转换import glob import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_file, class_names, output_dir): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name xml_file.replace(.xml, .txt) with open(txt_name, w) as f: for obj in root.iter(object): cls obj.find(name).text if cls not in class_names: continue cls_id class_names.index(cls) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.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 f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) class_names [speed_limit, no_entry, warning, yield, stop] for xml_file in glob.glob(xml/*.xml): convert_voc_to_yolo(xml_file, class_names, labels/)转换时一定要留意两点。第一类别名称映射也就是no_entry这类中文表达可能是“禁止驶入”不同数据集的命名不统一脚本里class_names的顺序必须和 yaml 里的names一致。第二如果 XML 里存在被遮挡或不完整的标志框建议直接过滤掉否则训练时模型会反复学习残缺目标最终在真实场景中对遮挡标志产生错误置信度。不少打包项目会直接给你转换好的 TXT但你在验收数据时会发现类别编号与标志类型对不上。最稳妥的自查方式是随机挑几张图把标签画回图片上人工对照。2.3 标签统计一个脚本定位数据分布与空标签问题拿到现成数据不要急着训练先跑一个标签统计脚本。这个脚本能暴露三类问题某个类别样本数过少、某张训练图没有对应标签、标签文件里出现越界坐标。import os import numpy as np from collections import Counter label_dir datasets/traffic/labels/train cls_counter Counter() empty_files 0 bad_lines 0 for txt in os.listdir(label_dir): path os.path.join(label_dir, txt) if os.path.getsize(path) 0: empty_files 1 continue with open(path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_lines 1 continue cls_id int(parts[0]) cls_counter[cls_id] 1 # 检查归一化坐标是否在 0-1 范围 vals [float(v) for v in parts[1:]] if any(v 0.0 or v 1.0 for v in vals): bad_lines 1 print(类别分布:, dict(cls_counter)) print(空标签文件数:, empty_files) print(异常行数:, bad_lines)提示空标签文件尽量不要直接删。如果训练集中删掉这张图必须同步删掉 images 下对应的 jpg否则训练时会报“检测到标签文件不存在但图片存在”的警告。交通标志数据集中人工标注时漏标很常见保留空标签比制造数据不一致更安全。输出里如果看到某个类别只有个位数样本这不会是“高分项目”会有的状态你需要回到第 5 章处理类别不平衡。如果bad_lines不为零定位到对应文件修改大概率是小数点和负号格式问题。3. 用源码训练 YOLOv5 模型命令、参数与训练流程数据就绪后核心工作就是选对预训练权重、调对 batch size 和超参数。YOLOv5 开源了官方源码主干绝大多数打包项目直接基于它二次开发因此训练入口就是你熟悉的train.py。想从零复现“训练好的模型”这个结果命令本身不复杂复杂的是理解每个参数在这类小目标任务里该往哪个方向改。3.1 最小可复现的训练命令进入 YOLOv5 源码根目录后训练一个交通标志检测模型最基础的命令是python train.py --data traffic.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 100 --device 0--img 640表示训练时把图片统一缩放到 640×640。交通标志是小目标这个值不要低于 640实际情况中提高到 960 或 1280 反而更常见代价是显存占用成倍增加。--weights指定预训练权重yolov5s.pt是最均衡的起点COCO 上预训练过的特征提取部分能大幅缩短收敛时间比从--weights 随机初始化强得多。--batch 16取决于显卡显存8GB 显存跑 640 分辨率用这个值基本是极限显存不够时优先降低--img其次才动 batch。训练启动后终端会实时输出每轮的box_loss、cls_loss、dfl_loss以及mAP0.5和mAP0.5:0.95。不要只盯着mAP0.5:0.95这一个指标交通标志这类小目标在 IoU 阈值升高后 AP 掉得很快重点观察mAP0.5是否稳定在 0.85 以上。3.2 针对交通标志修改的超参数锚框、学习率与数据增强YOLOv5 的超参数配置写在data/hyps/hyp.scratch-low.yaml里训练时通过--hyp指定。对交通标志检测影响最大的有三处。参数默认值交通标志场景建议作用说明lr00.010.005~0.01初始学习率小数据集用大学习率容易震荡mosaic1.01.04 图拼接增强提升小目标上下文理解anchor_t4.02.0~3.0锚框适配阈值标志宽高比单一降低该值让锚框更贴目标hsv_h/hsv_s/hsv_v0.015/0.7/0.4各减半交通标志颜色是强特征过度色彩增强会让红/蓝标志失真fliplr0.50.0禁止水平翻转否则“限速 40”和“限速 80”的左右对称文字会混淆mosaic是 YOLOv5 在 COCO 上取得好成绩的关键增强手段训练时将四张图随机裁剪拼接成一张等于让模型在单位训练时间看到更多小目标。但到了训练后期尤其是最后 20 个 epoch建议把mosaic关闭。原因是拼接出来的画面包含大量截断的标志会让模型对完整标志的边缘感知变差。anchor_t控制锚框与真实框的匹配程度。YOLOv5 默认会使用 k-means 在训练开始时重新计算数据集的锚框你可以把anchors单独写进模型配置也可以让训练器自适应。交通标志通常近似方形默认的 3×3 锚框组合里很多宽高比是给行人、车辆用的anchor_t调小后能提前过滤掉不匹配的锚框减轻分类分支的负担。3.3 中途续训resume 与 last.pt 的正确用法训练到第 70 个 epoch 时崩了或者你想把--epochs从 100 加到 150没必要重头开始。YOLOv5 每轮结束都会把当前权重存到runs/train/exp/weights/last.pt里面同时保存了优化器状态、学习率、epoch 号和随机种子。续训命令是python train.py --weights runs/train/exp/weights/last.pt --resume--resume后面不用带任何路径它会自动从runs/train/exp里找到最新的last.pt并恢复训练超参数和数据配置。但有一个陷阱如果训练过程中你修改了traffic.yaml或hyp文件--resume不会加载这些修改它只认权重里保存的旧配置。想改参数继续训练正确做法是不用--resume直接用--weights runs/train/exp/weights/last.pt加新的--hyp和--epochs这样相当于在新起点上微调超参数修改才生效。3.4 训练产物的验收格式与质量判断训练结束后产物在runs/train/exp/目录里。weights/best.pt是验证集 mAP 最高的一轮权重weights/last.pt是最后一轮权重。二者选哪个不绝对best.pt的 mAP 高但可能过拟合验证集last.pt更接近模型在新场景的泛化表现在交通标志这类数据分布相对固定的任务上我一般优先用best.pt。验收时不要只看 mAP 数值打开results.png看val/box_loss和val/cls_loss曲线。如果验证 loss 在后期不降反升说明过拟合如果曲线还处于陡降状态就停在第 100 轮说明训练不足可以续训练到 200 轮。这比任何单点 mAP 都更能说明训练质量。4. 加载训练好的模型做交通标志推理detect.py 与 Python 封装训练产物最终要落到“拿一张图或一段视频输出检测框”这个动作上。YOLOv5 的推理链路分两层命令行工具detect.py适合快速验证和批处理Python 接口适合嵌入到已经标好路径的摄像头或 Web 服务中间层。两个路径都用同一个权重文件但参数处理逻辑不同。4.1 detect.py 的最小推理命令与可调参数用训练好的权重对单张图片推理python detect.py --weights runs/train/exp/weights/best.pt \ --source data/images/test.jpg \ --conf-thres 0.4 --iou-thres 0.45 \ --save-txt --project runs/detect --name traffic_test--conf-thres是置信度阈值0.4是保守值。交通标志场景里如果你发现召回率低、标志被漏判把--conf-thres降到0.25如果误检多、路牌广告牌被框出来升到0.55。--iou-thres是 NMS 的 IoU 阈值两个高度重叠的框如果 IoU 超过这个值就合并。交通标志目标小且稀疏一般不会出现密集重叠0.45够用。--save-txt会让检测结果以 YOLO 格式存成labels/*.txt每行一个检测框输出直接作为后期统计程序的数据源。不带这个参数时结果只画在图上适合人工看效果。4.2 用 Python 接口集成从单张图片到视频帧项目要交付“源码”意味着调用方可能是你写的 GUI 程序也可能是上位机脚本。YOLOv5 官方推荐基于torch.hub加载权重但更稳的是直接用源码里的DetectMultiBackend做推理封装。一个可用的视频帧检测函数如下import cv2 import torch from pathlib import Path model DetectMultiBackend(runs/train/exp/weights/best.pt, device0, dnnFalse) stride model.stride names model.names def detect_frame(frame, conf_thres0.4, iou_thres0.45): # 保持宽高比缩放 h, w frame.shape[:2] ratio min(640 / w, 640 / h) new_w, new_h int(w * ratio), int(h * ratio) resized cv2.resize(frame, (new_w, new_h)) # letterbox 填充 canvas np.full((640, 640, 3), 114, dtypenp.uint8) x_off (640 - new_w) // 2 y_off (640 - new_h) // 2 canvas[y_off:y_off new_h, x_off:x_off new_w] resized img torch.from_numpy(canvas).permute(2, 0, 1).float() / 255.0 img img.unsqueeze(0).to(cuda) pred model(img)[0] return pred, ratio, x_off, y_off这段代码没有走letterbox工具函数而是手动实现了同样的逻辑目的是让你看清坐标变换的过程。DetectMultiBackend会返回原始预测结果包含xyxy坐标、置信度和类别编号对角线格式需要手动把x_off/y_off减回去再除以ratio才能映射到原图坐标。实际项目里直接from utils.augmentations import letterbox就能省掉这段手工代码但理解内部逻辑对排错很有帮助。提示视频流检测时模型推理时间只占帧处理的一部分。如果在摄像头场景下帧率很低不要急着换大模型先检查是否每帧都做了图像缩放和通道转换。先用cap.read()拿到帧再进入推理函数中间不要做任何不必要的cv2.imshow颜色空间转换放最后。4.3 推理结果不理想的排查顺序模型输出和预期差距大时排查顺序比调参数更重要。第一步确认数据类别映射模型输出的1是stop还是no_entry建一个字典打印names[cls_id]避免代码逻辑错了还盯着conf-thres改。第二步对比训练时的 mAP训练集上 mAP 很高、真实图片上很差说明过拟合这时的有效手段不是调阈值而是拿更多现场图片加入训练。第三步才是调置信度。很多“模型不准”的假象其实出在推理脚本的预处理和后处理上与权重无关。5. 交通标志检测的准确率瓶颈小目标、类别不平衡与误检往前走你会发现交通标志检测的性能上限不在模型结构而在三类工程问题标志在画面中占比太小、类别样本严重失衡、视觉相似的标志互相误检。这一章的处理手段直接决定你的“高分项目”在真实场景里能不能挺住。5.1 小目标漏检切图推理与输入分辨率的选择交通标志的典型尺寸是 32×32 到 64×64 像素而在 1920×1080 的原图中按 640 缩放后这类标志连 20×20 像素都不到正好落在 YOLOv5 最不擅长的小目标区间。两条有效路线提高推理分辨率或者切图后分别推理再合并结果。提高分辨率最省事。把--img从 640 提到 1280小目标的有效像素面积变大召回率通常能提升 3~5 个百分点。显存不够时切图推理# 将原图切成 2x2 网格分别送检再把框映射回原坐标 tiles [] for i in range(2): for j in range(2): x1, y1 j * 960, i * 540 x2, y2 x1 960, y1 540 tile frame[y1:y2, x1:x2] tiles.append((tile, x1, y1))切图后每块的标志相对尺寸变大小目标检测率提升明显。代价是重叠区域内的同一标志可能被两个 tile 重复检出最终合并时用 NMS 把重叠框合并掉。实际项目里切 2×2 就够切 4×4 会让标志跨块截断概率增大收益反而下降。但要注意提高输入分辨率会把训练/推理分布打偏。如果你训练时用 640推理时邪用 1280发现检测结果异常不要先怀疑模型回到train.py --img 1280重新训一个保证分辨率一致性再对比。5.2 类别不平衡限速标志占一半怎么办交通场景里“限速”标志数量远多于“禁止停车”“施工警告”这是数据采集的天然偏差。训练出来的模型会倾向把所有圆形标志预测为限速标志因为这么做分类 loss 最低。三个处理手段按性价比排序如下。方法操作位置效果小样本类别复制增强训练前复制少数类图片配轻微旋转缩放简单直接注意复制后的图片不能重复出现在验证集修改--cls损失权重代码里修改cls_loss的权重系数让分类分支更关注少数类但需要调参配合欠采样限速类每轮训练随机丢弃部分限速样本数据利用率低一般作为最后手段最常见做法是第一种。写一个脚本对“警告”和“停车让行”类别的图片做 ±15 度旋转、亮度抖动、随机裁剪扩充到接近限速标志数量。扩充后的样本注意不要让同一张原图的多个增强版本同时进训练集和验证集否则验证分数虚高。5.3 误检的根源相似外观与上下文缺失“停止”标志和“禁止驶入”标志都是红色圆形加白色元素这类成对误检很难用模型结构调整消除。误检多发生在两个条件同时满足时目标尺寸小导致纹理信息不足以及模型缺乏上下文判断能力。比如一个被遮挡大半的红色圆形既可能是不完整标志也可能只是红色招牌。工程化的做法是引入负样本。收集 200~300 张不含任何交通标志但包含大量红色圆形物体店铺招牌、灯笼、消防设备的图片把它们作为单独一类加入训练。如果不想改类别数可以只放进训练集不标注任何框让模型在背景分支上学到“这些不是标志”。这是克制误检最有效的手段比调 IoU 阈值实用得多。6. 把交通标志检测模型用到真实场景导出、视频流与嵌入式上报模型从.pt变成可用产品中间还差一步“部署形态选择”。交通标志检测落地最多的两个方向公共视频流实时分析以及边缘设备上的轻量化推理。这两条路对应完全不同的导出方式和推理配置。6.1 导出 ONNX 的取舍与命令边缘设备通常不直接跑 PyTorch先导出为 ONNX 格式再转其他推理后端是通用做法python export.py --weights best.pt --img 640 --batch 1 \ --include onnx --opset 12导出后检查模型的输入输出结构输入节点是images形状为[1, 3, 640, 640]输出节点包含 3 个尺度的检测头每个是[1, 3, 20, 20, 7]形状对应 3 个锚框、坐标 4 个、置信度 1 个、类别数 5 个。ONNX Runtime 里做 NMS 会比 PyTorch 版本多一层后处理代码你需要把三个输出头的坐标解码逻辑从utils/general.py里单独抽出来。提示--opset不要用太新的版本。边缘设备的推理框架往往只支持到 ONNX opset 12~13强行导 17 可能导致设备上报算子不支持。6.2 视频流检测的帧率策略接入摄像头视频流时核心矛盾是检测速度跟不上采集帧率。常见做法是检测与显示解耦采集线程每帧都读但只对每隔 N 帧送检一次检测结果缓存给显示线程。N 的值取决于检测耗时让检测频率维持在 8~12 fps 即可满足多数标志识别场景。# 伪代码结构主循环按帧率节流显示使用最近一次检测结果 while cap.isOpened(): ret, frame cap.read() if frame_count % 3 0: last_pred detect_frame(frame) overlay render_boxes(frame, last_pred) cv2.imshow(traffic_detect, overlay)检测频率降低后标志出现在两帧检测之间也不会被完全漏掉因为显示层用的是最近一次检测结果最坏情况只是框的位置滞后几十毫秒。这个结构比“每帧都检测但降低分辨率”的视觉体验好得多误检率也更低。6.3 边缘端上报条件与置信度门槛把检测结果上报到服务端时不要每个框都发。交通标志检测在下游系统里通常只需要“时间、类别、置信度、坐标”四个字段但上报策略要区分场景交通标志变更检测上报重复上报同一位置时做去重实时违规抓拍上报设更高的置信度门槛。一个通用的上报条件是类别在预设名单比如只报stop和no_entry且置信度高于 0.6且该类别在同一位置的累计帧数达到阈值。这个三重过滤能把误检率压低一个数量级。阈值具体设多少取决于你的模型在验证集上的 mAP 曲线在precision-recall曲线里找到 precision 开始明显下降的那个点附近取置信度会更合适。整个交通标志检测项目交付时能够讲清楚为什么选这个阈值、数据里哪些类别容易混淆、边缘端为什么只上报特定类别这比单纯贴出 99% 的 mAP 更能撑住“高分项目”这四个字。本文还有配套的精品资源点击获取
返回列表