☰
YOLO行人数据集实战:标签转换与训练避坑指南
2026/10/1 15:14:34 网站建设 项目流程

简介:面向YOLO系列目标检测算法训练与验证的行人数据集,包含七千二百零三张已标注图像,适合入门和进阶开发者快速开展行人识别实验。压缩包整体约二百三十三点七六兆,共收录两千个标签文件,并提供两种标注格式:YOLO格式的文本文件与VOC格式的XML文件;同时已划分好训练集与验证集,附带数据配置文件data.yaml,可直接适配YOLOv5至YOLO11等主流版本。目前已有138人学习下载。使用这份数据集可免去手动标注和格式转换环节,直接进入模型训练、验证与测试流程;清晰的目录结构和现成的配置文件,也能帮助读者快速对比不同YOLO版本的行人检测效果,适合作为项目起步数据或算法评测基准。资源包内文件命名规范,便于批量管理,可有效减少前期准备时间。

1. 这个行人数据集到底是什么,能帮你省下多少标注时间

拿到别的团队标好的行人数据集,是最省事的一条路。做目标检测最难的不是模型选型,而是数据;7203张图像带标签,按一张行人图平均多框、复杂遮挡场景反复缩放标注来算,一个人全职标也得两周起步,而“yolo算法-行人数据集-7203张图像带标签-人-主人.zip”这类资源就是把这段人工工期直接换成了硬盘空间和几个小时的训练时间。但别急着解压拖进训练脚本——标题里那个“人-主人”的双类别划分、标签到底是不是YOLO原生txt格式、样本数量能不能支撑目标场景,每一样都会决定你是否白忙一场。下面就把整个落地过程拆开。

2. 把“人/主人”两类标签盘清楚:格式识别、转换与合并

2.1 解压后先查三样东西:目录结构、标签格式、类别分布(几行命令)

无论从哪个渠道拿到压缩包,第一步统一动作:解压后先看结构,不要双击进去乱翻。常见的数据集会包含 images/ 或 JPEGImages/ 目录放图,labels/ 或 annotations/ 目录放标注,但也存在图片和txt混在一个文件夹里、靠同名后缀互相匹配的情况。

unzip -q "yolo算法-行人数据集-7203张图像带标签-人-主人.zip" -d avail cd avail find . -type f | sed 's/.*\.//' | sort | uniq -c

先看扩展名统计:jpg/jpeg/png 和 txt 各有多少,数量是否大致对应7203。这个阶段最怕的是图片7203张、标签只有一半,或者标签文件名里带着奇怪的编号对不上图。文件数对不上直接在源头止损,不用继续往下走。

确认文件数量后,打开一个标签文件看内容。YOLO格式的txt每行是“类别ID 中心x 中心y 宽 高”,五个数字全部相对于原图尺寸做归一化,范围在0到1之间。没有归一化、坐标大于1的,多半是从标注工具导出后没做转换,后面训练会直接出问题。

用一段小脚本统计所有txt里每个类别各有多少个标注框,顺便检查类别ID是否连续从0开始:

# count_classes.py from pathlib import Path import collections labels_dir = Path("labels") counter = collections.Counter() for txt in labels_dir.rglob("*.txt"): with open(txt, encoding="utf-8", errors="ignore") as f: for line in f: parts = line.split() if len(parts) >= 5: counter[int(parts[0])] += 1 print("类别ID -> 标注框数量:", dict(counter)) print("出现的类别ID:", sorted(counter.keys()))

这段脚本的价值在于一次暴露两个问题:第一,类别数量是否符合预期;第二,ID是不是从0开始且没有断档。如果打印出来只有0和2、缺1,说明原数据集在标注时删掉过某个类别,YOLO训练时类别索引必须从0连续排列,否则类别名称和标签彻底错位。出现断档不要手动猜,把映射关系整理完再进下一步。

2.2 标签格式识别与YOLO转换:txt/XML/JSON三个分支的处理脚本

txt格式是理想情况,但实际拿到的数据集经常是VOC XML或者COCO JSON格式。判断办法很直接:如果文本里每行是“class x y w h”,那就是YOLO;如果文件以<annotation>开头,那就是VOC XML;如果整体是JSON包着images和annotations两个数组,那就是COCO格式。混用格式的压缩包我也见过,一套数据里txt和xml并存,不能只转一种就开训。

