ARTICLE DETAIL

资讯详情

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

婴儿车检测数据集实战:VOC与YOLO双格式转换及YOLO训练部署指南

婴儿车检测数据集实战:VOC与YOLO双格式转换及YOLO训练部署指南 简介一套面向目标检测模型训练的图像数据集共包含1073张婴儿车场景图片标注类别覆盖婴儿车、行人、自行车、行李箱和轮椅同时提供VOC与YOLO两种常用格式适合计算机视觉学习者、算法工程师以及毕业设计人员直接用于检测模型训练省去从零采集和标注数据的环节。压缩包内共2000个文件主要文件类型为xml标注文件和YOLO格式的txt标注文件包体约58.43MB并附有标注规则和使用说明可核对LabelImg画框方式与坐标格式。压缩包不含分割路径内容更简洁便于直接查看图片与对应标注。数据分布上stroller与human两类标注框较多而bicycle、suitcase、wheelchair只有极少标注框可作为类别不均衡场景下的研究素材。资源已完成格式统一和合理性校验解压后即可划分数据集已有263人学习下载适合快速搭建检测任务的数据处理管线。1. 婴儿车检测数据集开局只有1073张图先别急着训练第一次拿到“婴儿车检测数据集VOCYOLO格式1073张5类别.7z”这个压缩包时多数人的第一反应是解压、扔进 YOLO 开训。1073 张图听起来不多但做目标检测的都知道数据质量比数据量重要得多双格式标注意味着你不用再为换模型重写标注转换器但双格式也常常意味着其中一份是旧版、另一份有遗漏。这份数据集的适用场景很具体商场婴儿车停放区监测、智慧园区推车引流分析、母婴室占用检测这类小目标域项目。适合的读者是刚拿到数据、准备做迁移学习训练或复现检测管线的工程师。本文从解压开始讲到训练、验证和部署前的阈值校准每一步都有可抄的命令。2. 先过压缩包这一关7z 解压、目录检查与 VOC/XML 和 YOLO/txt 的格式差异2.1 用 7z 命令行解开数据集三个常用参数常见做法是在 Windows 上双击用 7-Zip 解压但一次性的工程化处理我建议直接走命令行Linux 服务器上也一样。先确认系统有没有装 p7zip没装就装一下# Debian/Ubuntu 安装 p7zip sudo apt install p7zip-full -y # 解压到指定目录参数 -o 指定输出路径-y 表示遇到覆盖直接确认 7z x 婴儿车检测数据集VOCYOLO格式1073张5类别.7z -o/opt/datasets/stroller -y解压参数里最容易被忽略的是-o。如果不加它7z 会把所有文件原样撒进当前目录XML 标注、JPG 图片和 txt 标签混在一起后面找文件要命。-y适合脚本化场景解压过程中不再交互确认。压缩包比较大的时候可以加-p传密码但有些包的密码是中文或者含特殊符号用-p密码带上引号更安全。解压完做一次目录检查先看清里面到底有几层目录、图片和标注是不是一一对应# 统计图片数量正常应该是 1073 find /opt/datasets/stroller -name *.jpg | wc -l # 看顶层目录结构只展开两层 find /opt/datasets/stroller -maxdepth 2 -type d很多数据集打包时会在根目录下再套一层文件夹比如VOC2007/JPEGImages、VOC2007/Annotations之类。如果训练脚本里的路径写死为JPEGImages而解压出来的目录名是images就得先做软链接或移动目录这一步省不得。顺带说一句7z 格式相比 zip 的压缩率更高但因为用的 LZMA2 算法解压耗时也更长出现“卡住”的错觉是正常的不是进程死了。2.2 VOC 的 XML 和 YOLO 的 txt同一个框的两种记法同一个目标的标注在两种格式里表达方式完全不同。VOC 格式用的是 XML 文件一个object节点就是一个目标框坐标是像素绝对值annotation filenameIMG_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namestroller/name bndbox xmin318/xmin ymin402/ymin xmax624/xmax ymax691/ymax /bndbox difficult0/difficult /object /annotationYOLO 格式对应的是同名 .txt 文件每行一个框内容是类别id 中心点x 中心点y 宽 高前四个数值都做了归一化范围是 0 到 10 0.245312 0.506018 0.159375 0.267592计算方式不复杂中心点 x 等于(xmin xmax) / 2 / image_width宽等于(xmax - xmin) / image_widthy 和高度同理。区别在于 VOC 框坐标用整数像素给人读的YOLO 的归一化坐标给模型读的不管输入图片缩放成 640 还是 1280txt 都不用改。这套数据的价值就在于两份标注都在训练 YOLOv8 用 txt跑 Faster R-CNN 用 XML不用临时造轮子。2.3 为什么双格式数据集在工程上更顺手双格式最大的好处不是“多一种选择”而是省掉了格式转换这一步最常出现的低级错误。我见过不少项目因为转换脚本里忘记除以宽高导致训练出来的模型框全偏到图片边缘。有了现成的双格式你可以直接拿 YOLO 的 txt 开训同时留着 VOC 的 XML 做可视化检查和交叉验证。但双格式也有隐患两份标注可能是不同工具、不同时间生成的大概率存在不同步。比如有人用 LabelImg 补标过 XML却没重新导出 txt或者反过来txt 是脚本批量生成的遇到difficult1的困难样本时处理策略不一致。所以拿到手的第一步不是训练而是做一次数据体检看两份标注对不对得上。提示先跑训练前把 XML 和 txt 都抽查 10 个文件用脚本回读坐标比对这一步能省下后面几天排错时间。3. 数据体检把 VOC 标注转成 YOLO 标注的转换脚本与边界框核查3.1 写一个稳健的 VOC→YOLO 转换脚本如果你拿到的压缩包里 VOC 和 YOLO 两份标注只有一份齐全或者你想从零把 XML 转成训练用的 txt这里给一个可以直接用的做法。这个脚本的关键点有三个类别映射顺序必须固定、归一化时全用浮点运算、对越界和异常尺寸做上报。import os import glob import xml.etree.ElementTree as ET # 类别列表顺序决定 txt 里的 id务必与训练配置保持一致 class_names [stroller, person, backpack, shopping_cart, dog] def voc_to_yolo(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): # difficult 为 1 的样本通常质量差默认跳过可以按需求放开 if obj.findtext(difficult) 1: continue name obj.findtext(name) if name not in class_names: print(f[skip] unknown class {name} in {xml_path}) continue bnd obj.find(bndbox) xmin float(bnd.findtext(xmin)) ymin float(bnd.findtext(ymin)) xmax float(bnd.findtext(xmax)) ymax float(bnd.findtext(ymax)) # 归一化并顺便做边界保护防止标注出界 x_center min(max((xmin xmax) / 2.0 / img_w, 0.0), 1.0) y_center min(max((ymin ymax) / 2.0 / img_h, 0.0), 1.0) w min(max((xmax - xmin) / img_w, 0.0), 1.0) h min(max((ymax - ymin) / img_h, 0.0), 1.0) # 宽度或高度小于 3 像素的框大概率是坏标注提示但不丢弃 if (xmax - xmin) 3 or (ymax - ymin) 3: print(f[warn] tiny bbox in {xml_path}: {name}) class_id class_names.index(name) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines) \n) # 对 Annotations 目录下所有 XML 执行转换 xml_files sorted(glob.glob(/opt/datasets/stroller/Annotations/*.xml)) for xml_file in xml_files: voc_to_yolo(xml_file, /opt/datasets/stroller/labels, img_w1920, img_h1080)这个脚本的核心逻辑是解析 XML 里每个object把四个像素坐标转成中心点加宽高的归一化值再按类别映射表写出 id。参数上要注意class_names的顺序这个列表一旦定下就不能乱调否则训练时类别全对不上。img_w和img_h如果每张图尺寸不一致不要硬编码改为从 XML 的size节点读取。difficult 样本的处理要看项目如果你的测试场景里目标大量被遮挡强行跳过会让模型在遮挡场景漏检建议先保留训练后看效果再决定。好的数据集中应该有了一份可用的 txt 标注但并不意味着不需要自己跑一次转换脚本——通过重转换可以反向验证原有 txt 是否正确这就是“回读校验”的意义。3.2 五类别分布与图像尺寸统计发现不平衡和坏图数据体检的第二步是统计每个类别的目标数量、图像尺寸分布和标注框的宽高比。这一步能帮你判断该不该调整损失权重、要不要做增广。下面这个脚本读的是训练用的 txt 文件逐行解析并汇总import os from collections import Counter, defaultdict label_dir /opt/datasets/stroller/labels class_names [stroller, person, backpack, shopping_cart, dog] # 统计每个类别的目标数量、框的数量、以及极宽高比的数量 class_count Counter() box_area defaultdict(list) for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f[error] bad line in {txt_file}: {line}) continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) if cls_id len(class_names): print(f[error] class id {cls_id} out of range in {txt_file}) continue class_count[class_names[cls_id]] 1 box_area[class_names[cls_id]].append(w * h) for cls in class_names: cnt class_count[cls] areas box_area[cls] avg_area sum(areas) / len(areas) if areas else 0 print(f{cls}: {cnt} boxes, avg_norm_area{avg_area:.5f})跑完后重点看两个指标类别数量差异和平均面积。如果“person”类有 5000 个框而“dog”只有 120 个训练出来的模型对 dog 的召回率基本可以放弃。这时候的常见做法是给少数类提高 loss 权重或者用复制粘贴增广把小目标类的人工样本补到 500 个以上。平均面积则提示你这个任务主要是小目标还是大目标如果 stroller 的平均归一化面积只有 0.02那训练尺寸建议直接从 640 提到 960 或 1280别用小图硬扛。3.3 划分训练集与验证集按场景切而不是按文件随机切数据集划分看起来简单实际是另一个容易翻车的地方。婴儿车检测数据往往是连续视频抽帧得来的同一辆婴儿车会出现在几十张连续帧里。如果按文件名随机划分同一个场景的帧可能同时进入训练集和验证集导致验证分数虚高部署到新场景就露馅。比较稳的划分方式是先观察文件名规律按场景前缀分组。多数抽帧工具生成的图片名形如scene042_00017.jpg下划线前是场景 ID。按场景分组再划分import os import random from collections import defaultdict img_dir /opt/datasets/stroller/images scene_groups defaultdict(list) for img_name in os.listdir(img_dir): if not img_name.endswith(.jpg): continue scene_id img_name.split(_)[0] # 按前缀提取场景号 scene_groups[scene_id].append(os.path.splitext(img_name)[0]) all_scenes sorted(scene_groups.keys()) random.seed(42) random.shuffle(all_scenes) # 按场景数 85/15 切分而不是按图片数 val_scene_count max(1, int(len(all_scenes) * 0.15)) val_scenes set(all_scenes[:val_scene_count]) with open(/opt/datasets/stroller/train.txt, w) as f_train, \ open(/opt/datasets/stroller/val.txt, w) as f_val: for scene in all_scenes: for stem in scene_groups[scene]: img_path os.path.join(img_dir, stem .jpg) if scene in val_scenes: f_val.write(img_path \n) else: f_train.write(img_path \n)这样划分的思路是同一场景在不同光照、不同遮挡状态下高度相似按场景切才能测出模型真正的泛化能力。文件名前缀按_分割是常见抽帧习惯如果你的数据集前缀规则不一样改一行 split 的代码即可。另外注意random.seed(42)固定种子复现实验时划分结果一致这是论文和团队协作里容易被忽略的小细节。4. 用 YOLO 训练婴儿车检测模型数据配置、预训练权重与损失曲线判读4.1 先写对 data.yaml路径、类别数和类别名的对齐训练脚本读取的数据配置是个 YAML 文件看似简单实际坑不少。以 YOLOv5/v8 通用配置为例# data_stroller.yaml path: /opt/datasets/stroller # 数据集根目录写绝对路径别写相对路径 train: train.txt # 训练集图片路径列表 val: val.txt # 验证集图片路径列表 nc: 5 # 类别数量必须和转换脚本里 class_names 的长度一致 # 类别名顺序必须和转换脚本里的 class_names 顺序一致 names: 0: stroller 1: person 2: backpack 3: shopping_cart 4: dog这个文件最常出的问题有三个。第一path写相对路径训练脚本在多层目录切换时直接找不到文件第二nc和names的数量对不上YOLO 在读取标签时不会报错而是静默跳过越界的 id最后训练出来的模型类别全乱第三names顺序和 txt 里的 id 对应不上比如转换脚本里 stroller 是 0这里却把 person 放到了 0。这三个问题都会直接导致一个结果loss 正常下降但验证 mAP 是 0 或者类别全错乱。4.2 从 COCO 预训练权重起步而不是从零训练1073 张图的数据量从零初始化训练 YOLO 几乎必过拟合正确做法是加载预训练权重做迁移学习。YOLOv5 的官方仓库在 release 里提供yolov5s.pt、yolov5m.pt等预训练模型下载文件不大几十 MB 到一百多 MB 不等下载后放到项目根目录即可。训练脚本会按模型名自动查找对应权重文件。需要注意权重版本必须和训练代码匹配YOLOv5 的权重不能直接给 YOLOv8 用同一个大版本内的小版本也要尽量对齐否则加载时会出现结构不匹配的警告甚至直接报错。加载时只取 backbone 部分的参数detect head 因为类别数从 COCO 的 80 类改成了 5 类结构已经变了这部分会随机初始化。这也是为什么迁移学习对小数据集这么友好——预训练模型已经学会了边缘、纹理、部件结构你只需要让它重新学“婴儿车长什么样”这个具体概念。4.3 三个必调参数batch、epochs 和 imgsz训练 YOLO 模型最影响效果和显存开销的三参数是 batch size、epochs 和输入分辨率。下面是针对 1073 张图的参考设置# 在项目目录下执行项目源码从官方仓库拉取 python train.py \ --data /opt/datasets/stroller/data_stroller.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --project runs/stroller \ --name exp_v1--img 640是输入到网络的图片短边尺寸。如果前面统计发现目标平均面积很小建议直接提到 960代价是显存占用上升、训练时间变长。--batch 16在 16G 显存上跑 yolov5s 比较稳妥12G 显存降到 832G 显存可以开到 32。注意 batch 太小小于 8会让 BN 层的统计量不稳定这在训练后期容易诱发 loss 震荡。--epochs 100对千张量级的数据集够用了超过 150 轮基本开始过拟合验证集的 mAP 不再上升甚至下跌此时保存的 best.pt 不如提前停在第 80 轮的权重。还有一个容易被忽略的参数是--workers或者--cache。机械硬盘上训练数据加载可能成为瓶颈加--cache把图片缓存进内存能显著加速但如果内存不足 32G缓存 1073 张 1080p 图片会直接 OOM所以用之前先看 free 内存。4.4 看 yolo 损失函数曲线判断训练健康度而不是只盯 mAP训练日志里每个 epoch 会打印三组损失box_loss、obj_loss、cls_loss。它们的含义分别是预测框和真值框的回归误差、置信度误差、分类误差。三者在 YOLO 的损失函数中加权相加全部下降才说明训练在健康推进。常见的不健康曲线有几种box_loss 下降但 obj_loss 始终平稳不降说明模型没有学会“哪里有目标”大概率是正负样本不均衡或者 anchor 尺寸和真实目标不匹配。可以考虑用 k-means 重新聚类 anchor。cls_loss 不降类别名顺序错了模型在乱猜。三个 loss 都降到很低但验证 mAP 为 0数据划分泄漏了或者验证集标签是空的。loss 出现 nan 且训练中断这是 BN 崩溃或者学习率太大的典型表现后面第 5 章专门讲。看曲线时不要只看 train loss还要对比 val loss。如果 train loss 一路走低但 val loss 在第 60 轮开始回升说明已经过拟合early stopping 或减少 epochs 是更务实的选择。训练结束后runs/stroller/exp_v1/weights/下会有best.pt和last.pt两个文件best 是验证集上 mAP 最高的权重部署时默认用它。5. 从 7z 报错到 BN 崩溃训练婴儿车检测模型的 5 个高频踩坑记录5.1 7z 提示“密码正确但一直报错”有人拿到加密的数据压缩包明明密码没错运行7z x -p密码却一直提示 Wrong password。遇到这种情况先别怀疑自己的记忆力。常见原因是终端和压缩包创建时使用的字符编码不一致特别是密码里含中文或特殊符号时Linux 终端默认 UTF-8 编码很容易和 Windows 下创建压缩包时用的 GBK 编码对不上。解决方式是先在 7-Zip 图形界面里手工输入一次密码成功解压后再在命令行用-p密码原样复现。如果 GUI 能解、命令行不能解大概率是终端的引号和转义把密码搞坏了配合单引号传参能规避大部分问题7z x 数据集.7z -o/opt/datasets/stroller -y -p这里写密码5.2 训练时 loss 正常下降但验证 mAP 始终为 0现象很隐蔽训练曲线的损失在稳步下降每个 epoch 的验证阶段却提示 mAP0.5 和 mAP0.5:0.95 全是 0。原因九成出在标签的类别 id 上。转换脚本里 class_names 列表有 5 项但 txt 文件里的 id 是 1 到 5——比如有人从 VOC 的name直接映射数字时用了从 1 开始的编号。YOLO 读取标签时遇到 id 等于 5 会被判定为越界这个框直接被忽略剩下 id 1~4 的框又被当成类别 1~4恰好和 names 里的 0~3 错位。解决方法是检查任意一个 txt 文件的最大类别 id必须小于nc的值并统一从 0 开始编号。这一步可以在转换脚本里加一个断言越界直接抛异常。5.3 转完格式可视化发现框全部偏到图外用 OpenCV 画框调试时发现所有目标框都挤在图像左上角或右下角位置完全不对。原因基本是归一化时混用了变量把(xmin xmax) / 2.0 / img_w写成了(xmin xmax) / 2.0 * img_w或者把xmax - xmin除成了xmax xmin。另一个常见低级错误是用整型除法两个 int 相除得到 0框就直接塌缩成一条线。解决思路很简单转换脚本里所有坐标运算都用 float先把 xmin、xmax 等变量全部float()强转计算完后再做一次 sanity check打印前三个框的坐标看数值是否都在 0~1 之间再做批量转换。5.4 正常状态的婴儿车能检出折叠状态的几乎全漏训练完在测试视频上跑平放的、正向的婴儿车框得很稳但折叠起来靠在墙边的、倒置的或者被大人身体挡住一半的婴儿车全部漏检。这是典型的类别内差异过大。婴儿车这个类别本身就有展开、折叠、正向、侧向多种状态1073 张图里如果折叠状态的样本只占百分之几模型就没机会学好这个子模式。解决方向有两个一是从原数据里挑出所有折叠状态的图用翻转、旋转、调整亮度做增广补到 100 张以上二是先训一版模型把所有置信度低于 0.3 的漏检框捞出来人工核对把确实漏掉的样本补标进训练集这种“hard negative mining”的做法在小数据集上非常有效。5.5 训练中 loss 变成 nanBN 崩溃与 AMP 的连锁反应训练到第 40 轮控制台突然打印loss: nan再往后所有指标都是乱码best.pt 和 last.pt 基本上全废了。这在 YOLO 训练里有个俗称叫“BN 崩溃”触发条件通常是学习率过大、batch 太小或者混合精度训练中梯度缩放异常。BN 层的统计量一旦因为某些极端 batch 变成 NaN之后所有轮次都救不回来。解决方式按优先级尝试先把初始学习率从默认 0.01 降到 0.001观察前 10 轮是否稳定如果还在 nan把 batch size 从 16 提到 32 或 64BN 在更大的 batch 上统计量更稳如果用的 YOLOv5 且开了--amp混合精度先关掉再用纯 FP32 训练。注意训练中断后别直接接着旧权重续训nan 的痕迹会残留在优化器状态里。正确的做法是删掉 exp 目录重新训或者从官方预训练权重重新开始。6. 部署前的最后一课用混淆矩阵和 F1-置信度曲线做阈值校准6.1 训练完先看这四张图别急着打包模型训练结束后runs/stroller/exp_v1/下会自动生成confusion_matrix.png、PR_curve.png、F1_curve.png和labels.jpg。很多人只扫一眼 mAP 就过了这几个文件才是真正告诉你模型弱点在哪的材料。混淆矩阵的每一行是真实类别每一列是预测类别对角线越亮说明该类别的召回率越高。看混淆矩阵时要注意一个怪现象整个矩阵的行和加起来是 1但列和、总和都不是 1。原因是 YOLO 生成混淆矩阵时按行做了归一化每行代表该类别的 recall 分布另外还专门统计了 background 一类。新手拿这个矩阵想算个总准确率怎么加都对不上这是正常的别在“总和不唯一”上浪费时间直接看对角线和跨类混淆的位置。6.2 用 F1-置信度曲线找部署阈值而不是拍脑袋定 0.25PR 曲线告诉你模型的潜力F1-置信度曲线告诉你该把置信度阈值设在多少。F1 曲线横轴是 confidence纵轴是 F1 score曲线最高点对应的置信度就是当前模型的最佳工作点。# 用训练好的 best.pt 对测试视频做推理 python detect.py \ --weights runs/stroller/exp_v1/weights/best.pt \ --source test_video.mp4 \ --conf-thres 0.15 \ --iou-thres 0.45 \ --save-txt \ --save-conf--conf-thres 0.15来自 F1 曲线的峰值附近这个值在测试视频上通常能兼顾漏检和误检。--iou-thres 0.45控制两个预测框的合并阈值框重叠超过 45% 就会被 NMS 合并对婴儿车这类目标不会过于激进。输出加上--save-txt会保存每个框的坐标和置信度方便后续跟业务系统对接--save-conf则把置信度写进 txt做二次过滤时不用重新推理一遍。我自己的习惯是调完阈值后对着测试视频多看三遍第一遍只看漏检第二遍只看误检第三遍才整体评估流畅度。曾经因为偷懒只盯着 mAP 调参把阈值设成 0.5 直接上线结果折叠状态的婴儿车漏了一半从那以后 F1 曲线就成了我训练流程里的必看项。这个数据集虽然只有 1073 张图但把解压体检、格式校验、按场景划分、迁移学习和阈值校准每一步做扎实训出来的模型在西红柿大棚、停车场、商场通道这些真实场景里都很能打。希望帮到你。本文还有配套的精品资源点击获取
返回列表