简介:手提袋检测数据集取自COCO2017,提取全部含handbag的图片与标注,统一转为VOC与YOLO两种格式,类别仅handbag,样本7133个。数据分两部分发布,本压缩包为第二部分,zip打包,约395MB。包内txt文件为YOLO格式标签,共7134个;xml为VOC格式标签,共7133个;jpg原始图像7133张。三类文件对应清晰,可直接用于YOLOv5/YOLOv8训练,也可在Faster R-CNN、SSD等框架中读取。省去COCO标注转格式环节,特别适合快速验证检测算法的研究者、备赛选手与初学者;单一类别便于专项调参与误差分析,目前已有490人学习下载。
1. 手提袋检测数据集:7133张单类样本,VOC与YOLO双格式一次给齐
做目标检测的同行应该都有过这种经历:模型结构调好了,训练脚本写好了,结果卡在数据上。想检测手提袋(handbag),翻遍公开数据集,要么是COCO这种多类别大而全、单一类别样本量不够聚焦,要么是网上零零散散下载的图片,标签格式五花八门,还得自己写脚本清洗转换,一折腾就是两三天。这份数据集就是从COCO2017里把handbag类别单独提取出来,做成了VOC(xml)和YOLO(txt)两套标签,共7133张图片,属于拆分成两部分后的第二部分。适合直接用YOLOv5、YOLOv8或者SSD这类框架训练手提袋检测模型的人,也适合需要VOC格式做迁移学习或做数据增强实验的人群。省掉的是从COCO全量数据里筛类别、转格式、对编号这一整套重复劳动,拿到手整理好目录就能开训。
2. 数据集内部结构:VOC与YOLO两套标签的完整解读
2.1 文件清单与命名规则:从文件名能看出什么
先看项目正文里给出的这些文件名:
handbag_coco2017_0170.txt handbag_coco2017_05471.txt handbag_coco2017_04049.txt handbag_coco2017_04330.txt handbag_coco2017_01494.txt handbag_coco2017_02313.txt handbag_coco2017_05193.txt handbag_coco2017_02819.txt handbag_coco2017_02455.txt handbag_coco2017_05036.txt拿handbag_coco2017_05471.txt来说,名字拆成三段看:handbag是类别名,coco2017表示来源是COCO2017数据集,05471是它在COCO原图中的编号。这么做的好处是,万一你想回溯原始图片、查某张样本的原始标注信息,拿着这个编号去COCO2017的标注JSON里一查就能定位。实际下载解压后,目录里除了这些txt,还会有对应的xml标签文件以及图片文件,三者靠同名前缀关联。
这里提醒一个很多人第一眼会忽略的点:标注文件列表给出的都是txt,但摘要里说了VOC和YOLO两种格式都有,即xml和txt各一套。也就是说,同一张图片会对应两个标签文件——xml里存的是xmin, ymin, xmax, ymax这样的绝对坐标,txt里存的是归一化后的center_x, center_y, width, height。两份标签描述的是同一批目标,只是格式不同。
2.2 两种标注格式的字段含义与坐标换算
VOC格式的xml标签,典型内容长这样:
<annotation> <folder>handbag</folder> <filename>handbag_coco2017_05471.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>handbag</name> <bndbox> <xmin>118</xmin> <ymin>96</ymin> <xmax>412</xmax> <ymax>389</ymax> </bndbox> </object> </annotation>关键字段就三块:filename对应图片文件名,size里的宽高是计算归一化坐标的依据,bndbox里的四个值就是目标框的左上角和右下角绝对像素坐标。
YOLO格式的txt则长这样:
0 0.4140625 0.5052083 0.459375 0.6104167一行的五个数字依次是:类别id、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。拿上面这行验证一下:(0.4140625 * 640, 0.5052083 * 480)算出来就是(265, 242.5),对应绝对坐标的中心点;0.459375 * 640 = 294是框宽,0.6104167 * 480 = 293是框高。反推回去,左上角(265 - 147, 242.5 - 146.5)即(118, 96),右下角(265 + 147, 242.5 + 146.5)即(412, 389),和上面xml里的数值对得上。
这种双格式设计对实际工程很实用。YOLO系列训练直接读txt,省去在线解析xml的耗时;而VOC格式方便你在LabelImg里打开二次标注,也方便做数据增强时回退到绝对坐标计算。
2.3 为什么同一份数据要做两套标签
做检测训练的都知道,不同框架对标签格式的要求不一样。YOLOv5、YOLOv8原生吃txt,SSD的VOC版本实现要吃xml,如果以后想试DETR这类基于COCO格式的模型,手里有VOC格式也能转。这份数据直接提供两种格式,省掉的是最无聊也最容易出错的转换环节——坐标归一化、类别id映射、文件一一对应,任何一步错了标签就废了。
另外还要注意“第二部分”这个概念。摘要里写得很清楚,这批是拆分后的第二部分数据。手里只有第二部分的样本量,类别覆盖可能不如完整数据全面,所以训练前建议先检查一下是否有配套的第一部分。如果两份数据要合并使用,合并时要处理一个最常见的坑:txt和xml文件里如果类别id是从0开始编号的,两部分数据如果各自独立编号,合并前必须统一类别映射关系。
3. 用这份数据跑YOLO训练:目录组织与命令落地
3.1 推荐的目录组织方式
从网上下载的数据集,往往解压出来就是一堆图片和标签堆在一起,直接扔进训练脚本大概率报错。我一般会先在项目根目录重新整理一遍结构,按照YOLO训练的习惯组织:
handbag_dataset/ ├── images/ │ ├── train/ │ │ ├── handbag_coco2017_0170.jpg │ │ └── ... │ └── val/ │ ├── handbag_coco2017_01494.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── handbag_coco2017_0170.txt │ │ └── ... │ └── val/ │ ├── handbag_coco2017_01494.txt │ └── ... ├── data.yaml └── handbag.xml # VOC格式标签单独归拢到另一个目录这一步可以用个简单脚本完成,推荐用软链接而不是复制文件,省磁盘空间:
mkdir -p handbag_dataset/{images/{train,val},labels/{train,val}} # 假设下载解压后的原始目录是 raw/,图片和txt都在里面 # 按8:2随机划分train和val python split_dataset.py --source raw/ --output handbag_dataset/split_dataset.py里通常干三件事:读原始文件列表,按比例随机切分,把图片和对应的txt标签成对移动到目标目录。切分时务必保证每张图片都找到同名的txt,否则训练时候YOLO会跳过没有标签的图片,最后算出来的mAP是虚高的。操作完之后检查一下两边的文件数量是否对得上,用一行命令就能办到:
find handbag_dataset/images/train -name "*.jpg" | wc -l find handbag_dataset/labels/train -name "*.txt" | wc -l两个数字一致,说明图片和标签是成对搬过去的。
3.2 修改data.yaml与类别配置文件
YOLO训练时,数据集的配置写在data.yaml里,核心就三项:训练集图片路径、验证集图片路径、类别名列表。这份数据集只有一个类别handbag,所以配置很简单:
train: handbag_dataset/images/train val: handbag_dataset/images/val nc: 1 names: ['handbag']这里最容易翻车的点是nc和names的对应关系。数据集的txt标签里类别id是0,names列表第一项必须是handbag。如果某个txt文件里出现了类别id为1的标注,而配置里只有nc: 1,训练会直接报错或者所有id=1的标注被忽略。建议训练前写个一行命令扫描所有txt,检查类别id的最大值:
cat handbag_dataset/labels/train/*.txt handbag_dataset/labels/val/*.txt | awk '{print $1}' | sort -n | uniq -c输出应该是只有一行0开头的计数,如果出现了1 xxx,说明数据里有脏标签,要排查是不是格式转换时类别id偏移了。
3.3 训练命令与参数选择
数据准备好之后,train和val两个目录都有了,可以用YOLO系列的训练入口直接开跑:
yolo detect train data=handbag_dataset/data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0如果用的是YOLOv5,对应命令是:
python train.py --data handbag_dataset/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100参数选择上有几个实际考量。imgsz=640是COCO预训练模型的默认输入尺寸,手提袋这类目标在图片里尺寸跨度大,有的整体占了大半张图,有的只是街拍场景里的一个配件,640分辨率算是一个稳定折衷。epochs=100在这个数据量级下够用了,单类别检测收敛快,通常训练到60个epoch损失就平稳了。batch大小看显存,16G显存跑yolov8n、batch16问题不大,如果显存小就降到8。
训练过程中要重点看两个指标:Box_Precision和Box_Recall。手提袋检测有个特点,很多目标存在遮挡和形变,手提袋的提手部分在行人场景里经常被身体挡住。如果recall偏低,不用急着加epochs,先检查是不是验证集里混入了大量遮挡严重的样本,这类样本对mAP的影响比较大。
训练完成后,模型权重会存在runs/detect/train/weights/best.pt,直接用这个权重做推理就行。测试单张图片的推理效果:
yolo detect predict model=runs/detect/train/weights/best.pt source=test_images/handbag_demo.jpg4. 避坑指南:手提袋数据集最常见的四个问题
4.1 类别id错位导致标注全部失效
现象:训练时Loss正常下降,但验证集的mAP一直徘徊在0.2以下,明显不正常。
原因:数据集的txt标签里类别id为0,但如果你把这份数据和其他数据集合并,或者修改过data.yaml的names列表顺序,比如把names: ['handbag']改成了names: ['person', 'handbag'],那么原标签里的0会被当成person,handbag类别反而没有对应标注,模型等于在学一个错误的映射。
解决:合并数据集前先统一类别id映射表,重新生成txt标注,而不是直接改配置文件。如果只是单独用这份数据,训练前务必确认data.yaml的names第一项就是handbag,不要自作主张去调整顺序。
4.2 验证集里图片数量比标注文件多
现象:脚本报错提示某张jpg找不到对应txt,程序中断。
原因:原始数据在整理划分时没做成对校验。有些数据源里图片是完整的,但个别图片没有目标,标注文件生成时被跳过了;或者标注文件生成了但对应图片损坏被删除。这类不等配对问题在从大数据集里筛选子集时特别常见。
解决:写个脚本按图片目录遍历,逐个检查同名txt是否存在。缺了就把图片挪到无标签目录,不参与训练。
import os from pathlib import Path img_dir = Path('handbag_dataset/images/train') label_dir = Path('handbag_dataset/labels/train') missing = [] for img_path in img_dir.glob('*.jpg'): label_file = label_dir / (img_path.stem + '.txt') if not label_file.exists(): missing.append(img_path) for img_path in missing: no_label_dir = img_dir.parent / 'no_label' no_label_dir.mkdir(exist_ok=True) img_path.rename(no_label_dir / img_path.name) print(f"处理完成,共移出 {len(missing)} 张无标签图片")这段脚本逻辑很简单,但能在训练前把潜在隐患都清掉。
4.3 把VOC标签和YOLO标签搞混
现象:用YOLO训练时读取xml文件,或者反过来在需要voc格式的框架里喂了txt,报出坐标越界或解析失败的错误。
原因:这份数据集同时包含两套标签,目录里很容易混放。txt是归一化坐标,直接读出来是0到1的小数;xml是绝对像素坐标,两者混用轻则框偏,重则训练直接崩。
解决:VOC标签单独放一个目录,不要和YOLO的txt混在一起。训练YOLO模型就只让脚本读txt目录,xml目录保持独立。如果日后需要从VOC转YOLO,也要单独写转换脚本处理,不要在训练流程里夹带转换逻辑。
4.4 预训练权重与数据集来源不匹配
现象:使用COCO预训练权重开始训练,前几个epoch的loss下降特别快,但之后的mAP上不去。
原因:COCO预训练权重里包含80类,其中就有handbag。模型初始特征提取器已经能认出袋类目标,但如果数据集里手提袋的尺度分布和COCO原始分布差异大——比如你的场景全是密集小目标——模型需要更多迭代重新适应,而固定backbone会拖慢适配速度。
解决:如果数据尺度分布差异大,前30个epoch可以设置freeze=10冻结backbone前10层,之后再解冻。如果场景简单、目标尺度均衡,直接用完整权重微调就行,通常50个epoch内就能收敛。
5. 进阶技巧:训练前写个校验与格式互转工具链
5.1 标签完整性校验脚本
训练跑了一半才发现数据有问题是最痛苦的。我现在拿到任何检测数据集,第一步不是急着开训,而是先跑一遍完整校验。针对这份手提袋数据,我习惯写一个脚本同时验证VOC和YOLO两套标签的完整性、坐标合法性和格式一致性:
import xml.etree.ElementTree as ET from pathlib import Path def validate_yolo_txt(txt_path, img_width, img_height): errors = [] with open(txt_path, 'r') as f: for line_num, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: errors.append(f"第{line_num}行字段数不为5") continue cls_id, cx, cy, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cls_id != 0: errors.append(f"第{line_num}行类别id异常: {cls_id}") if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < w <= 1 and 0 < h <= 1): errors.append(f"第{line_num}行坐标越界: {parts[1:]}") return errors def validate_voc_xml(xml_path): errors = [] tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) for obj in root.iter('object'): name = obj.find('name').text if name != 'handbag': errors.append(f"类别名异常: {name}") bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) if not (0 <= xmin < xmax <= img_w and 0 <= ymin < ymax <= img_h): errors.append(f"VOC坐标越界: ({xmin},{ymin},{xmax},{ymax})") return errors这段脚本干了三件事:检查txt每行是不是5个字段,类别id是否都是0,归一化坐标是否在合法区间;检查xml里类别名是否统一为handbag,绝对坐标是否越界。跑一遍能把数据里99%的脏标签揪出来。坐标越界这个问题在从COCO转格式时经常出现——原图标注框有极少数是超出图片边界的,转成txt时如果没做裁剪,归一化后的w或h可能略大于1,训练时会导致anchor计算异常。
5.2 一键互转:VOC转YOLO的场景与实现
虽然这份数据两套格式都给了,但实际动手改数据时难免会有变动:比如你想合并第一部分和第二部分的VOC标注,或者要加标注师补充的xml框,就得自己再转一次。反过来,有些模型工具链只认VOC格式,也需要从txt反推回xml。这里给出VOC转YOLO的核心代码,我加过一些处理越界框的逻辑:
import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, output_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name != 'handbag': continue bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 越界框裁剪 xmin = max(0, xmin) ymin = max(0, ymin) xmax = min(img_w, xmax) ymax = min(img_h, ymax) # 裁剪后目标退化则跳过 if xmax <= xmin or ymax <= ymin: continue cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(output_path, 'w') as f: f.write('\n'.join(lines))这段转换脚本最值得注意的就是越界框裁剪逻辑。原数据集里的标注框偶尔会超出图片边界几个像素,直接转换会导致归一化坐标出现负数或大于1,训练时这些框的IoU损失计算会出问题。裁剪之后再过滤掉退化框,能保证每一行输出都是合法标注。从那以后我拿到任何VOC标注数据,都会先跑一遍这段逻辑再训练,没有再被坐标越界坑过。希望这篇拆解能帮到你,让你的数据集处理环节少走点弯路。
本文还有配套的精品资源,点击获取