☰
手提袋检测数据集:VOC与YOLO格式转换及训练避坑指南
2026/9/30 1:25:25 网站建设 项目流程

简介:这是一份面向目标检测学习与YOLO实战的handbag类别数据集,基于COCO2017提取,已转换为VOC与YOLO两种标注格式,可直接用于手提袋检测模型训练。本包为整个数据集的第二部分,压缩包共2000个文件,主要包含jpg样本图片、xml标签与txt标签三类文件,整体约395MB。数据集标注目标为handbag,描述信息显示样本总量为7133,适合作为二分类或单类别检测项目的训练/验证数据。目前已有490人学习下载,适用于刚接触YOLO标签格式、需要现成VOC/YOLO标注数据的开发者,可跳过数据清洗与格式转换环节,直接进入模型训练与调参。

1. 手提袋检测数据集:VOC 和 YOLO 两份标签到底能省多少事

做目标检测的工程师拿到一份新数据集,第一件头疼的事往往不是模型精度,而是格式。手提袋检测数据集把 VOC 格式和 YOLO 格式的标签都整理好了,等于把“做模型对比实验”和“直接跑 YOLO 训练”两条路都铺平了。VOC 的 XML 标注适合拿来喂 Faster R-CNN、SSD 这类经典检测器,也方便做数据可视化核对;YOLO 的 txt 标注则是为训练阶段准备的,省去在线转换坐标的麻烦。适合三类人:做零售自助收银、仓储出入库方案的目标检测开发者;需要公共数据做算法评估的研究者;以及刚入门想拿着真实标注数据跑通一次完整检测流程的初学者。

2. 拆开数据集看结构:VOC 和 YOLO 两种标注到底差在哪

2.1 顶层目录与文件构成

拿到手提袋检测数据集后,第一件事是看目录结构。VOC 格式那边通常保留着 PASCAL VOC 时期的经典三件套:JPEGImages 放原图、Annotations 放 XML 标签、ImageSets/Main 放训练/验证划分的 txt 索引。YOLO 格式那边则把图片和标签按 train/val 分开,每张图片对应一个同名的 .txt 文件。

handbag_dataset/ ├── VOC/ │ ├── JPEGImages/ │ │ ├── handbag_00001.jpg │ │ └── handbag_00002.jpg │ ├── Annotations/ │ │ ├── handbag_00001.xml │ │ └── handbag_00002.xml │ └── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── classes.txt └── YOLO/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

需要说明的是,不同版本的数据集在目录命名上会有差异。比如 VOC 那边有的把划分文件直接放在 Annotations 同级,或者用 trainval.txt 合并训练验证;YOLO 这边有人会把 labels 直接放在 images 的同级目录,也有人用 .txt 索引文件代替 train/val 文件夹。遇到这种变化不用慌,划分文件本身是纯文本,每行一个不含扩展名的文件名,用脚本重新生成即可。关键只有一条:图片文件名必须和 XML、txt 的文件名严格对应,否则后面所有脚本都白搭。

2.2 VOC 格式:XML 里有什么,坐标怎么定义

打开一个 Annotations 下的 XML,结构长这样:

<annotation> <folder>JPEGImages</folder> <filename>handbag_00123.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>handbag</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>412</xmin> <ymin>268</ymin> <xmax>893</xmax> <ymax>715</ymax> </bndbox> </object> <object> <name>handbag</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>1</difficult> <bndbox> <xmin>40</xmin> <ymin>900</ymin> <xmax>180</xmax> <ymax>1010</ymax> </bndbox> </object> </annotation>

VOC 的坐标是绝对像素值,xmin/ymin/xmax/ymax 直接对应原图坐标,左上角为原点,单位是像素。xmax - xmin 就是检测框宽度,ymax - ymin 就是高度。如果一张图里有多个手提袋,就写多个<object>块。

这里有两个细节容易忽略。第一,<difficult>标签表示目标被严重遮挡或模糊到人眼都难分辨;经典检测器如 Faster R-CNN 在训练时默认忽略 difficult=1 的样本,而 YOLO 训练脚本不区分,这也是两种格式在处理难例时的语义差别。第二,XML 里<size>的宽高必须和真实图片一致,有些标注工具导出时会写错,转换脚本读出来就是错框。还有一点:LabelImg 这类目标检测常用标注工具导出的 XML 基本兼容这个结构,但偶尔会多出<segmented>之类的节点,解析时不要假设字段顺序。

