ARTICLE DETAIL

资讯详情

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

YOLOV5交通标志识别实战:从数据标注到模型部署全流程

YOLOV5交通标志识别实战:从数据标注到模型部署全流程 简介这份资源面向计算机、人工智能相关专业的本科生与研究生提供一套可直接用于毕业设计、期末大作业或课程设计的交通标志识别检测完整方案基于YOLOv5深度学习框架实现适合具备Python基础、希望快速完成高分项目的学习者。压缩包共266个文件约423.33MB包含53个py源码文件、59个yaml配置、55张jpg与26张png图像样本、10个pt权重文件以及csv训练日志、sh脚本、Dockerfile部署文件等覆盖数据、训练、推理与部署全流程。目前已有105人学习下载。代码注释详尽新手也能看懂项目经过严格调试下载后简单部署即可运行界面美观、功能完善可直接作为毕设或课程设计提交具有较高的实际应用与参考价值。1. 交通标志识别这个题目为什么YOLOV5依然是性价比最高的起点做过交通标志识别的人都有一个共同体会数据集里的限速牌、禁令标志在白天清晰可见一到夜间或者强逆光场景模型就开始集体翻车。这不是模型本身的问题而是交通标志检测这个任务天然带着几个硬骨头——目标尺寸跨度大从远处的小三角到近处占半屏的圆形标志、类别不均衡限速40和限速120的样本量可能差十倍、背景干扰强广告牌、路灯、车身贴纸都容易被误检。YOLOV5在这个任务上之所以仍然是大多数人的第一选择原因很实际它的工程化程度足够高从数据标注格式转换、超参数配置、训练中断恢复到模型导出部署整条链路都有成熟的脚本支撑。你不需要自己去写数据加载器也不需要从零搭训练框架。对于毕业设计或者中小型落地项目来说这意味着你可以把精力放在数据质量和场景适配上而不是跟框架本身较劲。这篇文章面向的是手里已经有一批交通标志图片、想跑通检测链路的人。不管你是用公开数据集还是自己拍的下面这套流程从环境配置到训练调参到推理验证都能直接复用。我默认你用的是Linux或者WSLWindows下大部分命令也能跑但路径和依赖会多一些玄学问题。2. 从零把YOLOV5训练环境跑起来conda、依赖与目录结构2.1 环境配置的版本选择与conda创建YOLOV5对PyTorch版本有一定要求太新的版本反而容易出兼容问题。我一般会锁定在PyTorch 1.13到2.0之间CUDA用11.7或11.8这个组合在大多数显卡驱动下都能稳定跑。如果你用的是30系或40系显卡CUDA 11.8是底线否则可能识别不到GPU。# 创建独立环境避免和系统Python冲突 conda create -n yolo_traffic python3.9 -y conda activate yolo_traffic # 安装PyTorch以CUDA 11.8为例 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 验证GPU是否可用 python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))这段命令的逻辑很直接先建一个干净的Python 3.9环境然后装指定版本的PyTorch。最后那行验证很关键如果输出是False后面训练会默认走CPU速度慢到你想砸键盘。参数上python3.9是我踩过坑之后固定的版本3.10以上在某些旧版依赖上会报错。2.2 拉取YOLOV5源码与安装依赖git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt里包含了numpy、opencv-python、matplotlib、pyyaml、tqdm等核心依赖。安装完之后建议跑一下自带的检测脚本验证环境python detect.py --weights yolov5s.pt --source data/images/如果能看到输出图片里画了框说明基础环境没问题。这一步很多人会卡在opencv的版本冲突上如果报cv2相关的错单独执行pip install opencv-python4.8.0.74通常能解决。2.3 交通标志项目的目录组织方式YOLOV5默认的数据集目录结构是固定的不要随意改。我一般会在yolov5同级建一个datasets文件夹结构如下datasets/ traffic_sign/ images/ train/ # 训练集图片 val/ # 验证集图片 labels/ train/ # 训练集标注txt val/ # 验证集标注txt图片和标注文件必须同名只是后缀不同。比如images/train/0001.jpg对应labels/train/0001.txt。标注格式是YOLO格式每行一个目标内容是类别索引 中心x 中心y 宽度 高度全部归一化到0到1之间。如果你的原始标注是VOC的XML格式需要先转换这个后面会讲。3. 交通标志数据集的准备与YOLO格式转换从原始标注到可训练数据3.1 交通标志数据集的类别定义与标注规范交通标志的类别定义直接决定了模型能识别什么。国内常见的做法是参照GB 5768标准把标志分成警告、禁令、指示、指路等大类再细分成具体标志。但实际做检测的时候我建议不要一上来就分五六十类那样每类样本太少模型根本学不好。比较务实的做法是先合并成10到20类比如限速、禁止通行、禁止停车、注意行人、注意儿童、直行、左转、右转、人行横道、停车让行。这样每类至少有几百个样本训练出来的模型才有实用价值。标注的时候有几个血泪经验第一截断的标志也要标只要能看到一半以上就标出来否则模型在边缘场景会漏检第二模糊到人眼都认不出的不要标那是噪声第三同一张图里如果有多个标志每个都要单独标一行。3.2 从VOC XML到YOLO txt的转换脚本如果你拿到的数据集是VOC格式每个图片对应一个XML文件里面记录了bounding box的坐标。转换的核心逻辑是读XML里的xmin、ymin、xmax、ymax算出中心点和宽高再除以图片的宽高做归一化。import xml.etree.ElementTree as ET import os from PIL import Image # 类别映射表根据你的数据集实际情况修改 class_map { speed_limit: 0, no_entry: 1, no_parking: 2, pedestrian: 3, children: 4, go_straight: 5, turn_left: 6, turn_right: 7, crosswalk: 8, stop: 9 } def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) 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() # 获取图片尺寸用于归一化 img_name xml_file.replace(.xml, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue with Image.open(img_path) as im: w, h im.size lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化中心点和宽高 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_file os.path.join(out_dir, xml_file.replace(.xml, .txt)) with open(out_file, w) as f: f.write(\n.join(lines)) convert_voc_to_yolo(datasets/voc_xml, datasets/images, datasets/labels)这段脚本的关键点在于class_map你必须根据自己的数据集类别手动维护这个映射。归一化的时候保留6位小数就够了再多没必要。如果某个XML里没有任何有效类别就不生成对应的txt文件避免空标注干扰训练。3.3 数据集划分与data.yaml配置转换完之后要划分训练集和验证集一般按8比2或者9比1。划分的时候要保证每个类别在验证集里都有样本不能随机分完之后发现某个类别验证集里一个都没有。import os, random, shutil def split_dataset(img_dir, label_dir, out_base, ratio0.2): imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(imgs) val_count int(len(imgs) * ratio) val_imgs imgs[:val_count] train_imgs imgs[val_count:] for subset, files in [(train, train_imgs), (val, val_imgs)]: os.makedirs(f{out_base}/images/{subset}, exist_okTrue) os.makedirs(f{out_base}/labels/{subset}, exist_okTrue) for f in files: shutil.copy(os.path.join(img_dir, f), f{out_base}/images/{subset}/{f}) label_f f.replace(.jpg, .txt) src_label os.path.join(label_dir, label_f) if os.path.exists(src_label): shutil.copy(src_label, f{out_base}/labels/{subset}/{label_f}) split_dataset(datasets/images, datasets/labels, datasets/traffic_sign)划分完之后需要写一个data.yamlYOLOV5训练时靠这个文件找到数据和类别名path: /home/user/datasets/traffic_sign train: images/train val: images/val nc: 10 names: [speed_limit, no_entry, no_parking, pedestrian, children, go_straight, turn_left, turn_right, crosswalk, stop]nc是类别数names的顺序必须和class_map里的索引一一对应错一个位置整个训练就废了。这个文件我一般放在yolov5/data目录下命名为traffic_sign.yaml。4. 用YOLOV5训练交通标志模型超参数怎么设、训练怎么盯4.1 选择预训练权重与训练命令拆解YOLOV5提供了s、m、l、x四个规模的预训练权重。交通标志检测这个任务我建议从yolov5s.pt开始如果精度不够再换yolov5m.pt。s版本在单卡2080Ti上大概两三个小时能跑完100轮m版本要翻倍。python train.py \ --data data/traffic_sign.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img-size 640 \ --device 0 \ --workers 4 \ --project runs/train \ --name traffic_v1每个参数都有实际意义epochs是训练轮数100轮对大多数交通标志数据集够用了batch-size取决于显存8G显存用1612G以上可以上32img-size是输入分辨率640是默认值如果你的标志普遍很小可以调到1280但显存占用会翻四倍workers是数据加载线程数设成CPU核心数的一半左右比较稳。4.2 交通标志场景下的关键超参数调整YOLOV5的默认超参数在COCO上调得比较好但交通标志有几个特殊性需要针对性调整。这些参数在data/hyp.scratch.yaml里参数默认值建议值调整原因lr00.010.005交通标志数据集通常比COCO小学习率低一点更稳lrf0.010.05最终学习率不要降得太狠保留微调能力mosaic1.00.5马赛克增强对交通标志有时会引入不真实场景降低概率scale0.50.3缩放增强幅度降低避免小标志变得更小fl_gamma0.01.5开启Focal Loss缓解类别不均衡Focal Loss这个参数特别值得说。交通标志数据集里限速类样本往往占了一半以上而注意儿童这种可能只有几十张。fl_gamma设成1.5能让模型更关注难分类的少数类实测在类别不均衡的数据集上mAP能提升3到5个点。4.3 训练过程中的指标解读与中断恢复训练启动后控制台会输出每一轮的box_loss、obj_loss、cls_loss和mAP。box_loss是边界框回归损失obj_loss是目标置信度损失cls_loss是分类损失。正常情况下三个loss都应该稳步下降如果某个loss震荡剧烈或者不降说明学习率可能太大了。mAP0.5是主要参考指标交通标志检测一般能到0.85以上算不错。如果mAP在某个值附近卡住不涨了可以检查一下验证集里是不是有标注错误的样本。训练中断了不要慌YOLOV5支持断点续训python train.py \ --data data/traffic_sign.yaml \ --weights runs/train/traffic_v1/weights/last.pt \ --epochs 100 \ --resume--resume会自动读取last.pt里保存的优化器状态和epoch数从断的地方继续跑。注意--weights要指向last.pt而不是best.ptbest.pt只保存了模型权重没有优化器状态。5. 交通标志识别模型训练中的避坑与排查记录5.1 训练loss正常但mAP始终为0现象训练日志里box_loss和obj_loss都在降但mAP一直是0或者极低。原因最常见的是data.yaml里的nc和names数量对不上或者标注文件里的类别索引超出了nc范围。还有一种情况是图片路径和标注路径不匹配YOLOV5找不到标注文件把所有目标都当成了背景。解决先检查data.yaml的nc是否等于names列表长度。然后随便打开一个标注txt看第一列的数字是否在0到nc-1之间。最后确认images和labels的目录结构是否严格对应可以用ls datasets/traffic_sign/images/train | head和ls datasets/traffic_sign/labels/train | head对比文件名。5.2 显存溢出导致训练中途崩溃现象训练跑了几十轮之后突然报CUDA out of memory之前一直好好的。原因YOLOV5的mosaic增强会随机拼接四张图拼接后的图像尺寸不固定偶尔会生成比预期更大的张量。如果显存本来就吃紧就会在某个batch上崩掉。解决把batch-size降一档比如从16降到12。或者在hyp.scratch.yaml里把mosaic的概率从1.0降到0.5减少大张量出现的频率。还有一个办法是开启--rect参数用矩形训练代替方形训练能省一些显存。5.3 模型对某些类别完全漏检现象训练完之后推理发现限速标志检测得很好但注意儿童几乎一个都检不出来。原因类别不均衡。如果注意儿童的样本只有几十张而限速有几千张模型会倾向于把注意儿童也预测成限速或者直接忽略。解决三个方向。第一对少数类做数据增强复制粘贴到不同背景上第二在hyp里调高fl_gamma用Focal Loss压制多数类第三在data.yaml里给少数类加权重不过YOLOV5原生不支持类别权重需要改loss计算部分。5.4 推理时框的位置偏移明显现象模型能检测到标志但框的位置总是偏左或者偏上和实际目标对不齐。原因标注的时候坐标算错了。常见的是VOC转换时忘了减1因为VOC的坐标是从1开始的而YOLO是从0开始的。或者归一化的时候除错了宽高。解决写一个可视化脚本把标注框画到原图上肉眼检查几十张。如果发现系统性偏移就是转换公式的问题。VOC转YOLO的正确公式是cx (xmin xmax) / 2.0 / w注意xmin和xmax都不需要减1因为VOC的坐标本身就是像素索引。5.5 验证集mAP很高但实际场景效果差现象验证集上mAP到了0.9但拿实际道路视频去测漏检和误检都很严重。原因验证集和训练集来自同一个数据分布如果数据集本身场景单一比如全是白天高速场景模型就没有见过夜间、雨天、城市道路这些场景。解决在数据集里补充不同场景的样本至少要有白天、夜间、晴天、雨天各一些。如果实在没有条件采集可以用一些开源的交通标志数据集做补充但要注意类别定义要统一。6. 推理部署与效果验证从单张图片到视频流的完整链路6.1 用detect.py做单张图片和批量推理训练完之后权重保存在runs/train/traffic_v1/weights/best.pt。推理命令很直接python detect.py \ --weights runs/train/traffic_v1/weights/best.pt \ --source test_images/ \ --img-size 640 \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name traffic_test--conf-thres是置信度阈值交通标志检测我一般设0.4比默认的0.25高一些因为交通标志场景里误检的代价比漏检高。--iou-thres是NMS的IoU阈值0.45是比较通用的值。--save-txt会把检测结果保存成YOLO格式的txt方便后续做统计分析。6.2 视频流推理与帧率优化如果要处理视频或者摄像头流把--source改成视频文件路径或者摄像头编号比如0。但直接跑视频会发现帧率很低因为YOLOV5默认对每一帧都做完整推理。python detect.py \ --weights runs/train/traffic_v1/weights/best.pt \ --source test_video.mp4 \ --img-size 640 \ --conf-thres 0.4 \ --device 0优化帧率的几个方向第一用yolov5s而不是m或ls版本在2080Ti上单帧推理大概10毫秒第二开启半精度推理加--half参数能提速30%左右第三如果场景变化不快可以隔帧检测中间帧用跟踪算法补。6.3 用混淆矩阵和PR曲线验证模型真实水平YOLOV5训练完会自动生成混淆矩阵和PR曲线在runs/train/traffic_v1/目录下。混淆矩阵能看出哪些类别容易混比如限速40和限速50经常互相误判这是正常的因为两个标志长得太像。PR曲线看的是每个类别的precision和recall平衡点。如果某个类别的曲线明显低于其他类别说明这个类别的样本质量或者数量有问题。我一般会重点关注recall因为交通标志漏检的后果比误检严重。6.4 导出ONNX模型做跨平台部署如果要把模型部署到非Python环境比如C或者移动端需要导出ONNX格式python export.py \ --weights runs/train/traffic_v1/weights/best.pt \ --include onnx \ --img-size 640 \ --batch-size 1 \ --dynamic--dynamic参数让导出的ONNX支持动态batch和动态输入尺寸部署时更灵活。导出之后可以用onnxruntime验证一下import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name # 构造一个模拟输入1张图3通道640x640 dummy np.random.randn(1, 3, 640, 640).astype(np.float32) outputs sess.run(None, {input_name: dummy}) print(outputs[0].shape) # 应该是(1, 25200, 15)25200是候选框数15是510类输出shape里的15等于5加类别数5是cx、cy、w、h、conf五个值。如果类别数是10输出就是15。这个验证能确认模型导出没有丢东西。6.5 一个容易被忽略的技巧用TTA提升小目标召回交通标志里的小目标远处的限速牌是最容易漏检的。YOLOV5支持TTATest Time Augmentation推理时对图片做多种变换再合并结果python detect.py \ --weights runs/train/traffic_v1/weights/best.pt \ --source test_images/ \ --augment--augment会做水平翻转、缩放等变换然后把所有结果做NMS合并。实测在小目标上recall能提升5到8个点代价是推理速度慢三倍左右。如果对实时性要求不高这个技巧很值得开。我自己做交通标志项目最大的教训是不要等到模型训练完了才去看数据。标注阶段花一个小时肉眼检查一百张图比训练完之后调三天参都管用。数据里的系统性错误任何超参数都救不回来。希望帮到你。本文还有配套的精品资源点击获取
返回列表