简介:这份资源面向目标检测初学者与需要快速搭建训练流程的开发者,提供YOLO系列可直接使用的VOC2007完整版数据集,解决数据获取难、标注格式不统一、训练集划分繁琐等问题。压缩包共2000个文件,约846.91MB,以1986个xml标注文件为主,另含少量html教程、txt说明与py脚本,覆盖图片标注、格式转换与数据集划分等环节。资源同时提供voc、coco和yolo三种格式标签,分别存放于不同文件夹,适配不同框架的读取需求。附带的划分脚本可按需生成训练集、验证集与测试集,教程则涵盖Windows与Linux环境搭建及训练案例修改方法,便于读者对照跑通自己的数据。目前已有1264人学习下载,适合作为课程设计、毕业设计或算法验证的实战数据基础。
1. VOC2007 完整版数据集:为什么 10000 张图 + 三格式标签是目标检测的省心起点
如果你刚配好 YOLO 环境,准备拿自己的数据跑第一轮训练,大概率会卡在同一个地方:手头没有一份「拿来就能用」的数据集。网上搜 VOC2007,出来的要么是残缺的 5000 张子集,要么只有 XML 没有 YOLO 格式,要么标签和图片对不上号。这个标题讲的,就是一份把 VOC2007 补到 10000 张图片、同时给出 VOC、COCO、YOLO 三种格式标签、附带划分脚本和训练教程的完整方案。它解决的不是算法问题,而是数据准备阶段最耗时的格式转换和目录组织问题。适合两类人:一是想快速验证 YOLO 训练流程是否跑通的新手,二是需要一个标准基准来对比自己改进效果的老手。VOC2007 本身只有 20 类,但它的标注质量经过多年检验,作为目标检测的入门数据集,比直接上 COCO 要轻量得多,10000 张图的规模也刚好卡在单卡能跑、又不至于太小的区间。
2. 三种标签格式到底差在哪:VOC、COCO、YOLO 的目录结构与字段对照
2.1 VOC 格式:XML 的树形结构决定了它最啰嗦
VOC 格式的核心是每张图对应一个 XML 文件,文件名和图片名一致,放在Annotations目录下。XML 里记录了图片尺寸、目标类别和边界框的左上角与右下角坐标。这种格式的好处是可读性强,用文本编辑器打开就能看懂;坏处是解析慢,而且不同人写的解析脚本对<difficult>、<truncated>这些字段的处理不一致,容易埋坑。
一个典型的 VOC XML 长这样:
<annotation> <folder>VOC2007</folder> <filename>000001.jpg</filename> <size> <width>500</width> <height>375</height> <depth>3</depth> </size> <object> <name>person</name> <difficult>0</difficult> <bndbox> <xmin>100</xmin> <ymin>80</ymin> <xmax>300</xmax> <ymax>350</ymax> </bndbox> </object> </annotation><name>是类别名,<bndbox>是绝对坐标。注意<difficult>为 1 的目标在评估时通常会被忽略,但训练时是否保留要看你的策略。我一般会在转换时把 difficult 为 1 的框直接丢掉,避免模型学到模糊样本。
2.2 COCO 格式:一个 JSON 管所有,但索引容易绕晕
COCO 格式把所有标注塞进一个 JSON 文件,结构分images、annotations、categories三个顶层字段。images里每张图有唯一的id,annotations里每条标注通过image_id关联到图片,通过category_id关联到类别。边界框是[x, y, width, height],注意这里是宽高而不是右下角坐标,转换时最容易在这里翻车。
{ "images": [{"id": 1, "file_name": "000001.jpg", "width": 500, "height": 375}], "annotations": [ {"id": 1, "image_id": 1, "category_id": 1, "bbox": [100, 80, 200, 270], "area": 54000, "iscrowd": 0} ], "categories": [{"id": 1, "name": "person", "supercategory": "none"}] }bbox的四个值是左上角 x、左上角 y、宽度、高度。area是框面积,iscrowd为 1 表示这是密集人群之类的区域,训练时通常忽略。COCO 格式适合做多数据集统一评估,但如果你只是跑 YOLO,没必要绕这一圈。
2.3 YOLO 格式:归一化坐标 + 类别索引,最简但最不直观
YOLO 格式每张图对应一个.txt文件,每行一个目标,格式是类别索引 x_center y_center width height,所有坐标都除以图片宽高做了归一化,取值在 0 到 1 之间。类别索引用的是从 0 开始的整数,对应一个classes.txt里的顺序。
0 0.400000 0.573333 0.400000 0.720000这行表示类别 0,中心点在图片宽度的 40%、高度的 57.33% 处,框宽占图片宽度的 40%,框高占图片高度的 72%。归一化的好处是模型输入尺寸变化时标签不用改,坏处是肉眼没法直接判断框的位置对不对,调试时得反算回去。
三种格式的对照关系可以看这张表:
| 维度 | VOC | COCO | YOLO |
|---|---|---|---|
| 存储方式 | 每图一个 XML | 单个 JSON | 每图一个 TXT |
| 坐标类型 | 绝对坐标 xmin/ymin/xmax/ymax | 绝对坐标 x/y/w/h | 归一化中心点 + 宽高 |
| 类别表示 | 字符串名称 | 数字 id + 名称映射 | 从 0 开始的索引 |
| 适合场景 | 传统评估、可读性要求高 | 多数据集统一评测 | YOLO 系列直接训练 |
选型建议很直接:如果你确定用 YOLO 训练,最终一定要转成 YOLO 格式;VOC 格式保留作为原始标注备份;COCO 格式只在需要和其他数据集合并评估时才有必要生成。这份方案同时给三种格式,省得你后面想换框架时再回头转一遍。
3. 从 VOC 到 YOLO:划分脚本与格式转换的完整操作流程
3.1 目录组织:先定结构再动手,避免路径写死
拿到数据集后第一件事不是急着跑脚本,而是把目录结构定下来。我一般会这样组织:
dataset/ ├── VOC2007/ │ ├── Annotations/ # 原始 XML │ ├── JPEGImages/ # 原始图片 │ ├── ImageSets/ │ │ └── Main/ # 划分后的 train/val/test 列表 │ └── labels/ # 转换后的 YOLO txt ├── coco/ │ └── annotations.json └── data.yaml # YOLO 训练配置ImageSets/Main下放train.txt、val.txt、test.txt,每行一个图片文件名(不带扩展名)。这个结构是 VOC 官方约定,很多脚本默认从这里读划分列表,保持它省事。
3.2 划分脚本:按 8:1:1 分层抽样,固定随机种子
划分脚本的核心是保证每类在 train/val/test 中的比例大致一致,尤其是样本少的类。下面这个脚本按 8:1:1 划分,固定随机种子,输出三个列表文件:
import os import random from collections import defaultdict random.seed(42) # 固定种子,保证每次划分结果一致 anno_dir = "VOC2007/Annotations" img_dir = "VOC2007/JPEGImages" out_dir = "VOC2007/ImageSets/Main" os.makedirs(out_dir, exist_ok=True) # 统计每张图包含的类别,用于分层抽样 img_classes = defaultdict(set) for xml_file in os.listdir(anno_dir): if not xml_file.endswith(".xml"): continue img_id = xml_file[:-4] with open(os.path.join(anno_dir, xml_file), "r") as f: content = f.read() for line in content.splitlines(): if "<name>" in line: cls = line.strip().replace("<name>", "").replace("</name>", "") img_classes[img_id].add(cls) # 按主类别分组,保证每类都有样本进入验证集 groups = defaultdict(list) for img_id, classes in img_classes.items(): main_cls = sorted(classes)[0] # 取第一个类别作为分组依据 groups[main_cls].append(img_id) train, val, test = [], [], [] for cls, ids in groups.items(): random.shuffle(ids) n = len(ids) n_train = int(n * 0.8) n_val = int(n * 0.1) train.extend(ids[:n_train]) val.extend(ids[n_train:n_train + n_val]) test.extend(ids[n_train + n_val:]) for name, ids in [("train", train), ("val", val), ("test", test)]: with open(os.path.join(out_dir, f"{name}.txt"), "w") as f: f.write("\n".join(sorted(ids)))random.seed(42)是关键,不固定种子的话每次划分结果不同,实验没法复现。按主类别分组是为了防止某个类全部被分进训练集,验证时该类指标直接为零。如果你的数据集类别极不均衡,可以把main_cls换成更细的分组策略,比如按类别组合分组。
3.3 格式转换:VOC XML 转 YOLO TXT 的四个边界处理
转换脚本本身不长,但边界情况多。下面这个版本处理了四个常见问题:图片尺寸缺失、坐标越界、difficult 标记、类别名不在预设列表:
import os import xml.etree.ElementTree as ET classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] class_to_idx = {c: i for i, c in enumerate(classes)} def convert(anno_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(anno_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) if w <= 0 or h <= 0: print(f"跳过 {xml_file}:尺寸异常") continue lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_to_idx: continue if obj.find("difficult") is not None and obj.find("difficult").text == "1": continue bbox = obj.find("bndbox") xmin = max(0, int(bbox.find("xmin").text)) ymin = max(0, int(bbox.find("ymin").text)) xmax = min(w, int(bbox.find("xmax").text)) ymax = min(h, int(bbox.find("ymax").text)) if xmax <= xmin or ymax <= ymin: continue x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{class_to_idx[name]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, xml_file[:-4] + ".txt") with open(out_path, "w") as f: f.write("\n".join(lines)) convert("VOC2007/Annotations", "VOC2007/JPEGImages", "VOC2007/labels")max(0, ...)和min(w, ...)是防止标注框超出图片边界,VOC2007 里确实存在这种脏数据。difficult为 1 的直接跳过,避免模型学习模糊目标。类别名不在classes列表里的也跳过,防止索引错位。转换完记得抽查几个 txt,用反算公式验证一下坐标是否落在合理范围。
3.4 生成 data.yaml:YOLO 训练配置的必填项
YOLO 训练需要一个 yaml 文件告诉它图片和标签在哪、类别有哪些:
path: ./dataset train: VOC2007/ImageSets/Main/train.txt val: VOC2007/ImageSets/Main/val.txt nc: 20 names: ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']path是数据集根目录,train和val是相对于path的列表文件路径。nc是类别数,names的顺序必须和转换时的classes列表完全一致,否则标签全错。这个文件写错一个字符,训练时 loss 会直接不降,别问我是怎么知道的。
4. 训练教程:用这份数据集跑通 YOLO 的第一轮迭代
4.1 环境确认:三个版本号必须对齐
在跑训练之前,先确认三个东西的版本:Python、PyTorch、YOLO 框架。我一般用 Python 3.8 到 3.10,PyTorch 1.12 以上,YOLOv5 或 YOLOv8 都行。版本不对齐最常见的表现是ImportError或者 CUDA 报错。用下面命令快速检查:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"如果cuda.is_available()返回 False,先别急着跑训练,检查显卡驱动和 CUDA 版本。CPU 训练 10000 张图会慢到让你怀疑人生,建议至少有一张 8G 显存的卡。
4.2 启动训练:命令行参数逐个说清
以 YOLOv5 为例,训练命令如下:
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/train \ --name voc2007_exp1--data指向刚才写的 yaml 文件。--weights用预训练权重,从零训练收敛慢且效果差,常见做法是加载 COCO 预训练权重再微调。--img 640是输入尺寸,VOC2007 图片普遍偏小,640 够用,想提精度可以上 1280 但显存翻倍。--batch 16在 8G 显存上跑 640 尺寸比较稳,显存不够就降到 8。--epochs 100是上限,实际看验证集指标早停。--device 0指定第一张卡。
训练启动后重点看三个输出:box_loss、obj_loss、cls_loss。正常情况三个 loss 都该下降,如果obj_loss不降反升,大概率是标签格式有问题,回头检查 txt 文件里的坐标是不是归一化后的值。
4.3 验证与推理:确认模型真的学到了东西
训练完在验证集上跑评估:
python val.py --data data.yaml --weights runs/train/voc2007_exp1/weights/best.pt --img 640看mAP@0.5和mAP@0.5:0.95两个指标。VOC2007 上 YOLOv5s 跑 100 epoch,mAP@0.5 通常在 0.75 到 0.82 之间,低于 0.7 说明训练有问题。推理单张图:
python detect.py --weights runs/train/voc2007_exp1/weights/best.pt --source test_image.jpg --img 640输出图会画在runs/detect/exp下。重点看小目标和密集目标有没有漏检,VOC2007 里person和car的密集场景是重灾区。
5. 避坑与排查:标签转换和训练中最容易翻车的五个点
5.1 现象:训练 loss 一直不降,mAP 接近零
原因:YOLO 标签的类别索引和data.yaml里的names顺序对不上。转换脚本里classes列表的顺序和 yaml 里写的不一致,模型学的是错位的类别。
解决:把转换脚本里的classes列表和 yaml 里的names逐字对比,确保顺序完全一致。最稳妥的做法是只维护一份类别列表,转换和训练都从同一个文件读。
5.2 现象:验证集 mAP 正常但推理时框全偏了
原因:图片在训练时被 resize 了,但推理时没有保持同样的预处理。YOLO 默认会做 letterbox 填充,如果你自己写了推理脚本没做这一步,坐标就会偏。
解决:直接用框架自带的detect.py,不要自己手写预处理。如果必须自己写,确保 letterbox 的缩放比例和填充值跟训练时一致。
5.3 现象:某些类完全检测不到
原因:划分时该类样本全部进了训练集,验证集里没有,或者该类样本太少被模型忽略。
解决:检查train.txt和val.txt里每个类的出现次数,样本少于 50 的类考虑做数据增强或者过采样。VOC2007 里pottedplant和sofa样本偏少,容易出这个问题。
5.4 现象:训练到一半 loss 突然变成 NaN
原因:学习率太大或者 batch 里有脏数据,坐标归一化后超出 0 到 1 范围。
解决:先把学习率降到 0.001 试一轮。如果还 NaN,用脚本扫一遍所有 txt,检查有没有坐标值大于 1 或小于 0 的行。转换时的max(0, ...)和min(w, ...)就是防这个的,但如果你用了别的转换脚本,不一定有这层保护。
5.5 现象:COCO 格式评估时 AP 和 YOLO 自带的 mAP 对不上
原因:COCO 的 AP 计算方式和 YOLO 的 mAP@0.5 不是一回事,COCO 用的是 0.5 到 0.95 多阈值平均,且对小目标的定义不同。
解决:对比指标时统一用同一个评估工具。如果要用 COCO 评估,先把 YOLO 格式转回 COCO JSON,再用 pycocotools 跑,不要拿两个不同工具的数字直接比。
6. 进阶技巧:用这份数据集做消融实验和类别平衡
当你跑通第一轮训练后,这份数据集真正的价值在于做对比实验。我一般会固定三组配置:一组用全部 10000 张图,一组只用 5000 张,一组对稀有类做过采样,然后对比 mAP 变化。这样能快速判断你的改进到底是来自算法还是来自数据量。
具体操作上,写一个简单的采样脚本控制训练集规模:
import random random.seed(42) with open("VOC2007/ImageSets/Main/train.txt") as f: ids = f.read().splitlines() # 按比例采样,比如只用 50% sample_ratio = 0.5 sampled = random.sample(ids, int(len(ids) * sample_ratio)) with open("VOC2007/ImageSets/Main/train_half.txt", "w") as f: f.write("\n".join(sampled))然后在data.yaml里把train指向train_half.txt,其他不变,跑一轮看 mAP 掉多少。如果掉得不多,说明你的模型对数据量不敏感,可以往轻量化方向走;如果掉得厉害,优先补数据而不是改结构。
另一个技巧是检查类别分布。VOC2007 的person类占了将近 30% 的标注框,pottedplant不到 2%。这种长尾分布会让模型偏向多数类。我通常会在损失函数里给稀有类加权重,或者用重采样让每个 batch 里稀有类至少出现一次。YOLO 框架本身不直接支持类别加权,但可以在数据加载器里做。
最后说一个我踩过的坑:不要用测试集调参。test.txt里的图在训练和验证阶段都不该出现,只有最终报告结果时才跑一次。我见过有人把 test 当 val 用,调出来的模型在真实场景里一塌糊涂。固定好train、val、test的划分,random.seed写死,这是实验可复现的底线。希望帮到你。
本文还有配套的精品资源,点击获取