下面按VOC XML转YOLO格式给一套完整转换脚本,这也是行人检测里最常碰到的转换场景:

# voc_to_yolo.py import xml.etree.ElementTree as ET from pathlib import Path xml_dir = Path("Annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) class_names = ["person", "master"] # 按类别ID顺序 for xml_file in xml_dir.glob("*.xml"): tree = ET.parse(xml_file) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) if img_w == 0 or img_h == 0: print(f"{xml_file.name}: 图片尺寸为0,跳过") continue lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_names: continue box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # 边界裁剪:防止标注框超出图片范围 x1 = min(max(x1, 0), img_w - 1) y1 = min(max(y1, 0), img_h - 1) x2 = min(max(x2, 0), img_w - 1) y2 = min(max(y2, 0), img_h - 1) if x2 - x1 <= 1 or y2 - y1 <= 1: continue x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h lines.append(f"{class_names.index(name)} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_file = out_dir / f"{xml_file.stem}.txt" out_file.write_text("\n".join(lines), encoding="utf-8")

这段脚本有四个容易出事的位置。一是img_w和img_h从size节点读,缺失或为0时直接跳过,否则所有坐标算出来都是无效值。二是x1, y1, x2, y2做了min/max裁剪,VOC标注经常出现xmax等于原图宽度、xmin等于0的情况,如果不裁剪,归一化后可能正好等于1.0,训练时GT框落在边界外,损失在某些版本里直接变成NaN。三是类别名到ID的映射用class_names.index(name)实现,前一位加括号里的class_names.index(name)不能出现标--不存在的猫如是;四行输出要确保每行五个值全对。四是过滤掉宽度或高度小于等于1像素的框,这种框是脏标注,保留会让损失函数在处理极小框时数值不稳定。

转过之后把标签和图片做成同样的文件名前缀,放到images/和labels/两个目录,训练时YOLO靠“同名不同扩展名”找对应关系,这一步对不上,后面全白搭。

2.3 “人”与“主人”要不要合并:先可视化确认,再决定

标题里的“人-主人”两个类别很关键,但不要只凭命名猜——这个数据集里的“主人”可能是指牵着宠物的主人、保安亭里的值守人员、或者某个特定场景里需要单独识别的主体;原压缩包没有附带说明文档时,最可靠的办法是先画出来看。

画框脚本只需要OpenCV,几分钟能写完:

# draw_yolo_boxes.py import cv2 from pathlib import Path images_dir = Path("images") labels_dir = Path("labels") class_names = {0: "person", 1: "master"} for img_path in list(images_dir.glob("*.jpg"))[:50]: img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] txt_path = labels_dir / f"{img_path.stem}.txt" with open(txt_path, encoding="utf-8") as f: for line in f: parts = line.split() if len(parts) < 5: continue cls_id = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) color = (0, 255, 0) if cls_id == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(f"check_{img_path.name}", img)

看50张左右的图就能得出两个结论:两张标注框里的人是否真属于语义不同的目标;两个类别的样本量差距是否悬殊得没法训练。如果“主人”类别本质上是行人的一个子集,而且业务上最终输出只要“画面里有没有人”,那就合并成一个person类,不要因为压缩包里分了两类就强行保留。反过来,如果目标场景就是区分普通行人和带宠物的行人,那这两个类必须分开,合并等于把模型的上限砍死。我的经验是这类“人-XX”双类数据集,来自某个人群细分采集场景的概率远大于两类互斥目标;先花20分钟可视化,比训练完看测结果再返工划算得多。

3. 在YOLO上训练行人检测:从data.yaml到训练命令的参数

3.1 划分train/val目录,生成干净的data.yaml

把标签格式和类别定下来之后,下一步是划分训练验证集。划分时最容易犯的错是只挪图片不挪标签、或者用shutil.move时出异常导致文件对不上;最好用一段固定随机种子的脚本一次做完。

# split_data.py import random from pathlib import Path import shutil random.seed(42) images = sorted(Path("images").glob("*.jpg")) val_count = int(len(images) * 0.2) val_images = set(random.sample(images, val_count)) for split in ["train", "val"]: (Path(split) / "images").mkdir(parents=True, exist_ok=True) (Path(split) / "labels").mkdir(parents=True, exist_ok=True) for img in images: split = "val" if img in val_images else "train" label = Path("labels") / f"{img.stem}.txt" shutil.copy(img, Path(split) / "images" / img.name) if label.exists(): shutil.copy(label, Path(split) / "labels" / label.name)