2.3 YOLO 格式:txt 标注的归一化坐标与类别编号

YOLO 的 txt 标签比 XML 精简得多,每一行代表一个目标,五个数字依次是:类别编号、归一化中心点 x、归一化中心点 y、归一化宽度、归一化高度。

0 0.456000 0.375000 0.240000 0.410000 0 0.087000 0.860000 0.095000 0.110000

归一化就是把绝对像素值除以图片宽高,转换公式如下:

  • center_x = (xmin + xmax) / 2 / width
  • center_y = (ymin + ymax) / 2 / height
  • box_w = (xmax - xmin) / width
  • box_h = (ymax - ymin) / height

类别编号从 0 开始,编号对应的类别语义由 data.yaml 里的 names 列表决定。手提袋检测数据集如果是单类,那所有行的第一个数字都是 0;如果数据里分出了“纸袋”“布袋”“塑料袋”等多个类别,就得严格对照 names 顺序来读。这是后续最容易翻车的地方,我见过不止一次因为 class_names 顺序不一致导致整个训练集标签错位的案例。

YOLO 只存归一化坐标的好处是:训练脚本不需要每张图都去查原图尺寸,预处理速度快;原图被 resize 后标签无需改动依然自洽。代价是它丢失了原图尺寸信息,想从 txt 反向知道目标在画面里的实际大小,必须回到 VOC 格式或者原图。这也意味着,要做数据可视化和人工复核,VOC 格式的可用性远高于 YOLO 格式。

2.4 两种格式在训练流程中的分工

一份数据集同时给两种格式,不是资源浪费,而是覆盖了目标检测模型落地的两个阶段。VOC 格式适合做算法选型阶段的多模型对比——Faster R-CNN、SSD、EfficientDet 这些框架对 VOC 的支持最成熟,转换成本最低。YOLO 格式则直接对应训练阶段——YOLOv5 和 YOLOv8 的原生数据组织就是 images/labels 加 data.yaml,把数据放进去就能开训,不用在 DataLoader 里写自定义解析逻辑。

理解了这个分工,你就知道拿到数据集后该怎么用:先拿 VOC 格式做标注质量的随机抽检,确认无误后再切到 YOLO 格式训练。如果两份标签不是同一个版本生成的,那就以 VOC 为准做一次全量转换,不要盲目信任 txt。后续可以重新生成YOLO标签,也不要忘记先做一个数据体检。

3. 从 VOC 转到 YOLO 再转回来:转换脚本与参数细节

3.1 用 Python 写一个干净的 VOC 转 YOLO 脚本

常见做法是写一个脚本,遍历整个 Annotations 目录,把每个 XML 转成同名 txt。核心函数如下:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, output_txt, class_names): """ 把单张 VOC 格式 XML 转成 YOLO txt 标签 :param xml_file: 输入的 XML 文件路径 :param output_txt: 输出的 txt 文件路径 :param class_names: 类别列表,列表顺序决定 YOLO 类别编号 """ tree = ET.parse(xml_file) root = tree.getroot() # 图片尺寸必须从 XML 的 size 节点读取 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] # iter('object') 比 findall 更稳,能递归找到嵌套的 object 节点 for obj in root.iter('object'): name = obj.find('name').text.strip().lower() # 统一转小写,避免 Handbag 和 handbag 导致类别漏匹配 if name not in [c.lower() for c in class_names]: continue cls_id = [c.lower() for c in class_names].index(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) # 归一化到 [0,1],保留 6 位小数足够训练使用 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f'{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}') with open(output_txt, 'w') as f: f.write('\n'.join(lines) + '\n')

参数说明:class_names列表的顺序就是 YOLO 类别编号的语义,一旦确定就不要中途修改。用root.iter('object')而不是root.findall('object'),是因为部分标注工具导出的 XML 结构里 object 节点层级不标准,iter 能递归查找,兼容性更好。

这段脚本有一个容易被忽略的参数风险:如果 XML 里的difficult=1也被转出来了,训练时会当作正常目标参与计算。对 YOLO 来说这不是错误,但如果你希望和 Faster R-CNN 的结果公平对比,可以在转换前跳过 difficult 样本。

3.2 反向转换 YOLO 到 VOC 的注意点

反向转换在实际工作中用得到——数据可视化、修正标注、给其他检测框架做适配。但 YOLO 的归一化坐标丢失了原图尺寸,反向转换必须显式传入原图宽高:

def yolo_to_voc(txt_path, img_w, img_h, class_names): """ 把 YOLO txt 转成 VOC 格式的 object 列表 注意:img_w 和 img_h 必须来自原图,否则还原坐标会错位 """ objects = [] with open(txt_path, 'r') as f: for line in f: line = line.strip() if not line: continue parts = line.split() if len(parts) != 5: print(f'跳过非法行: {line}') continue cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) # 从归一化还原成绝对像素坐标 xmin = int((cx - bw / 2) * img_w) ymin = int((cy - bh / 2) * img_h) xmax = int((cx + bw / 2) * img_w) ymax = int((cy + bh / 2) * img_h) objects.append({ 'name': class_names[cls_id], 'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax }) return objects

这个函数输出的 object 列表还需要拼装成 XML 字符串,拼装时<size>节点必须和传入的img_w、img_h一致,否则下一次读入又会对不上。从归一化坐标还原像素坐标,本身就有 1 到 2 像素的取整误差,这个误差对检测训练没有影响,但如果你的任务是要做像素级裁剪,最好还是回到原始 VOC 标签取数。

3.3 转换后必须做的三个校验

转换脚本写完不是终点,校验才是避免后面翻车的关键。我每次转换完都会跑三件事。

第一,检查空标签。YOLO 训练时,一张图如果没有对应 txt,会被当作纯背景图;如果原本有目标、转换时漏写了,就等于把一个正样本变成了负样本,模型会学出“看到手提袋不要检测”的错误信号。第二,检查标签文件是否有对应图片。第三,检查归一化坐标是否越界。这三个问题可以用一个脚本一次性扫出来:

import os def validate_yolo_labels(label_dir, img_dir, class_num): """ 扫描 YOLO 标签目录,输出常见问题 :param label_dir: labels 目录 :param img_dir: 对应的 images 目录 :param class_num: 类别总数,用于校验编号越界 """ for label_file in sorted(os.listdir(label_dir)): if not label_file.endswith('.txt'): continue stem = label_file[:-4] # 检查是否有对应图片 has_img = (os.path.exists(os.path.join(img_dir, stem + '.jpg')) or os.path.exists(os.path.join(img_dir, stem + '.png'))) if not has_img: print(f'[无图] {label_file}') # 检查每行字段数和数值范围 with open(os.path.join(label_dir, label_file)) as f: lines = [l for l in f.read().strip().split('\n') if l] if not lines: print(f'[空标签] {label_file}') for i, line in enumerate(lines, 1): parts = line.split() if len(parts) != 5: print(f'[字段数错误] {label_file}:{i} -> 应5个字段,实际{len(parts)}') continue cls_id = int(parts[0]) if cls_id < 0 or cls_id >= class_num: print(f'[类别编号越界] {label_file}:{i} -> {cls_id}') vals = list(map(float, parts[1:])) if any(v < 0.0 or v > 1.0 for v in vals): print(f'[归一化越界] {label_file}:{i} -> {parts[1:]}') print('校验完成')

这个校验脚本本身就是一份数据集处理工具,对任何 YOLO 格式数据集都适用。跑完之后,检查输出的日志数量是否合理。如果空标签、无图的比例超过 5%,说明数据集的划分文件很可能有错位,这时候先修数据再训练,不要带着问题硬训。

4. 用手提袋数据集训练 YOLO 模型:目录组织与关键配置

4.1 YOLOv5 和 YOLOv8 的数据集目录组织与 data.yaml

YOLO 格式的目录组织各家框架基本统一,直接按下面的结构放就行:

datasets/handbag/ ├── images/ │ ├── train/ │ │ ├── handbag_00001.jpg │ │ └── ... │ └── val/ │ ├── handbag_10000.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── handbag_00001.txt │ │ └── ... │ └── val/ │ ├── handbag_10000.txt │ └── ... └── data.yaml

对应的 data.yaml 配置:

train: datasets/handbag/images/train val: datasets/handbag/images/val nc: 1 names: ['handbag']

nc是类别数量,names的列表顺序必须和 YOLO txt 里的类别编号一一对应。如果数据集里分了多个类别,比如['paper_bag', 'plastic_bag', 'cloth_bag'],那 txt 里出现数字 2 就代表cloth_bag。这个对应关系是全局唯一的,任何目录调整都不该改动它。

训练命令以 YOLOv5 为例:

python train.py --data data.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch 16

