简介:行人目标检测数据集(YOLOV5目录格式)面向目标检测开发者和入门学习者,聚焦生活场景中的单一类别person识别,可应用于安防监控、智能驾驶、人流量统计等任务,尤其适合需要快速搭建行人检测训练集的学生和工程师,省去自行采集与标注的繁琐流程。资源包共2000个文件,以1999个YOLO格式txt标注文件为核心,另含1个Python可视化脚本,整体压缩包约523MB。txt文件保存边界框类别与坐标信息,按YOLOv5的train/val目录结构划分训练集与验证集,并附类别字典文件,可直接用于YOLOv5系列框架训练,无需额外格式转换。py脚本支持随机传入图片绘制检测框,便于直观核查标注质量和调试数据。目前已有356人浏览学习,既可作为目标检测项目的数据基础,也能用来学习YOLO标注文件的具体规范。
1. 行人目标检测数据集,为什么要先盯住 YOLOV5 目录格式
真实的起步场景往往是这样的:你手里有一批行人图片,想用 YOLOv5 训练自己的行人检测模型,但网上的开源标注五花八门——COCO 给的是 JSON,CrowdHuman 是一堆密集框 txt,LabelImg 导出又是 XML。所谓“行人目标检测数据集”,交付时几乎都收敛成同一种形态:YOLOV5 目录格式。它要求 images 与 labels 双目录、同名 txt、归一化坐标,再配一份 data.yaml。这篇文章写给被格式转换劝退的人:目录格式到底怎么拆、COCO 和 CrowdHuman 怎么转、自建数据从哪里开始,以及你一定会撞上的几个坑。看完你能照着把任意行人数据整理成规范目录格式,并躲开最常见的翻车点。
2. YOLOV5目录格式拆解:一张行人照片怎么变成训练样本
把散落的行人图片整理成目标检测数据集,本质就是让每张图和它的标注在同一个命名规则下对齐。YOLOV5 目录格式的核心假设只有两条:图片与标签同名同层级,标签用归一化坐标写进 txt。所有转换工作,最后都收敛到这两条。
2.1 images 与 labels 双目录:文件如何一一对应
YOLOV5 目录格式最直观的形态是一棵双目录树:dataset 下面平级放 images 和 labels,各自再按 train/val/test 分子目录。训练脚本靠文件名匹配建立对应关系:images/train/street_001.jpg的标签一定在labels/train/street_001.txt。这个“一定”意味着,拷贝图片时顺手改了文件名而标签没有同步改,那一张图就等于没有监督信号,训练时会被静默跳过。
dataset ├── images │ ├── train │ │ └── street_001.jpg │ ├── val │ │ └── street_002.jpg │ └── test │ └── street_003.jpg └── labels ├── train │ └── street_001.txt ├── val │ └── street_002.txt └── test └── street_003.jpg上面我故意留了一个错误:labels/test/street_003.jpg应该是street_003.txt。标签文件必须是 txt,哪怕原图是大写.JPG或带空格的命名,标签后缀也固定为.txt,文件名主体与图片完全一致。常见翻车现场是同一份数据里出现Image_001.JPG、Image_001.jpg、Image_001.txt三套命名,最后大量图片没有标签可用。
labels/train/street_001.txt的内容长这样,一行一个目标:
0 0.486328 0.415740 0.089844 0.583333这一行的参数含义要先说死:第一个数字是类别 id,行人单类场景固定为 0;如果数据集里除了行人还想分骑手、背包客,那就按 0、1、2 依次排。后面四个分别是归一化中心点 x、中心点 y、归一化宽度、归一化高度,全部是 0 到 1 的小数。训练脚本用这五个数在图片上恢复检测框,所以一旦坐标不是归一化值,轻则框画到画外,重则训练直接崩。
2.2 归一化坐标为什么是行人检测的生命线
行人图片尺寸极不统一,监控截图常常是 1920×1080,手机随手拍又是 4032×3024。像素坐标在不同分辨率下无法混合训练,而归一化坐标把框的位置和大小折算成相对值,让跨分辨率训练成为可能。这也是为什么写转换脚本时,必须先拿到图片真实宽高,再去做除法,不能在某个固定分辨率下硬套。
下面这段脚本用来复查已有标注是否规范,做法是把归一化坐标还原成像素框并画出来。这一步很值得做,因为训练前先肉眼确认标注质量,比训练完再回头查 loss 快得多。
import cv2 img_path = "dataset/images/train/street_001.jpg" label_path = "dataset/labels/train/street_001.txt" img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) if not (0 <= cls <= 1): print("class id 越界:", cls) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check.jpg", img)这段脚本逻辑很简单:读取图片尺寸,把归一化中心点和宽高换算回像素坐标,再用 OpenCV 画矩形框。复查时重点关注两件事:框有没有包住行人主体,框是否超出图片边界。注意w和h必须来自当前图片的 shape,不能用一个固定尺寸。常见错误是先把图片 resize 到统一大小再做除法,实际上 YOLOv5 训练时会自己把输入缩放到img参数指定尺寸,标签始终按原图归一化值参与运算,转换端只要保证归一化正确即可。
有人会问:坐标写成0.486328这种 6 位小数够不够用?够用。YOLOv5 在数据增强阶段会对坐标做随机缩放和旋转,归一化坐标跟着图片变换走,精度保留到 1e-6 已经远高于像素级需求。
2.3 目录结构决定后续:train/val/test 怎么摆
目录格式的第三个隐藏点在于划分。YOLOv5 默认把 train 和 val 的路径写在 data.yaml 里,路径本身不是写死的,但目录结构直接决定 data.yaml 怎么写。常见两种摆法:一种把 dataset 作为独立根目录,data.yaml 里写train: dataset/images/train;另一种直接在 yolov5 工程目录下建 images 与 labels,data.yaml 里写train: images/train。两者都能跑,差别在于相对路径的基准——YOLOv5 以运行 train.py 的当前目录为基准,而不是以 data.yaml 所在位置为基准。
我一般把数据集放在 yolov5 工程外,data.yaml 里用相对路径指向,并固定用同一套 train/val/test 划分。不要每次训练前临时跑一个切分脚本,那是给自己埋雷:今天随机切分、明天又随机切分,验证集和训练集之间可能混入同一段视频的相邻帧,指标虚高不说,模型换场景就漏检。
提示:目录格式的价值就是让划分结果可见、可版本管理。train/val/test 体现在目录上,而不是写在某个没人维护的切分代码里,才算一份能交付的数据集。
3. 从 COCO/CrowdHuman 转出行人数据:转换脚本与三个边界坑
大多数人不缺标注数据,缺的是把公开数据转成 YOLOV5 目录格式的方法。目标检测论文里公认的行人数据源就那几个,本节讲怎么把它们变成能直接训练的目录格式,以及转换时最容易被忽略的三个边界问题。
3.1 值得转的三个开源来源:COCO 行人子集、CrowdHuman、BDD100K
COCO2017 是绕不开的起点。它提供 person 类别,JSON 结构完整,图片质量高,适合做行人检测的底座数据。CrowdHuman 是专门为密集行人设计的,一张图里几十上百人,带 head 和 visible 两种框,对遮挡和密集场景特别有价值,但标注格式是 LTRB 而不是 COCO 式 bbox。BDD100K 是自动驾驶场景,行人、骑手分开标,视角贴近车载摄像头,缺点是类别定义和 COCO 不是一套,转换时要自己维护映射表。
这三个来源的选型逻辑很简单:COCO 打底保证泛化,CrowdHuman 补密集遮挡,BDD100K 补开车视角。如果只做监控场景,COCO 加一部分 CrowdHuman 就够;如果做道路场景,再加 BDD100K。目标检测论文里的 SOTA 结论大多建立在这类公开数据上,但你要训练自己的落地方案,必须先把它们转成同一套目录格式。
3.2 把 COCO JSON 转成 YOLOV5 目录格式:转换代码
COCO 的标注文件是instances_val2017.json这种结构,里面images数组记录图片 id、文件名、宽高,annotations数组记录每个目标的 category_id 和 bbox。下面这段脚本只提取 person 类,并转成 YOLOV5 目录格式:
import json import shutil from pathlib import Path img_dir = Path("val2017") ann_file = "instances_val2017.json" out_img = Path("dataset/images/train") out_lbl = Path("dataset/labels/train") PERSON_ID = 1 # COCO 中 person 的 category_id 固定为 1 out_img.mkdir(parents=True, exist_ok=True) out_lbl.mkdir(parents=True, exist_ok=True) coco = json.load(open(ann_file)) img_id2name = {im["id"]: im["file_name"] for im in coco["images"]} img_id2wh = {im["id"]: (im["width"], im["height"]) for im in coco["images"]} anns_by_img = {} for ann in coco["annotations"]: if ann["category_id"] != PERSON_ID: continue x, y, w, h = ann["bbox"] if w <= 0 or h <= 0: continue anns_by_img.setdefault(ann["image_id"], []).append((x, y, w, h)) for img_id, boxes in anns_by_img.items(): if not boxes: continue src = img_dir / img_id2name[img_id] if not src.exists(): continue shutil.copy(src, out_img / img_id2name[img_id]) label_name = Path(img_id2name[img_id]).stem + ".txt" W, H = img_id2wh[img_id] with open(out_lbl / label_name, "w") as f: for x, y, w, h in boxes: cx = (x + w / 2) / W cy = (y + h / 2) / H bw = w / W bh = h / H f.write(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")几个参数说明。PERSON_ID = 1是 COCO 的固定值,不是随便选的;如果你要连骑手一起转,就得自己建一个 category_id 到新类别的映射表,而不是沿用原 id。COCO 的 bbox 格式是[x, y, w, h],x 和 y 是左上角坐标,所以中心点要加上 w/2 和 h/2。输出时统一写成0开头,因为这里只保留行人一类。txt 文件名用Path(img_id2name[img_id]).stem + ".txt"生成,避免手工拼字符串时踩大小写坑。
3.3 转换边界:类别标签、黑边坐标、无效框过滤
转换脚本能跑通不算完,真正决定数据集质量的是三个边界处理。
第一个是类别标签重新编号。如果最后只留 person 一类,输出固定写0没问题;但如果想要“行人 + 骑手”两类,就得先扫一遍 COCO JSON 里所有出现的 category_id,再做映射。最忌讳的是直接拿 COCO 的 category_id 当 YOLO class id,COCO 的 person 是 1、bicycle 是 2,但 YOLOv5 要求 class id 从 0 连续编号,中间空一个 1 会出现什么后果?训练时 nc 和实际类别数对不上,loss 直接起飞。
第二个是坐标越界。COCO 的部分 bbox 贴边,x 可能等于图片宽度减一,除以 W 之后加上 w/2 可能得到1.00001。这种越界值 YOLOv5 训练时会当作无效框忽略,但严重时会导致一张图一个正样本都没有。处理办法是转换时对 cx、cy 做一次 clamp 到[0, 1]区间,同时对宽高小于 1 像素的框直接丢弃。
第三个是 CrowdHuman 的重复框。它同一个人有 head 和 visible 两套框,常见的错误是把两套都转成独立 person,等于给一个行人发了两份标签。我一般只取 visible 框,并额外做一次 IoU 去重,把重合度超过 0.8 的框保留较大的那个。这属于血泪经验,别问是怎么知道的。
4. 自建行人数据集的完整流水线:采集、标注、切分与 data.yaml 闭环
开源数据再全,也覆盖不了你本地的真实场景——比如商场闸机视角、学校门口早晚高峰、停车场夜间灯光。这种时候就得自建数据集。本节给出从采集到 data.yaml 的完整流程,其中切分脚本是直接能抄的。
4.1 采集阶段先想清楚 scale:行人检测要覆盖哪几档尺度
行人检测最典型的难点是尺度跨度大。一个 1080p 监控画面里,远处行人可能只有 40 像素高,近处行人占半帧。如果采集时只顾近景,模型学不会小目标;只拍远景,近处细节又欠拟合。采集阶段就要按距离分档:近景、中景、远景各占三分之一左右。
这背后有明确的量化依据。YOLOv5 在img=640输入下,高度小于 16 像素的行人几乎不会被任何锚框匹配为正样本,等于白标。所以自建数据集时,低于 20 像素的“行人”可以直接不标,标了反而是噪声。另外要注意的是相机高度:同一批数据如果机位差异太大(无人机俯拍加行人手机平拍),模型会试图同时拟合两种视角分布,收敛变慢。我一般建议一个项目主体上用同一种机位,再少量掺入其他视角做增强。
4.2 标注约定:person 的框到底画到哪
标注行人框最容易吵的一处是遮挡。一个人被汽车挡住一半,到底框全身还是只框可见部分?我的约定是:只框可见部分。顶部取可见头部最高点,底部取可见身体最低点,左右取可见边界。这样训练出来的模型学的是“我能看到的部分”,而不是去脑补被遮挡的腿。
标注工具用 LabelImg 或 labelme 都行。LabelImg 选择 YOLO 格式导出时,会自动生成一个按类别顺序排列的classes.txt,这一点很方便;但如果你手工写标签,就必须保证类别顺序与 classes.txt 一致,否则同样一张标注可能对应完全不同的语义。自建数据时别贪类别多,先只标 person 一类跑通,后续再扩骑手、推车之类。
4.3 切分脚本:用随机种子保证验证集不泄漏
切分是整个自建流程里最值得写脚本的一步。常见的错误是用random.shuffle逐张打乱,然后切分,但这种做法会把同一段视频的相邻帧同时分到 train 和 val——训练集见过“前一帧”,验证集里是“后一帧”,指标虚高,换场景就露馅。
import random import shutil from pathlib import Path random.seed(42) # 固定种子,保证复现 # 假设原始图片按场景目录组织: raw/场景A/xxx.jpg all_imgs = sorted(Path("raw").glob("*/*.jpg")) random.shuffle(all_imgs) n_val = int(len(all_imgs) * 0.15) val_files = all_imgs[:n_val] train_files = all_imgs[n_val:] for split, files in [("val", val_files), ("train", train_files)]: out_img = Path(f"dataset/images/{split}") out_lbl = Path(f"dataset/labels/{split}") out_img.mkdir(parents=True, exist_ok=True) out_lbl.mkdir(parents=True, exist_ok=True) for img in files: # 同场景图片的标签按约定放在 raw/场景A/xxx.txt lbl = img.with_suffix(".txt") if not lbl.exists(): continue shutil.copy(img, out_img / img.name) shutil.copy(lbl, out_lbl / lbl.name)这个脚本有两个关键参数。random.seed(42)固定种子,让切分可复现,不会今天跑出一个版本、明天跑出另一个版本。Path.glob("*/*.jpg")限定原始数据按“场景目录/图片”组织,切分时以单张图为单位但保留场景目录的语义,你可以在切分前先按场景目录分组再随机,效果更稳。脚本里img.with_suffix(".txt")是查找同名标签的最稳妥方式,比字符串拼接更不容易踩大小写坑。
4.4 写 data.yaml 与 class_names:和目录格式闭环
最后一步是写data.yaml,让 YOLOV5 能找到数据。目录格式到这里才真正闭环:目录结构、类别定义、训练路径全部对齐。
# dataset/data.yaml train: dataset/images/train val: dataset/images/val nc: 1 names: - personnc是类别总数,names是类别名列表,顺序必须和标签文件里的 class id 一一对应。person排在 names 第一项,对应 id 0;如果加了骑手放在第二位,就是 id 1。路径写相对路径时,基准是 train.py 的运行目录,所以如果你把 yolov5 工程和 dataset 放在同一级,上面写法没问题;如果不在,要么写绝对路径,要么调整相对层级。
注意:很多人训练时发现“找不到 labels”,十有八九是 data.yaml 里的路径写到了 images 目录,而 labels 目录不存在于同一层级。YOLOv5 会尝试在图片路径同级或上级找 labels,路径错一层的表现是 cache 构建时报大量 missing。
5. 行人数据集避坑清单:5 个真实翻车现场与排查顺序
目录格式看着简单,真正跑起来翻车的点一个比一个隐蔽。下面五个问题是我做数据集时最常遇到的,按出现频率排序,每个都按现象、原因、解决三个层面讲清楚。
5.1 现象:训练刚起步 loss 直接起飞,日志里 class 数量对不上
训练脚本读到nc=1,但标签文件里出现了class id = 1,模型在类别空间里越界,loss 波动剧烈甚至 NaN。原因基本只有一个:类别编号映射错了。比如把 COCO 的 category_id 直接当 YOLO class id 写入标签,或者多类数据集里漏了一个类没写进 names。
解决方法是训练前扫一遍所有标签文件里的最大 class id,确保它小于 data.yaml 里的nc:
cat dataset/labels/train/*.txt | awk '{print $1}' | sort -n | uniq这个命令输出所有标签文件里实际出现的类别号,看一眼就知道是不是 0 到 nc-1。如果出现1而你配置的 nc=1,必然翻车。
5.2 现象:验证集 mAP 很高,部署到新场景全漏检
这是最容易误判的一个坑。mAP 0.85 给了你信心,结果换个商场、换条街道,漏检率直线上升。原因不是模型差,而是划分泄漏:同一段视频的相邻帧被随机切分进了 train 和 val,验证集里全是“看过”的样本。
解决方法是按场景或按视频片段分组切分,而不是逐张打乱。上一节的切分脚本就是为此设计的。另外,验证集里最好刻意留出 10% 的“陌生场景”数据,模拟部署时的分布偏移,指标才有参考价值。
5.3 现象:标签坐标出现 1.0001,训练时正样本大量减少
框贴边时,(x + w/2) / W可能得到略大于 1 的值。YOLOv5 在构建标签时会过滤越界框,过滤多了,一张图可能只剩一两个正样本,模型学不动。
原因是转换脚本做了除法但没做边界截断。解决方法是转换时对五个值统一clamp到合法区间,同时对极端小框做过滤:
cx = min(0.999, max(0.001, cx)) cy = min(0.999, max(0.001, cy)) bw = min(0.999, max(0.001, bw)) bh = min(0.999, max(0.001, bh)) if bw < 0.003 or bh < 0.003: continue这里的0.003是对 640 输入下约 2 像素的过滤阈值,你可以按最小目标尺寸调整。写一段这样的小工具脚本,全量刷一遍标签目录,比训练时看日志猜原因快得多。
5.4 现象:训练日志大量 warning,图片与标签文件名匹配不上
YOLOv5 构建 cache 时会报告找不到标签,但照样继续跑。你以为是正常提示,实际上已经有一半图片变成了无监督样本。原因很常见:图片扩展名大小写不统一(.JPG与.jpg),或者复制文件时改了图片名但没同步标签名,再或者标签文件里带了多余的空格和 BOM 头。
解决方法是转换完成后来一次硬校验:每个 images 下的文件,都必须在 labels 对应位置存在同名.txt文件。校验用一条 shell 命令就能排查:
for img in dataset/images/train/*.jpg; do base="${img##*/}" lbl="dataset/labels/train/${base%.*}.txt" [ -f "$lbl" ] || echo "missing: $img" done有输出就是有问题。这个检查跑一遍花不了几秒,值得形成习惯。
5.5 现象:密集行人场景闸机级漏检,NMS 把相邻框一起打压
单张图里几十个行人彼此重叠时,YOLOv5 默认的 NMS 阈值会认为相邻框是重复检测,把其中一些人直接抑制掉。这不是数据集标签错,而是密集场景下标签分布与后处理参数不匹配。
解决分两步。第一步,数据层面保证密集场景的样本量足够,并在标注时对完全重叠的两个行人分别画框,即使它们 IoU 很高。第二步,训练和推理阶段适当调低 NMS 阈值到 0.35 左右,让模型保留更多候选框;也可以在训练时用 YOLOv5 的默认超参数先跑通,再针对密集场景小范围调优。数据集本身的目录格式没有错,但标签里每个目标都是独立个体,这一点要传导到标注人员那里。
6. 先用 200 张图跑通最小闭环:目录格式合格与否一晚上见分晓
最后一个落地技巧:不要一上来转几千张图再训练。正确做法是先拿 200 张图(150 train、50 val)跑一个最小闭环,确认目录格式、标签坐标、训练链路都没问题,再大规模扩数据。
最小闭环包括三步。第一步,用前面的切分脚本从原始行人图片里抽出 200 张,生成 train 和 val 两个目录。第二步,跑一个短训练,看 loss 是否稳定下降:
cd yolov5 python train.py --data ../dataset/data.yaml --weights yolov5s.pt \ --img 640 --epochs 50 --batch 8第三步,训练结束后跑一次验证推理,把检出的框画出来,和原图标注做视觉对比:
python detect.py --weights runs/train/exp/weights/best.pt \ --source ../dataset/images/val --save-txt --save-conf这三个命令是 YOLOv5 的标准流程,不需要额外代码。关键判断标准有两个:loss 在前 20 个 epoch 内持续下降,而不是横盘不动;detect 输出的框基本贴合行人轮廓,没有大量偏移、漏检或重复框。
我自己的血泪教训是:有一回转完 CrowdHuman 直接全量导入训练,跑了一整夜,第二天发现因为标签扩展名大小写不一致,一半图片的标签根本没被读进去。那以后我固定了这套流程——任何数据集先抽 200 张跑通闭环,确认标签路径、坐标范围、类别编号都无异常,再谈扩量。目录格式合格的标志从来不是目录树长得好看,而是这份 YOLOV5 目录格式能让训练脚本一遍通过、验证结果与标注一致。先小样本验证再扩量,能帮你省掉大量返工时间。希望帮到你。
本文还有配套的精品资源,点击获取