用random.seed(42)固定划分结果,这样无论实验跑多少次,训练集和验证集始终一致,几个模型之间的对比才有意义。这里的比例是8:2,7203张图分出约5762张训练、1441张验证,对行人检测来说验证集是够的。不要用shutil.move,保留原始目录做备份,万一划分错了还有后悔药。数据量足够的场景下不用再单独切测试集,拿验证集当最终评估集用,等到部署前再用一个独立采集的小场景集做盲测。

然后写 data.yaml,路径尽量放到纯英文目录下。Windows上如果解压目录带“yolo算法”这几个中文,训练脚本的路径编码很容易在中间某一步崩掉,建议直接把整个数据目录重命名为pedestrian_data/再跑。

# data.yaml path: /home/user/pedestrian_data # 改成实际绝对路径 train: train/images val: val/images names: 0: person 1: master

names的排列顺序必须和标签文件里的数字ID一一对应。这里如果写反了,person和master会互换,训练不会报错但推理结果全错,属于最难排查的“静默错误”。路径我用绝对路径,避开相对路径在不同工作目录下找不到数据的问题。

3.2 训练命令与必调参数:epochs、imgsz、batch、lr0

YOLOv8是当前最稳的起步选择,环境配置好之后,训练命令本身只有一行。模型用yolov8s而不是yolov8n,行人类别小目标多,n模型轻但漏检率会明显上升;v100这类显存充足的卡上可以放心选s甚至m模型。

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=60 \ imgsz=640 \ batch=16 \ optimizer=AdamW \ lr0=0.0005 \ cache=True

几个参数按这个数据集的实际情况给理由:

参数建议值为什么这样设
epochs60起步30轮能跑通链路但精度没到瓶颈;60轮能看出过拟合拐点,不够再加
imgsz6407203张图足够喂饱模型,640是速度与精度的平衡线;待检目标整体偏小就上1280
batch按显存上限减半16在12G卡上跑yolov8s刚好;batch过小BN层不稳定,loss曲线容易抖
optimizerAdamWYOLOv8默认SGD收敛慢,AdamW在小数据集上表现更稳
lr00.0005默认0.01对预训练权重偏大,行人框普遍小,学习率太高会让回归分支震荡

yolo的损失函数由边界框回归和分类两部分组成;行人类别只有两个,分类压力不大,重心全在框定位上。小目标的中心点和宽高稍微偏一点,IOU损失就会显著变大,所以标签的归一化精度直接反映在loss曲线上。这也解释了为什么第2章要把坐标越界的框先修好——YOLO的标签分配策略(anchor-free匹配)会把所有GT框纳入计算,一个坐标错乱的框会同时污染匹配附近的预测样本。除非已经对yolo环境配置和权重下载很熟,否则第一轮训练建议先不调增强参数,跑通一次拿到baseline,再逐步加mosaic、mixup,而不是一开始就把超参塞满,翻车了都不知道是哪一项干的。

3.3 训练完成后先看这四个输出:混淆矩阵、预测图、PR曲线、loss曲线

训练跑完不要急着看mAP数字,先打开runs/detect/train/目录下的四样东西:confusion_matrix.png、val_batch_pred.jpg、PR_curve.png、results.png,这四张图能判断模型是真学会了还是瞎猜。

混淆矩阵要特别注意一个现象:两类别以上的模型,混淆矩阵的行和列加起来往往不等于100%,原因在于不同类别的预测框可能落在同一个GT附近,或者多个预测框对应一个GT,而矩阵在归一化时只按行或只按列做了一次归一化。看到“yolo混淆矩阵总合不唯一”不要立刻怀疑训练崩了,先确认保存的图是行归一还是列归一;但这不代表所有误差都能忽略,如果person类大量被分类成master,那就是类别难以区分,该考虑合并而不是继续训。

val_batch_pred.jpg直接给真相:绿色框和红色框画在图上,肉眼扫一遍就知道有没有把树干、路灯、商店橱窗里的人影当成人。PR_curve.png观察曲线是否在置信度0.5附近迅速掉头向下,掉得越陡说明大量低置信度预测是误检。results.png里看loss曲线到后期是否平滑下降,Box loss还在震荡就要加训练轮次,但超过80轮还在下降的很少,多数情况是数据集本身有噪声。