--img参数控制输入分辨率。这里有一个准则:看手提袋在画面里的像素占比。如果数据集中大部分目标占图面积超过 10%,640 分辨率就够;如果很多目标只有几十乘几十像素,建议直接上 1280,或者保持 640 训练、推理时用 SAHI 做切片。--batch按显存调整,16 在 8GB 显存上对 yolov5s 是安全的,显存小就降到 8。

4.2 单类数据集的类别平衡和尺寸分布问题

很多人以为单类数据集不存在类别不平衡,实际上对手提袋检测来说,真正的坑是目标尺寸分布和拍摄角度。

如果数据集中包含了大量近距离特写和少量远距离小目标,模型会偏向大目标。判断方法很简单:用脚本统计每个目标归一化宽度和高度,画个直方图,看看小于 0.1 的目标占比。如果远距离小目标占比低,常见做法是调大--img加高分辨率小目标响应,而不是盲目加训练轮数。YOLO 自带的 autoanchor 会自动适配标注框尺寸分布,但前提是训练前不手动关闭它。

另一个容易被忽略的是角度问题。手提袋在自然场景里经常被斜挎、挂在货架侧边,甚至旋转超过 45 度。标准 YOLO 检测框是水平矩形,如果数据的标注来自旋转框的导出,会把斜挎的手提袋真实尺寸拉大。遇到这种情况,我建议先做人眼抽检,随机看 50 张图的叠加可视化,确认标签和物体实际轮廓匹配,再开始训练。这个动作虽然费时间,但能避免整个训练过程建立在一个错误的标签基础上。

4.3 训练结束后的验证:用 mAP 和 PR 曲线判断标注质量

模型训练完,不要只看 loss 曲线。对目标检测模型来说,验证阶段的指标才反映真实效果。YOLOv5 在训练完会自动跑一次验证,也可以用下面的命令单独验证:

python val.py --data data.yaml --weights runs/train/exp/weights/best.pt --img 640

输出里重点关注下面几个目标检测评价指标:

指标含义手提袋场景解读
Precision预测框中真正含目标的占比数值低说明误检多,模型把背景当成了手提袋
Recall真实目标被召回的比例数值低说明漏检多,模型看不见部分手提袋
mAP@0.5IoU=0.5 时的平均精度工程落地的首要指标,达到 0.9 以上才可用
mAP@0.5:0.95多个 IoU 阈值的平均精度数值低于 0.5 说明标注框精度差,边界贴合不好

如果 mAP@0.5 高但 mAP@0.5:0.95 明显偏低,多半是标注框边界贴合不精细,或者物体本身遮挡严重。这类情况可以让标注工具重新校准,也可以用简单的后处理把预测框向中心收缩一点。如果 PR 曲线在召回率中段塌陷,说明模型对特定场景的目标一直漏检,去验证集里找对应 False Negative 图,大概率能看到某一类光照或角度从来没在训练集里出现过。

5. 手提袋检测数据集避坑:五个高频问题和排查方法

5.1 现象:loss 正常下降但验证 mAP 恒为 0

原因:训练集和验证集的标签划分错位。最常见的是 VOC 格式下用 ImageSets/Main 里的 train.txt 做划分,但 train.txt 里有的图片在 Annotations 里没有对应 XML;转成 YOLO 格式后,这些图变成了无标签背景图,训练时模型学到了“这张图没有目标”,验证时同一张图又有标签,导致预测和真值对不上。

解决:训练前统计划分文件与 XML/txt 的对齐情况。写一个对比脚本,找出 train.txt 里缺标签的图片,这种图片要么补标签,要么从划分文件里剔除。我一般会用上面 3.3 节的校验脚本跑一遍,空标签率超过 1% 就先修数据。

5.2 现象:训练到一半 loss 变成 NaN

原因:归一化坐标越界,或者某张图的检测框宽高为 0。转换脚本里如果 xmax 和 xmin 相等,宽度就是 0,归一化后仍然为 0,训练时计算 IoU 会出现除零错误。另一个可能是分割文件里混入了损坏图片,读取尺寸失败。

解决:训练前用 validate_yolo_labels 扫描一遍标签目录,重点看归一化越界的行。对于边界超出 [0,1] 的标签,可以做一个 clamp 处理,但对宽高为 0 的必须删除这一行标注。这类问题是数据层面的事,不要通过修改损失函数去规避。

5.3 现象:训练集和验证集指标都好,换新场景完全失效

