ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

摩托车与行人目标检测实战:数据清洗、YOLO格式转换与训练避坑指南

摩托车与行人目标检测实战:数据清洗、YOLO格式转换与训练避坑指南 简介摩托车与行人目标检测数据集面向目标检测、工业视觉与交通AI应用场景提供道路监控视角下的摩托车与行人两类目标的完整标注数据。所有图片均采用YOLO格式的归一化边界框标注可直接用于YOLOv5/v7/v8等主流框架训练适合交通监控、自动驾驶感知、智慧城市管理等项目开发与算法验证。资源包共2000个文件压缩包大小62.91MB包含1095个txt标注文件、903张jpg实景图片、1个yaml配置文件及1个docx数据说明文档目录结构清晰标注与图片一一对应并附有类别配置说明便于快速上手实验。目前已有124人学习使用。该数据集聚焦道路环境中的摩托车与行人两大关键目标涵盖多种光照、视角和密集程度标注精准且场景针对性强能有效支持模型训练和行人-摩托车互动行为研究减少数据采集与清洗成本是构建道路感知系统的实用基础资源。1. 摩托车与行人目标检测数据集.zip下载容易用明白难拿到“摩托车与行人目标检测数据集.zip”这个压缩包的人多半是正在做交通场景识别的工程师。两轮车和行人混行是城市路口监控、道路治理、外卖平台安全预警里绕不开的检测需求。这个数据集解决的问题就是直接给你一套带标注的摩托车和行人图片省掉自己采集视频、逐帧打标的两三周时间。适合的读者是手里有训练环境缺的是干净、能直接丢给模型的监督数据或者想快速验证一下 YOLO 系检测模型在混行场景下能到什么精度。先说结论这类交通目标检测数据集难点从来不在训练本身而在你解压之后花在数据体检、格式转换和数据清洗上的时间经常比训练还长。下面按这个顺序把整条路走一遍。2. 解压与体检先别急着训练把数据看明白再动手拿到 zip 第一件事不是解压是看。很多人在这一步省了几分钟后面赔进去几小时。压缩包是流转来的来源可能是同事、网盘或者某个技术论坛压缩工具、压缩参数、文件编码都是未知数先花两分钟摸清底细再解压是成本最低的保险。2.1 用 unzip 和 7z 完整放出压缩包伪加密和解压中断在 Linux 服务器上我习惯先用unzip -l看一眼压缩包内部结构不实际解压只列文件清单。这样能快速判断数据组织方式是分好了 images 和 labels 两个目录还是散成一地文件一眼就能看出来。unzip -l motorcycle_pedestrian.zip | head -30-l是 list 模式只列内容不解压head -30控制只看前 30 行避免图片数量大时刷屏。列出来的清单里如果文件名带中文或空格后面解压时建议加-O参数指定编码否则文件名可能乱码导致图片和标签对不上。真正解压时我一般优先用 7z 而不是 unzip。原因很实际Windows 端用 WinRAR 或好压压出来的 zip偶尔会带一些 unzip 不认的压缩算法或文件属性提示unsupported compression method这时候换成 7z 基本能解。7z x motorcycle_pedestrian.zip -o./datasetx表示解压并保留目录结构-o指定输出目录注意-o后面不能有空格直接跟路径。7z 会自动创建目录不用先 mkdir。还有一种情况是提示要密码也就是热词里常说的 zip 伪加密。压缩包本身没加密但文件头的加密标志位被置位了解压工具误判成加密文件。遇到这种先别放弃用 7z 跑一遍完整性测试7z t motorcycle_pedestrian.zipt是 test 模式只校验压缩包完整性不实际解压。如果测试通过但解压仍然要密码说明是伪加密的可能性很大。常见做法是用zip -FF尝试修复文件头或者干脆换一个解压工具重试很多伪加密在不同实现下表现不一样。这里要提醒一句如果压缩包是别人刻意加密后发出来的那不属于伪加密别花时间绕直接找对方要密码。解压之后还要顺手确认一下文件数量是完整的。一个很笨但有效的办法是看解压前后的字节数对比du -sh看目录总大小和 zip 列表里显示的总大小差太多说明中间有文件没解出来这时候重新解压一遍比后面训练时排查莫名其妙的数据错误要省事得多。2.2 目录结构与标注格式识别VOC、COCO 还是 YOLO 底子解压完先别急着写训练脚本先看目录结构。用tree命令列两层基本就能判断数据集是哪种常见格式。tree -L 2 dataset目标检测数据集在市面上流转的无非三种底子特征非常明显。VOC 格式是 JPEGImages 目录放图片、Annotations 目录放 XML 标注文件COCO 格式是 images 目录放图片、annotations 目录里躺着一个大的 JSON 文件YOLO 格式则是 images 和 labels 两个平级目录labels 里每个图片对应一个同名 txt。三种格式的关键差异整理成表更清楚格式图片目录标注文件标注内容VOCJPEGImagesAnnotations/*.xmlxmin, ymin, xmax, ymax 绝对像素坐标COCOimages/annotations/*.json单个 JSON 包含全部图片和标注信息YOLOimages/labels/*.txt每行 5 个数类别、中心点 xy、宽高归一化如果目录结构不明显直接打开一个标注文件看内容是最快的判定方式。XML 里出现xminyminxmaxymax标签就是 VOCJSON 里有images和annotations两个顶层字段就是 COCOtxt 里每行是0 0.45 0.32 0.21 0.18这种格式就是 YOLO。这个识别步骤决定了后面格式转换脚本怎么写别跳。顺带说一句标注工具。VOC 格式往往是 LabelImg 打出来的COCO 多是 labelme 或者脚本转换来的YOLO 格式最常见的是 LabelImg 选了 YOLO 模式直接输出。拿到数据集之后看一眼标注文件是哪来的能帮你预判一些典型问题比如 LabelImg 打出来的 XML 经常存在坐标越界和空框。2.3 用一段 Python 给数据集做体检类别、样本数、标签完整性在不确定数据干净程度的情况下直接训练后面排查 loss 不收敛会花掉几倍的时间。所以体检这一步是整篇文章里性价比最高的动作。假设你手里是 VOC 格式写一段脚本统计类别的样本数from pathlib import Path import xml.etree.ElementTree as ET annotations_dir Path(dataset/Annotations) class_stats {} for xml_file in annotations_dir.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_stats[name] class_stats.get(name, 0) 1 for cls, count in sorted(class_stats.items()): print(f{cls}: {count})这段脚本遍历所有 XML 文件把每个object里的name累加统计。跑完会看到两类信息一是类别到底有几种是不是只有摩托车和行人还是混进了汽车、自行车、电动车二是各类别的样本数量是否均衡如果行人一万个框、摩托车只有一百个那训练时要做针对性处理。这里要特别提醒很多数据集标注质量参差同一类目标可能被标成motorbike、motorcycle、bike三种名字需要先用这个脚本发现再合并成统一类别表。类别统计完还要检查图片文件的完整性以及标注和图片的对应关系。这一步用一段独立脚本from pathlib import Path from PIL import Image images_dir Path(dataset/JPEGImages) corrupted [] for img_path in images_dir.glob(*.jpg): try: with Image.open(img_path) as im: im.load() except Exception: corrupted.append(img_path.name) print(f损坏图片数: {len(corrupted)}) for name in corrupted[:10]: print(name)Image.open()只读文件头im.load()才会真正解码像素数据所以文件头正常但内容截断的图片在这里会现出原形。体检脚本跑完你基本就知道这批数据能不能直接用了别省这一步。3. 把数据转成 YOLO 格式转换脚本与四个边界坑体检完的数据不管原来是什么格式最终大概率要转成 YOLO 格式才能喂给训练脚本。YOLO 系目标检测模型的默认输入格式就是归一化的 txtVOC 和 COCO 都要经过一步转换。这一步逻辑不复杂但边界情况极多是整条链路里翻车率最高的环节。3.1 三类格式的坐标差异与转换公式VOC 的 XML 里存的是绝对像素坐标左上角xmin, ymin和右下角xmax, ymaxYOLO 需要的是框中心点的归一化坐标和归一化宽高。假设图片宽度为 W高度为 H转换公式是x_center ((xmin xmax) / 2) / W y_center ((ymin ymax) / 2) / H box_w (xmax - xmin) / W box_h (ymax - ymin) / HCOCO 的 JSON 里bbox字段存的是[xmin, ymin, width, height]和 VOC 的唯一区别是宽高不用做减法直接拿width和height除以图片宽高就得到归一化宽高。中心点的算法和 VOC 完全一致。理清这个换算关系后转换脚本只是套公式的问题。3.2 转换脚本VOC 到 YOLO 的完整代码以最常见的 VOC 转 YOLO 为例脚本核心部分是这样from pathlib import Path import xml.etree.ElementTree as ET # 类别表顺序即类别 ID训练时 data.yaml 的 names 要与之一致 CLASSES [motorbike, person] def voc_to_yolo(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASSES: # 出现未知类别时跳过并打印方便排查类别表遗漏 print(funknown class {cls_name} in {xml_path.name}) continue cls_id CLASSES.index(cls_name) 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) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 过滤无效框宽或高为 0 的标注没有训练价值 if box_w 0 or box_h 0: print(finvalid box in {xml_path.name}) continue yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) return yolo_lines这段函数接收 XML 路径和图片尺寸返回 YOLO 格式的文本行列表。CLASSES是类别映射表列表的顺序就是类别 ID这个顺序必须和后面 data.yaml 里的names完全一致否则模型输出头的第 0 类可能是自行车而不是摩托车。cls_name not in CLASSES的判断能帮你发现类别表遗漏比如数据里混进了truck脚本会打印出来而不是悄悄丢弃。转换完还要写回文件文件名要和图片严格同名只是扩展名不同。遍历部分如下annotations_dir Path(dataset/Annotations) labels_dir Path(dataset/labels) labels_dir.mkdir(exist_okTrue) images_dir Path(dataset/JPEGImages) for xml_file in annotations_dir.glob(*.xml): img_file images_dir / (xml_file.stem .jpg) if not img_file.exists(): print(fmissing image for {xml_file.name}) continue from PIL import Image with Image.open(img_file) as im: img_w, img_h im.size yolo_lines voc_to_yolo(xml_file, img_w, img_h) out_path labels_dir / (xml_file.stem .txt) out_path.write_text(\n.join(yolo_lines))这里用xml_file.stem .jpg来拼图片路径假设图片扩展名是 jpg。如果数据里混着 png这段就会漏掉。稳妥的做法是遍历图片目录时用rglob(*)检查扩展名或者先统计一下图片目录里的扩展名分布再决定。3.3 四个必踩的边界坑越界、空框、类别断层、文件名错位格式转换脚本跑完不报错不代表数据是对的。我在实际项目里反复踩过四个坑每个都导致了后续训练异常。第一个坑是坐标越界。标注人员在打标时手一抖框的坐标可能超出图片边界比如 xmax 比图片宽度还大。归一化之后就会出现大于 1 的坐标值。YOLO 训练时对这种框的处理逻辑因版本而异但结果通常是 loss 异常或预测框跑飞。解决方案是转换时强制裁剪xmin、xmax 都限制在 0 到 img_w 之间ymin、ymax 限制在 0 到 img_h 之间。别只打印日志要在脚本里直接纠正。第二个坑是空框。xmax xmin或ymax ymin也就是标注框没有面积。这类框在 VOC 里是合法 XML但转换后宽高为 0除以图片宽高后全是 0训练时梯度直接出问题。我在脚本里已经加了box_w 0 or box_h 0的判断遇到这种情况跳过并打印。第三个坑是类别 ID 断层。有些数据集为了迁就历史版本类别编号是 0、1、4、7 这种跳跃的比如从更大的类别表里裁剪出来的子集。如果把 XML 里的类别名字直接映射到原有编号而不是重新按连续编号排模型的输出头会和不连续的类别张量错位。最典型的症状是训练时 loss 正常但混淆矩阵里的类别名和实际检测结果对不上。解决方法是永远用CLASSES.index(cls_name)重新分配连续编号。第四个坑是文件名错位。XML 文件存在但对应的 jpg 在流转过程中丢失或改名了转换脚本会生成一个没有对应图片的 txt训练时图片加载器直接报错。反过来也可能出现图片完好但 XML 缺失的情况。转换脚本里的img_file.exists()判断就是为了提前发现这个问题宁可在这里多花一分钟也别让训练跑到一半中断。4. 训练与验证用 YOLOv8 跑通摩托车和行人的最小闭环数据格式干净了下一步就是训练。现在做目标检测训练YOLOv8 已经是最主流的默认选项ultralytics框架把数据加载、增强、训练、验证全封装好了命令行就能跑通。这一章从数据划分开始走完整个最小闭环。4.1 训练集/验证集划分脚本随机种子和同源分组划分数据集是最容易犯错的环节。很多人直接random.shuffle了事但如果这批数据来自交通视频抽帧连续帧高度相似随机划分会把同一场景的相近画面同时分进训练集和验证集导致验证指标虚高模型一上真实场景就现原形。我一般会先看文件名是否有规律。很多交通数据集按视频抽帧命名比如video_001_000123.jpg前段是视频 ID后段是帧号。这种情况下要先按前缀分组组为单位划分from pathlib import Path from collections import defaultdict import random random.seed(42) images sorted(Path(dataset/images).glob(*.jpg)) # 按文件名前缀分组保证同一视频的帧不跨数据集 groups defaultdict(list) for img_path in images: prefix img_path.name.rsplit(_, 1)[0] groups[prefix].append(img_path) group_keys list(groups.keys()) random.shuffle(group_keys) split int(len(group_keys) * 0.8) train_images [img for k in group_keys[:split] for img in groups[k]] val_images [img for k in group_keys[split:] for img in groups[k]]这段按视频 ID 分组后以组为单位做 8:2 划分保证同一个视频的连续帧只出现在训练集或验证集其中一边。random.seed(42)是固定随机种子让划分结果可复现——同一个数据集划分两次得到同样的结果这对调试和对比实验很重要。划分完之后生成两个列表文件YOLO 训练时直接引用with open(train.txt, w) as f: f.write(\n.join(str(p) for p in train_images)) with open(val.txt, w) as f: f.write(\n.join(str(p) for p in val_images))如果数据集本身没有视频分组信息文件名看不出前缀规律就退化为纯随机划分但 seed 必须固定否则每次训练验证集都不一样实验之间没法比较。4.2 data.yaml 和最小训练命令参数这样设YOLOv8 用自己的 data.yaml 描述数据集路径和类别名都定义在这里。一个最简配置如下path: ./dataset train: train.txt val: val.txt names: 0: motorbike 1: personpath是数据集根目录train和val指向前面生成的图片路径列表文件names的键和值必须与第 3 章转换脚本里的CLASSES一一对应。这里最常见的错误是 names 里把motorbike和person的位置写反模型训练完成后预测结果类别颠倒。训练命令就一行yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16参数逐个说data指定刚才的 yamlmodel是预训练权重yolov8s.pt是 small 版本速度和精度的平衡点显存 6G 以下就选它显存充裕可以换yolov8m.ptepochs100对中小规模数据集够用再多了容易过拟合imgsz640是输入分辨率摩托车和行人这类中等尺度目标 640 起步没问题batch16是批大小根据显存调整2G 显存就降到 48G 显存可以开到 16。如果是初次跑建议先epochs30快速验证整个链路是否通确认 loss 在下降、验证集有输出再拉长到 100 个 epoch 做正式实验。这样能避免因为数据格式问题白跑几小时。4.3 训练完看什么混淆矩阵、PR 曲线和 mAP 的读法训练完别只盯着 mAP 一个数字。YOLOv8 在runs/detect/train目录下会生成混淆矩阵、PR 曲线和验证集预测图这些东西比 mAP 本身更能说明模型的实际能力。先跑一遍验证确认 best 权重的效果yolo detect val modelruns/detect/train/weights/best.pt datadata.yaml跑完看三个文件confusion_matrix.png里看摩托车和行人之间的误检率如果两个类互相混淆严重说明类别外观相似度高需要更多数据或更强的特征提取网络PR_curve.png里看曲线右下角有没有明显缺口有缺口说明某些类别的召回率到不了高处往往是小目标漏检val_batch0_pred.jpg里看预测框和标注框的贴合程度框偏大偏小一眼就能看出来。参数方面实际部署时要把conf和iou调一调。训练时的默认conf0.25偏低验证时输出一堆低置信度框视觉上显得误检很多这不代表模型差。真正上线时把conf提到 0.4 以上误检会大幅下降代价是召回率略降。这个取舍和场景强相关头盔识别场景宁可误报也要保住召回流量统计场景则相反。5. 避坑指南这类交通数据集最常见的五个坑这一章把我在摩托车与行人检测项目里实际碰过的五个高频问题列出来每条按现象、原因、解决三个层次写。这些问题单看都不难但叠加在一起很容易让人误判是模型结构问题实际上全是数据问题。5.1 现象解压后大量图片打不开训练集加载时报错中断这类数据集从源头到手里的流转路径通常很长中间任何一个环节丢包都会造成图片文件损坏。最典型的是用下载工具拉到一半断掉后自动续传但 zip 的字节流已经错位解压工具不报错解出来的图片却只有半截。训练时数据加载线程读到损坏文件直接抛异常整个训练流程中断。原因也很直接解压时没做完整性校验图片损坏被忽略直到训练时才暴露。解决方法是解压后第一时间跑体检脚本就是第 2.3 节那段Image.openim.load()的代码。这一步会把所有损坏图片揪出来。处理办法是把损坏图片和对应标注一起删掉不要尝试修复修复单张图片的时间成本远超重新下载。5.2 现象txt 和 jpg 同名但框的位置明显对不上这个坑最隐蔽因为从文件数量到文件命名全部正常但模型训练出来的检测框总是偏的。我遇到过一次跑完 epoch 之后可视化验证集预测图发现预测框整体往右下角偏移仔细观察才发现是标注框相对于图片内容整体错位。出现这种问题的原因通常是转换脚本里读图片尺寸时拿到的宽高顺序反了。PIL 的Image.size返回的是(width, height)但如果有人在脚本里写成img_h, img_w im.size交换坐标轴算归一化出来的框就全错。解决方法是转换后做可视化校验。随机抽 20 张图把标注框画在原图上肉眼看过再进入训练流程。这一步用任何画图库都能做别跳过用眼睛看一次比任何自动化检查都靠谱。5.3 现象训练时 loss 突然变成 NaN前面第 3.3 节提到的坐标越界是引发 NaN 的常见原因。标注框坐标超出图片边界归一化后出现负值或大于 1 的值模型在计算损失时对数值范围外的坐标做对数或平方运算数值直接溢出。另一个常见来源是空标签文件。某些图片的标注转换后是空 txtYOLO 训练时读到空标签某些版本会计算出无效损失。解决方法分两路一是在转换脚本里加clip操作把坐标强制限制在合法范围内二是在训练前统计数据集中空标签文件的数量把空 txt 和对应图片一起剔除或者检查这张图是不是真的没有目标如果确实没有目标那它本来就不该出现在检测训练集里。5.4 现象摩托车类别样本极少训练后该类 mAP 惨不忍睹交通场景数据集的通病行人的框可能是摩托车的十倍以上。原因很现实采集视频的监控点位不同行人出现频率天然高于摩托车。这种情况有两个处理方向。第一是类别加权YOLOv8 在 loss 里给不同类别分配不同权重把摩托车这个稀有类别的权重调高让模型更重视它的梯度贡献。第二是数据层面处理把摩托车样本做复制粘贴增强或者离线增强但要注意不能只在训练集里复制验证集要保持原始分布。更简单的办法是直接把训练 epoch 拉长配合早停给稀有类更多学习机会。5.5 现象验证集 mAP 很高真实场景一测就明显变差这就是第 4.1 节说的同源数据泄漏。如果训练集和验证集来自同一批视频的相邻帧模型很大程度上是“记住”了场景而并非学到了摩托车和行人这类语义概念。真实环境一变性能掉一截。判断方法很简单跑一次验证把预测框可视化输出如果验证集上几乎看不到误检但换一个视频源就大量漏检基本可以断定是数据划分的问题。解决办法是划分后检查两个集合的视频来源有没有重叠。如果数据里只有一段视频那就只能退而求其次按时间间隔抽帧每 30 帧取一帧做验证保证训练集和验证集至少没有相邻帧。做对比实验时固定同一份验证集别边训练边改验证集。6. 进阶让这个数据集的模型真正能跑的三个验证技巧数据、训练、排错都走通之后剩下的是怎么让模型在真实场景里站得住。这里分享三个成本低、收益明显的技巧。6.1 用测试集而非验证集做最终验收训练过程全程依赖验证集做早停和选模型验证集的分布早就参与进了模型选择。最终验收一定要准备一份完全没参与训练和验证的测试集。可以单独留 10% 的数据不碰等所有实验做完了用最优权重跑一次测试集得到的 mAP 才是真正可以对外报的数字。yolo detect val modelruns/detect/train/weights/best.pt datadata_test.yaml测试集的 data.yaml 单独写一个只指向预留的 10% 数据。这一步不花多少时间但能让你在汇报结果时心里有底。6.2 小目标处理用 SAHI 做切片推理交通场景里行人和摩托车经常出现在画面远端像素尺寸很小。YOLOv8 在 640 分辨率下对小目标的召回率有限。常见做法是把输入分辨率提到 1280但显存负担大。更省资源的做法是用 SAHI 做切片推理把大图切成带重叠的若干小块分别检测再合并结果。切块尺寸 640、重叠率 20% 是比较稳妥的起点重叠能避免目标正好卡在切片边界上被截断。6.3 用混淆矩阵校准类别定义训练完的混淆矩阵除了看精度还能反过来校准数据的类别定义。我在一个项目里发现摩托车的预测框经常和行人重叠细致排查后发现是行人的标注框把摩托车后座上的乘客也框进去了导致模型学到的行人概念混入了骑乘者的语义。这类问题用模型反查数据标注是最有效的路径比人工从头检查数据节省大量时间。最后说一个习惯我每次拿到新的目标检测数据集都会先跑一遍完整的数据体检和格式转换然后训练一个 30 个 epoch 的短实验确认链路通畅后再做正式训练。这个流程多花两小时但几乎能拦截掉所有能想到的数据问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表