4. 行人检测的避坑与排查:遮挡、小目标、类别不平衡、训练崩坏

4.1 标签越界与类别ID错位:现象、原因、解决

现象:训练loss在正常下降的途中突然出现阶梯式跳变,或者前几轮验证集mAP直接是0;可视化训练样本时发现框画在图片外、框中心点在角落里、框尺寸巨大。

原因:数据集从XML/JSON转换时没有做边界裁剪,归一化坐标出现了负数或者大于1的值;也可能是标签的类别ID范围超过data.yaml里names的数量,比如names只写了0和1两个类,txt里却出现ID等于2的行,YOLO在标签匹配时直接跳过或错配。另一种很隐蔽的情况:names的顺序和标注时的类别顺序不一致,但数字都对得上,模型把person和master学反了。

解决:训练前跑一次全量检查脚本,扫描所有标签文件的ID集合和坐标范围:

# check_labels.py import numpy as np from pathlib import Path labels_dir = Path("labels") valid_ids = {0, 1} # 根据data.yaml修改 errors = [] for txt in labels_dir.rglob("*.txt"): with open(txt, encoding="utf-8") as f: for line in f: parts = line.split() if len(parts) != 5: errors.append((txt.name, "列数不等于5")) continue cls = int(parts[0]) vals = list(map(float, parts[1:])) if cls not in valid_ids: errors.append((txt.name, f"非法类别ID {cls}")) if any(v < 0 or v > 1 for v in vals): errors.append((txt.name, f"坐标越界 {vals}")) if errors: print("发现问题:", len(errors)) for err in errors[:20]: print(err) else: print("全部标签合法")

发现问题直接跑第2章的转换脚本重新生成,不要写临时代码打补丁——一次性补丁会把已有的问题覆盖掉,后面再出问题更难看。类别ID错位的修复方法更简单:把data.yaml的names顺序调整成标签里实际用的ID顺序,两处对齐即可。

4.2 远距离小目标行人漏检:imgsz与切片推理

现象:验证集mAP看着不低,但实际测试时距离稍远的行人全部漏掉,或者检测框歪歪扭扭只框住上半身。看PR_curve.png时,召回率在置信度0.7以上突然断层,说明大量远距离目标根本没生成有效预测。

原因:YOLO的主干网络有五次下采样,输入640分辨率时,特征图上最小步长对应的原图区域就是8x8像素。远距离行人可能只有16x24像素,在深层特征图上只占据2x3个格子,语义信息已经被池化层抹掉。这是行人检测数据集里最普遍的问题,尤其监控类场景,目标小还不是少数。

解决:最直接的手段是把训练和推理的imgsz从640提到1280,实测对中远距离行人AP提升最明显;显存不够就先用yolov8n配1280跑一轮,看效果再上大模型。另一个思路是推理阶段做切片,把大图切块后逐块检测再合并结果,常见做法是用sa-hi这类切片推理工具,或者自己写滑窗逻辑。切片推理要注意边界位置的物体被切断,切片之间需要预留重叠区域。这项优化放到最后做——先把训练分辨率提上去,如果部署设备跑不动1280再切片。小目标问题靠硬调置信度阈值没用,模型根本没生成预测,阈值再低也捞不出来。

4.3 “主人”类样本太少导致精度虚高:类别权重与合并策略

现象:训练日志里总体mAP达到65%,看起来不错;但PR_curve.png里person类曲线和master类曲线差距极大,master类的AP可能只有20%。实际用的时候master类几乎全部漏检,模型输出的绝大多数框都是person。

原因:两个类别的标注框数量不在一个数量级,“主人”在7203张图里可能只占几个百分点。YOLO默认按样本数量加权训练,少样本类别在损失函数里占比太小,模型倾向于把一切看起来像人的东西预测成person,因为这样总损失最低。整体mAP被大类撑起来,“精度虚高”就是这么来的。

解决:先明确业务上到底需不需要master这个类别。如果不需要,直接把标签里的master合并到person,模型只输出一类,精度反而更高,推理速度还略快。如果必须保留,第一选择是把master样本做数据增强复制,多拷贝几份到训练集里,配合mosaic、mixup增强;第二选择是在损失里给master类提高权重,YOLOv8里可以通过修改配置文件的类权重参数实现。不要只简单重复master样本几十遍,那会让模型过拟合到少数几个样本上,测试时换个场景直接崩。最终评估必须逐类看AP,不要看平均mAP掩盖问题。

