YOLO与VOC双格式行人数据集:从标注解析到目标检测训练实战
2026/9/15 5:22:34 网站建设 项目流程

简介:面向行人检测的YOLO标注数据集,基于CUHK Occlusion Dataset构建,已完成训练集与验证集划分,同时提供YOLO和VOC两种格式标签,可直接用于主流目标检测模型的训练与评估。压缩包共计约2000个文件,其中2125张jpg原图、2124个txt格式YOLO标签、1062个xml格式VOC标签,以及少量缓存文件,压缩后大小为315.63MB,目录结构清晰,可按需选取对应格式。已有593人学习/下载,尤其适合计算机、电子信息工程、数学等专业学生用于课程设计、期末大作业或毕业设计,也可作为行人检测算法复现的基准数据。整理者为资深算法工程师,在计算机视觉领域有多年经验,除数据文件外,还提供了清晰的标注说明和划分策略,能够减少自行处理标注格式的重复劳动,帮助读者更快进入模型训练与参数调优阶段。

1. 一个能直接喂给 YOLO 的行人数据集意味着什么

拿到一个「2125 张图像 + YOLO / VOC 双格式标注」的行人数据集压缩包,最直接的价值不是省下载时间,而是省掉目标检测项目里最枯燥的两周:采集、清洗和标注。做行人检测的人都知道,标注边际成本很高,一个框中一个人还好,遇到遮挡、密集人群和小尺寸行人,一个下午标不了几百张。这个 zip 把图像和标注文件捆绑好,并且同时给出 YOLO 和 VOC 两种格式,意味着你可以直接进训练流程,也可以反向验证标注一致性。对初学者,它是不用碰标注工具就能跑通 yolo 目标检测流程的起点;对熟手,它是一个可以快速做预训练、迁移学习和格式转换测试的样本集。唯一要做的,是把里面的格式和坐标含义先摸清楚再喂给模型。

2. 解析 YOLO 与 VOC 标注:目录结构、坐标换算与双格式对齐

2.1 目录结构:两种格式的存放位置

常见做法是,压缩包解压后包含三个并行的目录:images 放原始图像,labels 放 YOLO 格式的 txt,Annotations 放 VOC 格式的 xml,根目录还有一个 classes.txt 或 names.txt 记录类别名。目录结构类似:

pedestrian_dataset/ ├── images/ │ ├── person_0001.jpg │ └── person_0002.jpg ├── labels/ │ ├── person_0001.txt │ └── person_0002.txt ├── Annotations/ │ ├── person_0001.xml │ └── person_0002.xml └── classes.txt

注意:YOLO 的 labels 和 VOC 的 Annotations 可能不在根目录,而是在某个子目录下。先看 zip 内顶层目录,不要上来就假设路径。图像文件名和标注文件名必须同名前缀,否则就是标注错位,后面训练必炸。这种并行目录结构是 yolo 数据集最常见的组织方式,Ultralytics YOLO、YOLOv5、YOLOv8 都能直接吃这种布局。

2.2 YOLO 格式 txt:归一化坐标与类别从 0 开始

YOLO 的每个 txt 文件对应一张图,每行描述一个目标,五列:class_id x_center y_center width height。class_id 是整数,从 0 开始;后四列是归一化坐标,以图像宽高为分母,值域 [0,1]。例如:

0 0.5125 0.4102 0.2435 0.6803

表示这个人框的中心在图像横向 51.25%、纵向 41.02% 处,框宽为整图宽的 24.35%,高为整图高的 68.03%。很多人第一次拿到标注会以为是像素值,直接拿去画框图,结果画到角落,这就是 yolo 标注训练工具最常见的误用。检查一个数据集是否靠谱,先看 txt 里的数值范围,如果出现大于 1 的值,说明格式不对或没归一化。

2.3 VOC XML:bndbox 与 size 缺一不可

VOC 格式是 XML,每张图一个文件,节点层级固定。最关键的是<size><bndbox>

<annotation> <filename>person_0001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>person</name> <bndbox> <xmin>349</xmin> <ymin>103</ymin> <xmax>661</xmax> <ymax>593</ymax> </bndbox> </object> </annotation>

xmin/ymin/xmax/ymax 是像素坐标,左上角为原点,x 向右,y 向下。这个和 YOLO 的中心点表示法差异很大,但可以互相转换,核心就是 size 里的宽高。很多转换脚本出问题,都是漏读 size 或把 width 和 height 搞反,导致框被拉伸。

2.4 用 Python 交叉校验 YOLO 与 VOC 是否对齐

