简介:本资源是一份开箱即用的行人目标检测专用数据集,严格遵循YOLOv5目录结构规范,面向计算机视觉初学者、算法工程师及模型训练实践者,解决小规模场景下人形目标检测模型快速验证与微调的数据需求。压缩包共2000个文件,主体为1999个YOLO格式标注txt文件(含训练集3000张图片对应标签、测试集300张图片对应标签)及1个可视化脚本show.py,所有文件按images/train、labels/train等标准路径组织,无需额外转换即可直接接入YOLOv5训练流程;配套的classes.txt字典与可视化脚本支持即时效果验证,运行后自动绘制边界框并保存结果图。资源大小523.04MB,结构清晰、标注准确、环境覆盖多样,已获356人学习下载,是开展行人检测baseline实验、模型性能对比或课程实训的理想数据基础。
1. 行人检测不是调个参数就能跑通:YOLOv5目录格式数据集为什么卡住90%新手的训练第一步
你手上有几张行人照片,想用YOLOv5跑出一个能框出人的模型——结果train.py报错FileNotFoundError: No labels found in .../labels/train/,或者训练完 mAP 始终为 0,或者验证时满屏乱框。这不是代码写错了,而是数据集结构没对齐 YOLOv5 的硬性契约。YOLOv5 不接受任意命名的图片+txt文件夹,它只认一种目录骨架:images/train/、images/val/、labels/train/、labels/val/四个路径必须存在,且每张xxx.jpg必须在同名xxx.txt中有严格格式的归一化坐标(中心点x,y + 宽高w,h,全部0~1之间),类别索引从0开始。这不是“建议”,是Dataset类里__getitem__方法直接os.path.join(self.img_path, self.label_path)拼路径、torch.load()读取前校验文件存在的刚性逻辑。很多新手卡在第2小时,不是不会写训练命令,而是根本没意识到:YOLOv5 的“数据集”本质是一个带强约束的文件系统协议,而非通用图像集合。本文不讲YOLOv5原理,只聚焦一件事:如何把原始行人图片(哪怕只有20张)零误差地塞进这个协议,让train.py第一次运行就成功加载、不报路径错、不丢样本、不混标签。适合刚下载完 CrowdHuman 或自己手机拍了10张路人的你,也适合被--data mydata.yaml卡住三天、反复重命名文件夹的工程师。
2. 从原始图片到YOLOv5可识别结构:四步落地流程与每个环节的致命细节
2.1 确认原始数据形态:先别急着改名,先看懂你的“原料”
YOLOv5 对数据源不挑食,但必须知道它是什么形态。常见行人数据源有三类:
- 自有拍摄图:手机/监控截图,无标注,需先用 LabelImg 或 CVAT 标注生成
.txt; - 公开数据集(如 CrowdHuman、CityPersons):已提供标注,但格式多为 COCO JSON、XML 或自定义 TXT,绝非 YOLOv5 原生格式;
- 半成品数据集(如某公司内部导出的 CSV+图片):坐标可能是像素值、未归一化、类别名非数字。
提示:用
ls -l images/ | head -5和head -3 labels/000001.txt快速判断。若labels/下是.xml或.json,跳转 2.3;若是空文件夹或报错No such file,说明标注根本没生成,回退到标注工具环节。
2.2 构建 YOLOv5 目录骨架:4个文件夹的创建逻辑与权限陷阱
YOLOv5 要求的最小结构如下(注意大小写和斜杠方向):
my_person_dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/关键细节:
images/和labels/是同级兄弟目录,不是images/labels/;train/和val/必须是子目录,不能是train.jpg和val.jpg这样的文件;- Linux/macOS 下区分大小写,
Images/≠images/;Windows 虽不敏感,但ultralytics代码中Path(img_path).glob("*.jpg")默认小写匹配,建议统一小写; - 若用
mkdir -p images/train labels/val创建,检查权限:ls -ld images/应显示drwxr-xr-x,若为drw-------(无执行权),os.listdir()会报PermissionError,用chmod 755 images/修复。
# 一行创建标准骨架(Linux/macOS) mkdir -p my_person_dataset/{images/{train,val},labels/{train,val}} # 验证结构 tree my_person_dataset -L 2 # 输出应为: # my_person_dataset # ├── images # │ ├── train # │ └── val # └── labels # ├── train # └── val2.3 标注格式转换:从 COCO JSON/XML 到 YOLOv5 TXT 的不可省略步骤
CrowdHuman、CityPersons 等主流行人数据集提供 COCO 格式 JSON。其标注含categories(类别列表)、annotations(每个 bbox 的image_id,category_id,bbox[x,y,w,h])。YOLOv5 要求每个图片对应一个.txt,每行class_id center_x center_y width height(全部归一化到 0~1)。转换核心逻辑:
- 读取 JSON,提取
images列表(含file_name,width,height); - 遍历
annotations,用image_id匹配图片尺寸; - 将
bbox从[x_top_left, y_top_left, w, h]→ 转为中心点坐标:x_center = (x_top_left + w/2) / img_width y_center = (y_top_left + h/2) / img_height w_norm = w / img_width h_norm = h / img_height - 写入
labels/train/xxx.txt,每行0 {x_center} {y_center} {w_norm} {h_norm}(行人统一为 class 0)。
# coco2yolo.py:将 CrowdHuman 的 annotation.json 转为 YOLOv5 格式 import json import os from pathlib import Path def convert_coco_to_yolo(json_path, img_dir, label_dir): with open(json_path) as f: data = json.load(f) # 构建 image_id -> info 映射 img_info = {img['id']: img for img in data['images']} # 创建 label_dir 下所有图片的空 .txt for img in data['images']: txt_path = Path(label_dir) / f"{Path(img['file_name']).stem}.txt" txt_path.parent.mkdir(exist_ok=True) txt_path.write_text("") # 预创建,避免后续写入失败 # 写入标注 for ann in data['annotations']: img_id = ann['image_id'] img = img_info[img_id] w, h = img['width'], img['height'] # COCO bbox: [x, y, width, height] x, y, box_w, box_h = ann['bbox'] # 归一化 x_center = (x + box_w / 2) / w y_center = (y + box_h / 2) / h w_norm = box_w / w h_norm = box_h / h # 行人类别固定为 0 line = f"0 {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n" txt_path = Path(label_dir) / f"{Path(img['file_name']).stem}.txt" with open(txt_path, "a") as f: f.write(line) # 使用示例 convert_coco_to_yolo( json_path="crowdhuman/annotation.json", img_dir="crowdhuman/Images", label_dir="my_person_dataset/labels/train" )逻辑说明:
Path(img['file_name']).stem去掉.jpg/.png后缀,确保图片000001.jpg对应000001.txt;.6f保证浮点精度,避免0.000000导致解析失败;"a"模式追加写入,因一张图可能有多个行人(多个ann)。
2.4 图片与标签的严格配对:文件名一致性检查与自动清洗脚本
YOLOv5 在datasets.py中通过self.img_files = sorted(glob.glob(...))获取图片列表,再用label_path = img_path.replace('images', 'labels').replace('.jpg', '.txt')推导标签路径。这意味着:
images/train/a.jpg→labels/train/a.txt;- 若
images/train/a.jpeg存在,但labels/train/a.txt不存在,该图被静默跳过; - 若
labels/train/b.txt存在,但images/train/b.jpg不存在,训练时IndexError: list index out of range(因self.img_files长度 <self.label_files)。
以下脚本自动清洗不配对文件:
# sync_images_labels.py import os from pathlib import Path def sync_dataset(img_dir, label_dir, exts=(".jpg", ".jpeg", ".png")): img_dir = Path(img_dir) label_dir = Path(label_dir) # 获取所有图片文件名(不含扩展名) img_stems = set() for ext in exts: img_stems.update(p.stem for p in img_dir.rglob(f"*{ext}")) # 获取所有标签文件名(不含扩展名) label_stems = set(p.stem for p in label_dir.rglob("*.txt")) # 找出只在图片中存在、标签缺失的 missing_labels = img_stems - label_stems if missing_labels: print(f"[WARNING] {len(missing_labels)} images missing labels:") for stem in sorted(missing_labels)[:5]: # 只打印前5个 print(f" {stem}") # 自动创建空 .txt(YOLOv5 允许无目标图片,但必须有空文件) for stem in missing_labels: (label_dir / f"{stem}.txt").write_text("") # 找出只在标签中存在、图片缺失的 missing_imgs = label_stems - img_stems if missing_imgs: print(f"[WARNING] {len(missing_imgs)} labels missing images:") for stem in sorted(missing_imgs)[:5]: print(f" {stem}") # 删除孤立标签 for stem in missing_imgs: (label_dir / f"{stem}.txt").unlink() # 使用示例 sync_dataset( img_dir="my_person_dataset/images/train", label_dir="my_person_dataset/labels/train" )参数说明:
exts指定支持的图片格式,避免.JPG大写导致匹配失败;rglob递归查找,兼容子文件夹;write_text("")创建空文件,比touch更跨平台;unlink()安全删除,比os.remove更鲁棒。
3. 配置文件 yaml:为什么 80% 的训练失败源于 data.yaml 的三个隐藏字段
3.1 data.yaml 的最小必要字段:路径、类别数、类别名缺一不可
YOLOv5 通过--data my_person_dataset/data.yaml加载配置。该文件不是可选的,而是数据集的元数据身份证。最简data.yaml必须包含:
train: 训练图片路径(相对于data.yaml文件位置);val: 验证图片路径;nc: 类别数量(行人检测为1);names: 类别名称列表,顺序与nc对应(['person'])。
错误示范(常见翻车点):
train: ./images/train→ 正确;train: images/train→ 缺少./,YOLOv5 解析为绝对路径/images/train;nc: 1→ 正确;nc: '1'→ 字符串,int(nc)报错;names: [person]→ 缺少引号,YAML 解析为变量而非字符串;
# my_person_dataset/data.yaml train: ../images/train # 注意:此文件放在 my_person_dataset/ 下,所以用 ../ 回退一级 val: ../images/val nc: 1 names: ['person']逻辑说明:
train和val是相对于data.yaml所在目录的路径。若data.yaml在my_person_dataset/,而images/也在同级,则写../images/train;若data.yaml放在my_person_dataset/内,images/是其子目录,则写images/train。用ls -l my_person_dataset/data.yaml确认位置。
3.2 路径调试技巧:用 Python 一行验证 data.yaml 是否可被正确解析
在运行train.py前,先用以下代码验证路径是否真实存在且可读:
# test_yaml.py import yaml from pathlib import Path def test_data_yaml(yaml_path): with open(yaml_path) as f: data = yaml.safe_load(f) for split in ['train', 'val']: path = Path(data[split]) if not path.exists(): print(f"[ERROR] {split} path not found: {path.absolute()}") return False if not path.is_dir(): print(f"[ERROR] {split} path is not a directory: {path.absolute()}") return False # 检查是否有图片 imgs = list(path.rglob("*.jpg")) + list(path.rglob("*.png")) if len(imgs) == 0: print(f"[ERROR] {split} directory is empty: {path.absolute()}") return False print(f"[OK] {split}: {len(imgs)} images found") print(f"[OK] nc={data['nc']}, names={data['names']}") return True # 使用 test_data_yaml("my_person_dataset/data.yaml")运行后输出
OK才代表data.yaml无路径问题。若报path not found,说明train:路径写错;若报directory is empty,说明图片没复制到正确位置或扩展名不匹配。
3.3 高级字段:test和download的实际价值与弃用建议
YOLOv5 官方data.yaml模板含test:和download:字段:
test:用于测试集路径,但train.py默认不使用,需手动修改val为test或在val.py中指定;download:是自动下载脚本 URL,对自定义数据集无意义,且易触发网络超时中断训练。
实战建议:删除
test:和download:字段。它们不参与训练流程,反而增加 YAML 解析复杂度。ultralytics代码中data.get('test', data['val'])会 fallback 到val,无需显式定义。
4. 避坑指南:YOLOv5 数据集准备阶段的5个血泪经验
4.1 现象:训练启动后立即报错OSError: image file is truncated
原因:图片文件损坏(如传输中断、SD卡故障),YOLOv5 用cv2.imread()读取时返回None,后续img.shape报错。
解决:批量检查图片完整性:
# Linux/macOS:用 identify(ImageMagick)检查 find my_person_dataset/images -name "*.jpg" -exec identify {} \; 2>/dev/null | grep -v "JPEG" # 或用 Python 脚本 from PIL import Image for img_path in Path("my_person_dataset/images").rglob("*.jpg"): try: Image.open(img_path).verify() except Exception as e: print(f"Corrupted: {img_path} - {e}") img_path.unlink() # 删除损坏文件4.2 现象:训练时mAP@0.5始终为 0.000,loss 不下降
原因:标签文件中坐标超出 [0,1] 范围(如x_center=1.05),YOLOv5 在general.py的xywhn2xyxy()中 clip 到 [0,1],导致 bbox 被压扁为线段。
解决:检查labels/下随机.txt文件,用以下脚本扫描越界值:
for txt_path in Path("my_person_dataset/labels").rglob("*.txt"): with open(txt_path) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) < 5: continue try: x, y, w, h = map(float, parts[1:5]) if not (0 <= x <= 1 and 0 <= y <= 1 and 0 <= w <= 1 and 0 <= h <= 1): print(f"Out of range in {txt_path}:{i+1} -> x={x:.3f} y={y:.3f} w={w:.3f} h={h:.3f}") except ValueError: pass4.3 现象:train.py报错AssertionError: train: No labels found
原因:labels/train/下存在空文件(0字节),YOLOv5 的LoadImagesAndLabels.__init__()中len(labels) == 0触发断言。
解决:删除所有空.txt:
find my_person_dataset/labels -name "*.txt" -size 0c -delete4.4 现象:验证时出现ZeroDivisionError: division by zero
原因:val/目录下图片数极少(如仅1张),YOLOv5 计算 mAP 时分母为0。
解决:确保val/至少有 50 张图片(建议占总数 10~20%)。用wc -l my_person_dataset/images/val/*.jpg | tail -1统计。
4.5 现象:训练日志显示2000 images, 2000 labels,但val阶段2000 images, 0 labels
原因:labels/val/下文件名与images/val/不一致(如IMG_001.jpgvsimg_001.txt),大小写或下划线差异。
解决:统一文件名:
# Linux:批量小写重命名 for f in my_person_dataset/images/val/*.jpg; do mv "$f" "$(dirname "$f")/$(basename "$f" | tr '[:upper:]' '[:lower:]')"; done for f in my_person_dataset/labels/val/*.txt; do mv "$f" "$(dirname "$f")/$(basename "$f" | tr '[:upper:]' '[:lower:]')"; done5. 验证与可视化:用 detect.py 和 utils.plots 看懂你的数据集是否真正就绪
5.1 用 detect.py 做端到端数据流验证:不训练,只跑推理链
detect.py可加载训练好的权重(如yolov5s.pt)对images/val/进行推理,这是检验数据集是否“活”的最快方法:
python detect.py \ --weights yolov5s.pt \ --source my_person_dataset/images/val \ --data my_person_dataset/data.yaml \ --conf 0.25 \ --save-txt \ --save-conf--save-txt: 在runs/detect/exp/labels/生成预测.txt,格式与训练标签一致;--save-conf: 在runs/detect/exp/labels/的.txt中保留置信度,便于分析漏检/误检;- 若输出
0/2000(0张图处理),说明--source路径无图片;若报No labels found,说明--data指向错误。
关键观察点:
runs/detect/exp/下是否生成图片?labels/下是否生成.txt?打开一个.txt,看内容是否为0 0.5 0.5 0.2 0.3 0.95(class x y w h conf)?若有,证明数据集路径、格式、读取全链路通畅。
5.2 可视化标签真值:用 plot_one_box 直接画出 labels/train/ 中的标注
YOLOv5 自带utils.plots模块,可将labels/train/xxx.txt叠加到images/train/xxx.jpg上:
# visualize_labels.py import cv2 from pathlib import Path from utils.plots import plot_one_box def visualize_labels(img_dir, label_dir, save_dir, conf_thres=0.0): save_dir = Path(save_dir) save_dir.mkdir(exist_ok=True) for img_path in Path(img_dir).rglob("*.jpg"): label_path = Path(label_dir) / f"{img_path.stem}.txt" if not label_path.exists(): continue img = cv2.imread(str(img_path)) h, w = img.shape[:2] # 读取标签 with open(label_path) as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls, x, y, w_norm, h_norm = map(float, parts[:5]) # 归一化坐标转像素 x1 = int((x - w_norm/2) * w) y1 = int((y - h_norm/2) * h) x2 = int((x + w_norm/2) * w) y2 = int((y + h_norm/2) * h) plot_one_box([x1, y1, x2, y2], img, label=f"person {cls:.0f}", color=(0,255,0), line_thickness=2) cv2.imwrite(str(save_dir / f"vis_{img_path.name}"), img) visualize_labels( img_dir="my_person_dataset/images/train", label_dir="my_person_dataset/labels/train", save_dir="my_person_dataset/visualize_train" )运行后my_person_dataset/visualize_train/下生成带绿色框的图片。这是你数据集的“X光片”:若框完全偏离行人(如框在天空或地面),说明标注坐标计算错误;若框严重变形(宽高比失真),说明归一化时w/h用反了。
5.3 统计分析:用 pandas 透视数据集分布,发现隐性偏差
行人检测常因姿态、遮挡、尺度不均导致性能瓶颈。用以下脚本生成统计报告:
# analyze_dataset.py import pandas as pd import numpy as np from pathlib import Path def analyze_labels(label_dir): records = [] for txt_path in Path(label_dir).rglob("*.txt"): with open(txt_path) as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls, x, y, w, h = map(float, parts[:5]) # 计算面积(归一化面积) area = w * h # 计算长宽比 ratio = w / h if h > 0 else 0 records.append({ 'file': txt_path.stem, 'class': int(cls), 'area': area, 'ratio': ratio, 'x': x, 'y': y }) df = pd.DataFrame(records) print("=== Dataset Statistics ===") print(f"Total boxes: {len(df)}") print(f"Average area: {df['area'].mean():.4f} (range: {df['area'].min():.4f}-{df['area'].max():.4f})") print(f"Aspect ratio median: {df['ratio'].median():.3f} (range: {df['ratio'].min():.3f}-{df['ratio'].max():.3f})") print(f"Coordinate distribution:\n{df[['x','y']].describe()}") # 检测极端值 small_boxes = df[df['area'] < 0.001] if len(small_boxes) > 0: print(f"[WARNING] {len(small_boxes)} boxes with area < 0.001 (likely noise or annotation error)") return df df = analyze_labels("my_person_dataset/labels/train")输出示例:
=== Dataset Statistics === Total boxes: 12480 Average area: 0.0245 (range: 0.0001-0.3210) Aspect ratio median: 0.421 (range: 0.052-2.103) Coordinate distribution: x y count 12480.0 12480.0 mean 0.512 0.498 ...这告诉你:若
area平均值 < 0.005,说明大量小目标,需开启mosaic或multi-scale;若x均值远离 0.5,说明行人总在画面一侧,需数据增强中的random_perspective。
6. 进阶技巧:用自定义 DataLoader 替换默认逻辑,绕过 YOLOv5 的路径硬编码
6.1 为什么需要自定义 DataLoader:当你的数据不在标准路径时
YOLOv5 默认假设images/和labels/同级,但实际场景中:
- 数据分散在不同磁盘(
/data1/images/,/data2/labels/); - 图片是视频帧,按
video_id/frame_id.jpg组织,无法简单用stem匹配; - 标签是数据库查询结果,需实时生成,不存为
.txt。
此时硬改datasets.py风险高(升级后覆盖),更安全的方式是继承torch.utils.data.Dataset,重写__getitem__。
6.2 构建 PersonDataset:支持任意路径、动态标签、多尺度采样
# person_dataset.py import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import cv2 from pathlib import Path class PersonDataset(Dataset): def __init__(self, img_paths, label_func, img_size=640, augment=False): """ :param img_paths: List[str], 所有图片路径 :param label_func: Callable[[str], List[List[float]]], 输入图片路径,返回 [[cls,x,y,w,h], ...] :param img_size: int, 输入尺寸 :param augment: bool, 是否启用增强 """ self.img_paths = img_paths self.label_func = label_func self.img_size = img_size self.augment = augment def __len__(self): return len(self.img_paths) def __getitem__(self, index): # 读图 img_path = self.img_paths[index] img = cv2.imread(str(img_path)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 获取标签 labels = self.label_func(img_path) # [[0,0.5,0.5,0.2,0.3], ...] labels = np.array(labels, dtype=np.float32) if labels else np.zeros((0,5), dtype=np.float32) # 增强(简化版,实际用 albumentations) if self.augment: # 随机缩放、裁剪、HSV增强 pass # 归一化到 0~1 img = img.astype(np.float32) / 255.0 # 调整尺寸 img = cv2.resize(img, (self.img_size, self.img_size)) img = torch.from_numpy(img).permute(2,0,1) # HWC -> CHW return img, torch.from_numpy(labels) # 使用示例:从数据库动态获取标签 def db_label_func(img_path): # 伪代码:根据 img_path 查询 PostgreSQL # SELECT class_id, x_center, y_center, w, h # FROM annotations WHERE image_path = %s; return [[0, 0.5, 0.5, 0.2, 0.3]] # 返回示例 # 构建数据集 img_list = list(Path("/data1/person_images").rglob("*.jpg")) dataset = PersonDataset( img_paths=img_list, label_func=db_label_func, img_size=640, augment=True ) # 直接用于 DataLoader dataloader = torch.utils.data.DataLoader(dataset, batch_size=16, shuffle=True)优势:完全脱离
images//labels/目录约束;label_func可对接数据库、API、甚至大模型生成标注;__getitem__中可插入任意预处理逻辑(如去雾、超分)。
6.3 集成到 YOLOv5 训练流程:替换 train.py 中的数据加载部分
YOLOv5 的train.py中,数据加载由create_dataloader()函数控制。找到该函数调用处(约第350行),将其替换为:
# 在 train.py 开头添加 from person_dataset import PersonDataset # 替换原 create_dataloader(...) 调用 # 原代码: # train_loader = create_dataloader(train_path, ...)[0] # 新代码: img_list = list(Path(opt.data).parent / "images" / "train").rglob("*.jpg") # 或你的路径 dataset = PersonDataset( img_paths=list(img_list), label_func=lambda p: load_yolo_labels(p), # 你的标签加载函数 img_size=opt.imgsz, augment=True ) train_loader = torch.utils.data.DataLoader( dataset, batch_size=opt.batch_size, shuffle=True, num_workers=opt.workers, collate_fn=lambda x: tuple(zip(*x)) # 保持 img, labels 分离 )这样,你既复用了 YOLOv5 的训练引擎(损失计算、优化器、日志),又彻底解耦了数据加载逻辑。
我做行人检测项目时,曾因 CrowdHuman 的ignore区域(遮挡行人)未过滤,导致 mAP 虚高 5%,后来在label_func中加入if ann['iscrowd'] == 0:过滤才回归真实。数据集不是静态文件夹,而是你对现实世界的建模入口——它的结构、分布、噪声,直接决定模型的天花板。希望帮到你。
本文还有配套的精品资源,点击获取