4.4 训练中loss冲高与BN崩溃:参数与环境的双重排查

现象:训练到第20轮左右,loss曲线突然从1.2飙到3.5甚至更大,之后要么回不来,要么直接出现NaN。用TensorBoard或者日志曲线看是单点突变,不是缓慢上升。

原因:yolo训练中bn崩溃最常见的触发点是优化器切换会导致BN统计量剧烈波动,尤其从默认SGD换成AdamW且学习率没降的时候;其次是数据集里混入了损坏图片(解码失败、全黑、全白),这些图片的前向传播产生异常梯度。还有一些外部因素:预训练权重下载不完整、多卡训练时batch size设置不一致、混合精度下loss溢出。

解决:按优先级排查。第一步把lr0降到原来的一半重新训练,排除学习率问题;第二步在训练命令里加cache=True并把batch改成8,排除显存不足和IO异常导致的黑匣子问题;第三步用一段脚本检查数据集的图片是否都能被OpenCV正常解码:

python -c " import cv2 from pathlib import Path bad = [p for p in Path('images').rglob('*.jpg') if cv2.imread(str(p)) is None] print('损坏图片数:', len(bad)) "

有损坏图片直接剔除,同时删掉对应的标签文件。BN崩溃的坑在于它在训练日志里出现得很突然,让人以为模型没救了;实际上一半情况是数据集里有毒样本,换掉就恢复正常。养成一个习惯:每轮训练前把训练log里的loss曲线截个图存到实验目录,出问题时有据可查,不用凭记忆复盘。

5. 用测试集把行人模型压榨到上限:推理、阈值与坏例复盘

5.1 单张图片推理,调置信度阈值而不是改模型

训练完的best.pt先拿几张没见过的场景图跑推理,观察效果再决定下一步:

yolo predict model=best.pt source=test_scene.jpg conf=0.25 iou=0.45

conf是置信度阈值,低于这个值的预测框全部丢弃;iou是NMS时的交并比阈值,值越大越容易把两个重叠框合并成一个。行人密集场景里,先把conf拉到0.4,误检率肉眼可见下降;如果发现漏检,再把conf调回0.2或者更低,看能不能捞回一部分框。如果调到0.1仍然漏检严重,说明模型没学会特征,跟阈值没关系。调阈值的本质是在误检和漏检之间找平衡点,换场景时第一件事是重新搜阈值,而不是改模型重训。

5.2 用验证集统计mAP和每类AP,坏例到底坏在哪

验证集才能给出可信的量化数据,单张图看得再仔细也只是玄学。用命令直接在验证集上评估:

yolo val model=best.pt data=data.yaml batch=32

输出里重点关注两行:mAP50-95和每个类目的AP50。行人类别AP50能达到80%以上才值得去部署,如果只有60%,先别优化推理,回到第4章找原因。验证完把预测错误的图片挑出来,按错误类型归档:遮挡、模糊、远距离、截断、视角罕见,统计每一类占比。这一步能明确告诉你后续要补什么数据——是补充遮挡样本还是提高训练分辨率。一开始直接闷头加训练集的轮次是低效的,数据分布的问题用更多训练算力也治不好。我现在拿到任何数据集都会先花半小时做坏例统计,而不是直接开一轮长训练,这个习惯救过我很多次。

5.3 导出engine做部署前的最终确认,以及按场景剪裁习惯

验证通过后,如果要上NVIDIA设备部署,把模型导出成TensorRT engine格式再跑一次推理:

yolo export model=best.pt format=engine half=True device=0

engine是高度硬件相关的产物,换一张GPU型号必须重新导出,导出后拿同一批验证集图片重新测一遍,确认精度没有因为半精度损失而明显下降。下一步是按业务场景剪裁:只需要检测人的场景建议在训练阶段就合并类别、推理时固定conf=0.35附近;需要区分细分类别的场景,则把conf放在0.25左右给下游留下更多候选框,同时把每类AP的基线记录在实验日志里。版本迭代时只对比同一验证集上的mAP就好。

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

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

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

立即咨询