拿到双格式数据集,第一步不是训练,而是写脚本检查两种标注描述的是不是同一个框。我一般会写一个非常短的解析函数,把一个 txt 和一个 xml 分别读出来做映射:

import xml.etree.ElementTree as ET from pathlib import Path def read_yolo_txt(txt_path): boxes = [] with open(txt_path, 'r') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue parts = line.split() cls_id = int(parts[0]) x_center, y_center, w, h = map(float, parts[1:]) boxes.append((cls_id, x_center, y_center, w, h)) return boxes def read_voc_xml(xml_path): root = ET.parse(xml_path).getroot() size = root.find('size') W = int(size.find('width').text) H = int(size.find('height').text) objects = [] for obj in root.findall('object'): name = obj.find('name').text bnd = obj.find('bndbox') xmin = int(bnd.find('xmin').text) ymin = int(bnd.find('ymin').text) xmax = int(bnd.find('xmax').text) ymax = int(bnd.find('ymax').text) # 像素坐标转归一化中心点 x_center = ((xmin + xmax) / 2) / W y_center = ((ymin + ymax) / 2) / H w = (xmax - xmin) / W h = (ymax - ymin) / H objects.append((name, x_center, y_center, w, h)) return W, H, objects

逻辑说明:read_yolo_txt 按行解析五列数据,过滤空行和注释行,把宽高保留为归一化值。read_voc_xml 先读 size 得到图像宽高,再把 bndbox 的像素坐标换算成中心点格式,这样两边的输出就是同一坐标系。注意这里四舍五入导致的微小偏差是正常的,但如果同一个框在两种格式里坐标偏差超过 0.01,就说明标注文件不同步,需要找压缩包的原始版本或重新转换。

参数说明:width 和 height 来自<size>,如果 XML 里缺少 size,这种标注无法可靠转成 YOLO 格式。解析函数的容错点在于过滤空行,因为某些标注工具会在 txt 末尾留下换行符或额外空格。

这两个函数是后续所有验证的基础。拿到数据后先随机抽 10 对图像和标注,用 OpenCV 把 YOLO txt 的框画出来,再在同图上画 XML 的框,若两张图的重叠率(IoU)都在 0.95 以上,说明双格式是同源的,可以放心使用下面表里的对应关系做后续转换。

YOLO 与 VOC 两种标注格式对照:

格式存放文件每目标信息坐标基准类别表示常用解析方式
YOLO txtlabels/*.txtclass_id, x_center, y_center, width, height图像宽高归一化,中心点整数 class_id按行 split(),float() 转换
VOC XMLAnnotations/*.xmlname, xmin, ymin, xmax, ymax像素绝对值,左上角原点字符串类别名xml.etree.ElementTree 解析

这张表也说明,VOC 转 YOLO 需要类别名到 id 的映射,YOLO 转 VOC 需要 id 到类别名的映射,单向转换其实很简单,难在两边数据源不一致时无法对齐。

3. 用这个数据集走通 yolo 目标检测流程:数据划分、data.yaml 与训练参数

3.1 按比例划分 train/val/test 并保持文件对同步

先别急着训练。yolo 目标检测流程里第一步是数据划分,否则验证集失去意义。我一般会把 2125 张图按 8:1:1 划成 train/val/test,并且保证 images、labels、Annotations 三个目录同步移动。不要直接手动拖文件,写一个脚本用同一个随机种子处理三个目录:

import random import shutil from pathlib import Path src_images = Path('pedestrian_dataset/images') src_labels = Path('pedestrian_dataset/labels') src_annots = Path('pedestrian_dataset/Annotations') all_images = sorted(src_images.glob('*.jpg')) random.seed(42) random.shuffle(all_images) n = len(all_images) train_split = int(n * 0.8) val_split = int(n * 0.9) splits = { 'train': all_images[:train_split], 'val': all_images[train_split:val_split], 'test': all_images[val_split:], } for split, images in splits.items(): out_img_dir = Path('data') / split / 'images' out_lbl_dir = Path('data') / split / 'labels' out_xml_dir = Path('data') / split / 'Annotations' for d in (out_img_dir, out_lbl_dir, out_xml_dir): d.mkdir(parents=True, exist_ok=True) for img in images: stem = img.stem # 三个同名前缀文件一起复制 shutil.copy2(img, out_img_dir / img.name) shutil.copy2(src_labels / f'{stem}.txt', out_lbl_dir / f'{stem}.txt') shutil.copy2(src_annots / f'{stem}.xml', out_xml_dir / f'{stem}.xml')

逻辑说明:先用固定随机种子确保实验可复现,再按 8:1:1 切分,test 不参与训练也不参与验证,只留到最后做最终评估。每个 split 下都建立 images、labels、Annotations 三套目录,并保证同名文件同步复制,这样后续既可以用 YOLO 格式训练,也可以用 VOC 格式做别的实验。参数说明:shuffle 的种子固定为 42,换数据集时想复现实验就保留这个种子;如果对行人类别不平衡敏感,可以改成按视频片段分组,避免同一个场景的人出现在 train 和 val 里导致指标虚高。

3.2 写 data.yaml 时注意绝对路径与类别顺序

Ultralytics YOLO 和 YOLOv5 都用 data.yaml 描述数据集。这里最容易踩的坑就是 path 用相对路径,换台机器跑直接报错 image not found:

# data.yaml path: /absolute/path/to/pedestrian_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: person

逻辑说明:path 是数据集根目录,train/val/test 是相对 path 的目录名。nc 是类别数,这个行人数据集只有 person 一类,因此 nc: 1。names 从 0 开始索引,和 YOLO txt 里的 class_id 对应。参数说明:如果你的压缩包 classes.txt 里写的不是 person 而是 pedestrian,必须改名或映射,否则训练能跑但模型输出的类别名是错的。

注意:数据集中如果夹杂了不是行人的图像,比如下载失败的占位图,训练前用 python 检查一下所有图片能否被 cv2.imread 正常读取,否则会在训练中途报错,非常难排查。

3.3 YOLOv8/v5 训练命令与必调参数

环境配置好后,训练命令非常短:

# YOLOv8 yolo detect train data=data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 lr0=0.01 # YOLOv5 python train.py --data data.yaml --weights yolov5s.pt --epochs 100 --batch-size 16 --img 640

逻辑说明:yolo detect train 是 Ultralytics YOLO 的入口命令,model 参数可以直接写预训练权重 yolov8s.pt,程序会自动下载。epochs=100 对 2125 张图的单人数据集略多,通常 50 个 epoch 就会过拟合,建议配合早停。imgsz=640 是训练和推理统一分辨率,像素不是 640 的图像会自动缩放加 letterbox。参数说明:batch=16 取决于显存,RTX 3060 12G 可以开 32,8G 建议 16 或 8;YOLOv5 的 --img 同时控制输入尺寸,这个参数对行人小目标检测影响很大,512 会丢失小行人细节,800 以上训练速度明显下降。

关键点是,yolo 损失函数里对边界框回归和分类有不同的权重,训练时如果小目标漏检严重,优先调大 imgsz,而不是盲目堆 epochs。这个数据集的标注是完整、可直接使用的,所以不需要在标注上耗时间,把精力放在 imgsz、batch 和 lr0 这三个 yolo 训练参数上就好。

3.4 训练过程中的标注自动校验

训练时用以下命令看标注真实分布:

yolo detect train data=data.yaml model=yolov8s.pt epochs=1 batch=1

第一次只跑 1 个 epoch,观察输出,关注 train/val box loss 和 cls loss 是否正常,以及标签是否出现在标注预览里。如果输出中标签数远少于图像数,说明许多图片和标注没有形成配对,回去检查 3.1 的划分脚本有没有漏文件。

4. VOC 转 YOLO 的踩坑清单:坐标转换脚本与常见误用边界

4.1 为什么还需要 VOC 格式的转换脚本

很多标注工具原生导出 VOC XML,比如 LabelImg。这个数据集虽然双格式都有,但你可能要把它合并进另一个只有 VOC 标注的数据集,或者在代码里统一走 VOC 接口。与其依赖图形工具,不如自己持有一个可靠的 XML 转 txt 脚本。VOC 转 YOLO 本质做两件事:像素坐标变归一化中心坐标,类别名映射成数字 id。

4.2 一个生产可用的 XML 转 YOLO 脚本

下面的脚本能处理 size 缺失、多类别映射和宽高为 0 的情况:

import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(xml_path, out_txt_path, class_to_id): root = ET.parse(xml_path).getroot() size = root.find('size') if size is None: raise ValueError(f'{xml_path} 缺少 size 节点,无法计算归一化坐标') W = float(size.find('width').text) H = float(size.find('height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text cls_id = class_to_id.get(name) if cls_id is None: print(f'跳过未知类别 {name} 在 {xml_path} 中') continue bnd = obj.find('bndbox') xmin = float(bnd.find('xmin').text) ymin = float(bnd.find('ymin').text) xmax = float(bnd.find('xmax').text) ymax = float(bnd.find('ymax').text) # 检查异常框 if xmax <= xmin or ymax <= ymin: print(f'忽略无效框 {xmin},{ymin},{xmax},{ymax} 在 {xml_path}') continue x_center = ((xmin + xmax) / 2) / W y_center = ((ymin + ymax) / 2) / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f'{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') out_txt_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')

逻辑说明:先检查 size 是否存在,规避最隐蔽的坐标漂移问题。class_to_id 是外部传入的字典,例如{'person': 0},未知类别直接跳过而不是崩溃,这在多数据集合并时很实用。异常框检测放在转换前,避免把退化框喂给模型。参数说明:输出用 6 位小数,对 640 分辨率来说误差在亚像素级,够用;如果数据集有 4K 大图,建议保留 8 位小数。

4.3 常见误用边界:类别 id、文件配对与坐标方向

第一,类别 id 从 0 开始。VOC 里如果同时有 person 和 car,很多新人习惯让 person=1,结果训练时模型学到的类别索引和 data.yaml 错位,推理时预测的类别全是反的。第二是文件配对,转换脚本必须严格用 xml 的 stem 生成 txt 文件名,不能复用 images 目录下的文件名,否则一旦有漏标注的图,txt 和 xml 就错位了。第三是坐标方向,VOC 的 y 轴向下和图像坐标系一致,但某些标注工具导出时 y 轴向上或原点在左下,这种数据流不能直接转,需要先用可视化确认。这个数据集本身是标准的 VOC 结构,所以不涉及这些脏数据问题,但如果你把它和其他来源的数据合并,规则就得立住。

5. 训练前最后一关:5 分钟脚本检查标注质量与异常框

5.1 全量扫描 labels 目录的数值合法性

最后给一个我自己每次拿到标注都会先跑的质量检查脚本。它扫描所有 txt、检查类别 id 是否在 names 范围内、坐标是否在 [0,1] 区间、框是否退化成线或点:

python check_labels.py --labels labels --num-classes 1

check_labels.py 核心逻辑里只做一个循环和几行判断:

from pathlib import Path import argparse def check_labels(label_path, num_classes): bad = [] for txt_file in Path(label_path).glob('*.txt'): for line in txt_file.read_text().splitlines(): if not line.strip(): continue parts = line.split() cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:]) if not (0 <= cls_id < num_classes): bad.append(f'{txt_file}: 类别 {cls_id} 超出范围') if not (0 <= x <= 1 and 0 <= y <= 1): bad.append(f'{txt_file}: 中心点越界 {x},{y}') if w <= 0 or h <= 0 or not (0 < w <= 1 and 0 < h <= 1): bad.append(f'{txt_file}: 宽高异常 {w},{h}') return bad if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--labels', required=True) parser.add_argument('--num-classes', type=int, default=1) args = parser.parse_args() problems = check_labels(args.labels, args.num_classes) for p in problems: print(p) print(f'共发现 {len(problems)} 个问题')

逻辑说明:类别越界说明 txt 和 names 定义不同步;中心点越界说明 XML 转 YOLO 时 size 用错;宽高异常说明 bndbox 坐标写反或漏读。所有异常汇总后统一打印,不遇错即停。参数说明:--labels指向 labels 目录,--num-classes默认 1,如果数据集包含多个类别,必须改成实际类别数,否则误报一大堆。

5.2 随机抽 20 张叠加可视化,肉眼确认标注语义

脚本检查完数值,还要画一次图确认语义。很多错误数值检查发现不了:比如框中心对但框住了两个人的一半,或者类别名是 person 但框里只有自行车。用 OpenCV 画框时注意坐标要乘回原图宽高,也就是把归一化中心点和宽高换算成左上右下:

import cv2 img = cv2.imread(str(image_path)) H, W = img.shape[:2] x1 = int((x_center - w / 2) * W) y1 = int((y_center - h / 2) * H) x2 = int((x_center + w / 2) * W) y2 = int((y_center + h / 2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)

提示:如果看到大量框整体偏移,不是标注坏了,而是标注工具导出时使用了不同的坐标原点,检查一遍 VOC XML 的 ymin/ymax 是否对称就能确认。

5.3 把检查能力固化进训练流程

这几个验证步骤熟悉后,可以把它们合并成一个 preflight 脚本,放在训练命令之前。每次换数据集、合并新标注、或从网上下载 yolo 数据再裁剪子集,都先跑一遍检查再启动训练。这样不依赖某个标注工具,也能在任何环境里快速判断数据集可用性,比等训练完看 NaN loss 再回头排查快得多。脚本本身也不绑定具体模型,多模态目标检测、红外小目标检测这些场景里,只要标注格式还是 YOLO txt,这套检查就一样适用,复用价值很高。

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

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

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

立即咨询