原因:类别编号错位。如果数据集的 YOLO 标签由多个批次的标注合并而来,某个批次的类别顺序和主表不一致,就会出现部分 txt 里数字 0 代表纸袋,另一部分 txt 里数字 0 代表塑料袋;模型学了一个混合语义,自然在新场景下行为不稳定。

解决:以 VOC 格式的 class_names 为唯一真源,重新全量生成 YOLO 标签。生成后用 3.1 节的脚本跑一遍,再抽 50 张图把标签画出来人工核对。这个“画框看标签”的动作虽然原始,却是最有效的确认手段。

5.4 现象:远距离的手提袋漏检严重,近距离正常

原因:目标尺寸分布极不均衡。数据集中大多数手提袋是近距离拍摄,占图比例超过 20%;挂在货架远处的手提袋只有几十像素,在 640 分辨率下经过下采样后特征所剩无几。漏检的往往是这部分小目标。

解决:先做尺寸分布统计,归一化宽度小于 0.1 的目标占比若低于 10%,说明数据集本身小目标太少。常见做法是提高输入分辨率到 1280,或者在推理阶段做切片检测。训练侧可以开 mosaic 增强,但要控制切片的尺度范围,否则生成的手提袋比例失真。

5.5 现象:斜挎手提袋的检测框只框住一半

原因:旋转框被存成了水平框。真实场景里手提袋经常倾斜 30 到 60 度,水平 bndbox 会包含大量背景或截掉袋体。如果数据集原始标注是旋转矩形,导出成 VOC 的<bndbox>时没有做最小外接水平矩形重算,就会出现框偏大或偏小。

解决:检查原数据集是否附带旋转框标注。如果只有水平框,可以用分割掩膜重新计算最小外接矩形来修正。如果倾斜目标占比高,建议直接换用支持 OBB 的检测模型,比如 YOLOv8-OBB,不要再在水平框上强行训练。这个坑在自助收银、货架巡检这类场景里特别常见,因为相机视角和物体姿态变化大。

6. 投入训练前先跑一遍数据集体检:五个检查项一脚本全过

最后给你一个我每次拿到手提袋检测数据集都会先跑的“体检”流程。花五分钟做这件事,能省下后面几天排查训练问题的精力。

import os, random def dataset_health_check(yolo_root, class_num, sample_size=50): """ 一个脚本覆盖五个检查项:标签数、空标签、越界、类别分布、样本对齐 """ img_train = os.path.join(yolo_root, 'images', 'train') lab_train = os.path.join(yolo_root, 'labels', 'train') img_files = [f for f in os.listdir(img_train) if f.endswith(('.jpg', '.png'))] lab_files = [f for f in os.listdir(lab_train) if f.endswith('.txt')] print(f'图片数: {len(img_files)} 标签数: {len(lab_files)}') # 1. 图片和标签数量偏差 if abs(len(img_files) - len(lab_files)) > len(img_files) * 0.02: print('[警告] 图片与标签数量偏差超过 2%,检查数据集划分') # 2. 类别分布 class_count = [0] * class_num empty_count = 0 bad_count = 0 for lab in lab_files: with open(os.path.join(lab_train, lab)) as f: lines = [l for l in f.read().strip().split('\n') if l] if not lines: empty_count += 1 for line in lines: parts = line.split() if len(parts) != 5: bad_count += 1 continue cls_id = int(parts[0]) if 0 <= cls_id < class_num: class_count[cls_id] += 1 vals = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in vals): bad_count += 1 print(f'空标签文件: {empty_count} 非法行: {bad_count}') print(f'类别分布: {class_count}') # 3. 随机抽样本核对图片和标签是否存在 random.seed(42) sample = random.sample(img_files, min(sample_size, len(img_files))) for img in sample: stem = os.path.splitext(img)[0] if not os.path.exists(os.path.join(lab_train, stem + '.txt')): print(f'[缺失标签] {img}')

体检脚本的输出就是一张“能不能开训”的成绩单。空标签多,先修划分;非法行多,先修转换脚本;类别分布严重倾斜,先做数据增强策略。这些验完再训,模型翻车的概率会小很多。

做目标检测这几年来,我的一个习惯是:任何数据集到手,不画框直接训练的,后面代价迟早会还回来。标注格式统一、越界检查、随机抽检这三个动作,是投入训练前成本最低的保险。希望这份手记能帮你少踩几个坑,也希望这份手提袋检测数据集能真正跑出你想要的效果。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询