简介:这份人类目标检测数据集面向计算机视觉开发者与目标检测初学者,共456张标注图片,按训练集、验证集、测试集划分,全部采用YOLO格式标注,包含边界框坐标与类别标签,实际采集场景覆盖监控、自动驾驶、零售分析及无人机航拍等多样化环境,可显著提升模型在不同光照和背景条件下的泛化能力。资源共914个文件,主要为456张jpg图片及对应的456个txt标注文件,另附yaml配置与docx说明文档,合计约25.15MB,结构清晰、开箱即用,可直接投入YOLO、Faster R-CNN等主流框架训练。已有85人学习,适合需要快速获得高质量人员检测数据以验证模型或落地安防、行人检测项目的开发者使用。
1. 人类目标检测数据集:先确认边界,再决定怎么用
目标检测的工业场景里,漏一个人进危险区,比误检一辆叉车更让人头疼。拿到「人类目标检测数据集.zip」这种打包资源,我第一反应不是解压丢进 YOLO,而是先确认三件事:标的是单类 person 还是连带物体类;标注格式是 VOC/COCO 还是原生 txt;图片场景跟现场像不像。这份数据的价值在「人」和「人与物共现」,适合做人员闯入检测、人流统计、工位人员定位。它不是黑匣子,决定训练效果的是格式转换、类名对齐和负样本清洗。这篇笔记就按这三步落地成能复现的流程。
2. 解压与标注格式处理:先看清布局,再写转换脚本
2.1 目录结构先看三样东西:train/val 划分、标注格式、文件名后缀
这类打包数据最常见的坑,不是标注内容,而是目录结构本身。解压后我一般会先跑一遍目录树,而不是直接写训练脚本:
unzip -q human_det_dataset.zip -d human_det find human_det -maxdepth 3 -type d | sort常见做法是 images 和 annotations 两个目录平铺,也可能直接分好 train/val 两个子目录。如果所有图片混在一起、标注文件堆在一个文件夹里,就说明这份资源把划分工作留给了你。我的习惯是先划分、后转格式,顺序不能反。原因是:如果先把 COCO/XML 转成 YOLO txt 再划分,容易出现图片复制过去了、txt 没跟上的情况,而且你还要回头检查两份清单是否一一对应,纯属给自己加活。
这里有一个工业数据特有的坑:视频抽帧数据集里,相邻帧之间高度相似。如果随机按 8:2 划分,很多「相似帧」会同时出现在训练集和验证集,最后验证 mAP 虚高,落地时才发现模型根本没见过真实视角。我处理这类数据的做法是先按视频片段分组,再整段划分,保证训练集和验证集不存在连续的上下文关联。
下面是固定随机种子的划分脚本,按图片后缀匹配同名 txt:
import os import random import shutil random.seed(42) img_src = "images" label_src = "labels" train_img, val_img = "train/images", "val/images" train_lab, val_lab = "train/labels", "val/labels" for d in [train_img, val_img, train_lab, val_lab]: os.makedirs(d, exist_ok=True) names = [n for n in os.listdir(img_src) if n.lower().endswith((".jpg", ".jpeg", ".png"))] random.shuffle(names) split_idx = int(len(names) * 0.8) for i, name in enumerate(names): img_dst = train_img if i < split_idx else val_img lab_dst = train_lab if i < split_idx else val_lab shutil.copy(os.path.join(img_src, name), os.path.join(img_dst, name)) label = os.path.splitext(name)[0] + ".txt" if os.path.exists(os.path.join(label_src, label)): shutil.copy(os.path.join(label_src, label), os.path.join(lab_dst, label))random.seed(42)保证每个人跑出来的划分结果一致,不然你和我复现同一个项目时 train/val 永远对不上。split_idx = int(len(names) * 0.8)是把 20% 的样本留给验证,工业场景我一般不会把验证集压到 10% 以下,否则类别不均衡时验证损失波动会非常大。标签文件用splitext去掉后缀再拼.txt,是为了规避图片是.JPG而标签是.txt这类大小写不一致的问题。
2.2 从 COCO JSON 转到 YOLO TXT:类别映射是第一道关口
这个数据集如果带的是 COCO 格式的annotations.json,训练 YOLO 前必须转成一行一个标注的 txt。YOLO 的标签格式是固定的:class cx cy nw nh,其中中心点坐标和宽高都相对图片宽高做了归一化。这条规则没有例外,即使原标注是 VOC/COCO,喂给 YOLO 前也只有这一种形式。
转换的第一道关口是类别映射。COCO 自己的 person 类别 id 是 1,但 YOLO 要求的类别 id 必须是从 0 开始的连续索引,而且每个类在data.yaml里的顺序要和这里保持一致。你可以在 annotations.json 的categories字段里看到原始 id 列表。下面这段脚本把 COCO bbox 转成 YOLO txt,并顺手做了坐标防御:
import json import os def coco_to_yolo(json_path, out_dir, cat_map): os.makedirs(out_dir, exist_ok=True) with open(json_path) as f: data = json.load(f) img_by_id = {img["id"]: img for img in data["images"]} for ann in data["annotations"]: cid = ann["category_id"] if cid not in cat_map: continue img = img_by_id[ann["image_id"]] w, h = img["width"], img["height"] x, y, bw, bh = ann["bbox"] # 防御:非法标注直接跳过,并在终端打印文件名方便排查 if bw <= 0 or bh <= 0 or x < 0 or y < 0: print(f"bad bbox in {img['file_name']}: {ann['bbox']}") continue cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h txt_name = os.path.splitext(img["file_name"])[0] + ".txt" txt_path = os.path.join(out_dir, txt_name) with open(txt_path, "a") as fo: fo.write(f"{cat_map[cid]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n") if __name__ == "__main__": coco_to_yolo("annotations.json", "labels", {1: 0, 2: 1})脚本里cat_map是「COCO 原始类别 id → 本地连续 id」的映射字典。假设这份数据集只有 person 一类,写{1: 0}就行;如果还有物体类,就按你data.yaml里names的顺序手动排好,别指望自动推断——不同数据集的 category_id 定义五花八门,自动推断等于埋雷。坐标归一化前我加了防御判断,非法的负宽高直接跳过。这类脏数据数量通常不多,宁可丢几条不要的,也别让它们混进去把训练 loss 搅乱。
2.3 转换后画框抽查:OpenCV 把标签画回图上人工过一遍
转完格式别急着训练。我一般随机抽 50 张图,用 OpenCV 把标注框画回去,肉眼过一遍。这一步能拦住大部分低级错误:
import cv2 import os ann_dir = "labels" img_dir = "images" out_dir = "check" os.makedirs(out_dir, exist_ok=True) class_colors = {0: (0, 255, 0), 1: (0, 0, 255)} txts = os.listdir(ann_dir)[:50] for txt in txts: img_path = os.path.join(img_dir, os.path.splitext(txt)[0] + ".jpg") img = cv2.imread(img_path) if img is None: continue h, w = img.shape[:2] for line in open(os.path.join(ann_dir, txt)): c, cx, cy, nw, nh = map(float, line.split()) x1 = int((cx - nw / 2) * w) y1 = int((cy - nh / 2) * h) x2 = int((cx + nw / 2) * w) y2 = int((cy + nh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), class_colors[int(c)], 2) cv2.imwrite(os.path.join(out_dir, os.path.splitext(txt)[0] + ".jpg"), img)画框时把归一化坐标乘回图片宽高,所以h, w必须取自img.shape而不是 JSON 里的原始值——如果转换脚本和实际图片尺寸对不上,这里立刻能看出来。检查时重点盯三类问题:框有没有明显歪、有没有跑到画面外、类别颜色和对象对不对得上。这是血泪经验:有次我拿到一批标注,没抽查就开训,训完发现所有预测框都缩在人的腰部,最后查出来是原始标注的坐标定位点定义就不一致,转换脚本把xmin/ymin当成了中心点。那轮训练白跑了两天。
3. 用 YOLOv8 跑通这份数据:data.yaml、训练命令和参数取舍
3.1 data.yaml 的路径和 names 顺序不能错
格式转换完成后,下一步是让 YOLO 读到你整理好的目录。数据集的训练配置就是一个 yaml 文件,但很多人在这里翻车。最典型的问题是path写成相对路径,而训练命令又在别的目录执行,结果直接报「No labels found」。我一般写绝对路径:
path: /home/user/human_det train: train/images val: val/images nc: 2 names: 0: person 1: objectnc必须和names的数量一致,少一个多一个都在加载阶段报错。names的索引顺序要和 2.2 节脚本里cat_map映射出来的数字完全对齐:脚本里把 person 写到 id 0,那 yaml 里0就必须是 person。很多人疏忽在标注文件里的类别号是 0 开头,而names列表写成了 1 开头,两类号交错错位,模型学出来的框全错位。
train和val指向的是 yaml 文件相对path的路径。注意目录里包含了images和labels两个子目录,YOLO 会自动根据images路径推导同名labels目录,不需要你在 yaml 里单独写一行labels。
3.2 训练参数与显存匹配:imgsz、batch、epochs、patience
跑通训练的命令很简短,但参数不能照抄网上模板。针对这种以中小尺寸为主的目标检测数据,我的起步命令是:
yolo train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ project=runs/person_detimgsz不一定要跟着显存拉高。640 是准确率和速度的平衡点,工业数据里人员目标通常占画面比例不小,640 起步足够;如果验证阶段发现小目标 AP 很差,再整体提高到 960,而不是一开始就追求大分辨率。batch完全看显存,16G 显存跑yolov8n用batch=16比较稳,报 OOM 就先减半到 8,没必要为了涨那一点训练稳定性去冒险。patience=20表示验证指标连续 20 轮不涨就提前停,这个参数对工业项目很重要——你可能同时跑好几个模型,早停能省下大量排队时间。
下面是这份数据我常用的参数对照表,按数据量分了档:
| 参数 | 样本量小(<500) | 样本量中等(500~3000) | 说明 |
|---|---|---|---|
| imgsz | 640 | 640 或 960 | 小目标多再升 960 |
| batch | 8 | 16 | 显存受限减半 |
| epochs | 150 | 100 | 配合 patience 用 |
| patience | 30 | 20 | 防止验证指标震荡误停 |
| freeze | 10 | 0 | 小数据冻结 backbone 防过拟合 |
epochs不是越多越好。我见过有人把 epochs 拉到 500 挂着不管,结果模型早就收敛了,后面几百轮全在过拟合,验证 loss 一路狂涨。有 patience 兜底也不建议这么干,因为 early stop 判断的是综合指标,个别类别的 AP 可能先涨后崩。
3.3 从预训练权重微调而不是从零训练:先定模型的复杂度
这份数据集如果只关注「人」这一类,我强烈建议用预训练权重微调而不是yolo train model=yolov8n.yaml从零训练。理由很直接:预训练权重在 COCO 上学过泛化的物体表征,人这一类在 COCO 里就是重头戏,微调相当于站在一个已经认识人的模型上做场景适配,收敛速度和最终精度都比从零训练好一截。从零训练也不是不行,但你需要把 epochs 提到 300 以上,并且对数据质量要求更高。
数据量决定模型复杂度。几百张图用yolov8n.pt,几千张可以升到yolov8s.pt,上万张才考虑yolov8m.pt。工业场景的私有数据集通常只有几百到几千张,一上来就选大模型只会让训练更慢、过拟合更快,属于典型的「用算力掩盖数据问题」。小数据量下,我习惯在微调时冻结 backbone 前几层:
yolo train \ data=data.yaml \ model=yolov8n.pt \ freeze=10 \ epochs=150 \ imgsz=640 \ batch=16freeze=10的意思是冻结前 10 层网络参数,训练时只更新后面部分。它适合标注数量少、场景相对单一的数据,能显著降低过拟合风险。如果数据量超过 2000 张,我会把freeze去掉重新在完整模型上调,效果通常会更好。这个参数组合没有标准答案,但「小数据冻结、大数据解冻」是基本判断逻辑。
4. 避坑排查:模型只学背景不学人的五个现场问题
4.1 训练 loss 不降、预测全空:先查标注 txt 是不是空的
现象是 loss 降到 0.8 左右就卡住不动,验证集预测全空。翻更多日志会发现,每个 batch 参与训练的 target 数少得可怜。
原因大部分是标注文件为空,或者部分 txt 里的类别 id 超出了nc的范围。YOLO 在加载标签时会静默丢弃类别非法的行,如果一堆 txt 都是空文件,整个数据集对模型来说就是「一张有图、无目标的图片」,loss 永远下不去。
解决方法是先统计 txt 行数:
find labels -name "*.txt" -size 0 | wc -l awk '{print $1}' labels/*.txt | sort | uniq -c第一行列空文件数量,第二行列类别 id 分布。如果空文件占比超过 5%,建议回到 2.2 节检查转换脚本里是不是把某些类过滤掉了;如果类别 id 有 5、6 这种大数字,说明cat_map没写全,重转一遍即可。
4.2 验证 mAP 虚高、落地视频全漏:连续帧泄漏
现象是训练曲线很好看,val mAP50 到 0.9 以上,但把模型接到现场视频里,一个行人都框不出来,或者框出来的全是背景。
原因多半出在视频抽帧数据的划分方式。很多数据集是从监控视频按帧抽取的,相邻帧之间场景、人物姿态几乎一样,如果随机打乱后按比例划分,训练集和验证集里会出现大量「同场景不同帧」,模型相当于已经见过验证集的画面,评估分数自然虚高。
解决方法是回到 2.1 节,按视频片段划分 train/val,而不是按帧随机划分。严格的做法是每个视频片段只进入其中一个集合;退一步的做法至少保证同一个连续时间段里的帧不要被切到两个集合。工业场景里这个问题的危害比想象中大,因为它不影响训练,只影响你对自己模型能力的判断。
4.3 密集人群只出一个框:NMS 阈值和 Mosaic 时机
现象是画面里三个人并排站着,模型只输出一个覆盖三个人的大框,或者两个框但置信度很低。
原因有两层。推理端,YOLO 默认 NMS 的 IoU 阈值是 0.7,行人框交叠程度高,两个高度重叠的框会被合并成一个。训练端,默认 Mosaic 增强会把四张图拼在一起,远处的小人在拼图里被缩得很小,模型在训练时看到的小目标样本不够,自然学不会密集小目标。
解决方法是推理时把 NMS 阈值调低:
yolo predict model=runs/person_det/weights/best.pt \ source=test_video.mp4 \ conf=0.15 \ iou=0.45 \ imgsz=640conf=0.15是把置信度门槛降到 15%,宁可多出几个误检框,也别漏掉远处的行人;iou=0.45是让 NMS 在框重叠明显时更「手下留情」不合并。训练端,我会加mosaic=0.5 close_mosaic=10,意思是 Mosaic 概率降到一半,并且在最后 10 个 epoch 完全关闭 Mosaic,让模型在接近真实分布的数据上收敛。
4.4 框整体偏移半个人身:EXIF 方向问题
现象是训练出来的模型在部分图片上框整体偏右或偏下,而且只发生在用手机、相机拍的照片上,截图类图片一切正常。
原因是 jpg 文件头里的 EXIF orientation 信息。手机竖拍的照片,实际像素数据是横着的,方向信息写在 EXIF 里。OpenCV 的imread不解析 EXIF 方向,读取后图片是横的,但标注文件是按竖的视觉方向标的,坐标整体错位。模型被迫学了两种坐标分布,表现出来就是「半个人身偏移」。
解决方法是训练前统一把图片方向转正并清除 EXIF:
from PIL import Image, ImageOps import os os.makedirs("images_fixed", exist_ok=True) for name in os.listdir("images"): img = Image.open(f"images/{name}") img = ImageOps.exif_transpose(img) img.save(f"images_fixed/{name}")ImageOps.exif_transpose会把图片按方向信息旋转到正确姿态,并重写文件。处理好之后回到 2.3 节重新画框抽查,应该能看到所有框都贴合目标了。这个问题容易忽略,因为训练 loss 照样下降,只有看到具体预测框时才会暴露。
4.5 小目标 AP 接近 0:输入尺寸和标注下限
现象是混淆矩阵里 person 类的大目标 AP 很高,但metrics/mAP50-95(B)里小目标这一段几乎为 0。
原因有两个:一是图片里的远处行人只有 10×20 像素,在 640 分辨率下下采样后只剩不到 2 个像素,模型根本没有足够特征;二是有些标注框小到只有几个像素,转换时归一化后值很小,训练时被视为噪声被忽略。
解决方法是先看这批小目标在数据里占比。如果占比高,把imgsz从 640 提到 960,效果好但显存消耗也会上升;如果占比低,更实际的做法是把小目标图按滑窗切片成若干大图后再训练和推理,或者引入 SAHI 这类切片工具,只在推理端做。还有一种低成本思路是训练时把imgsz=960但推理用imgsz=640,不过这种「训练大推理小」的做法会引入尺度偏差,短期能用,长期不稳,我不太推荐作为生产方案。
5. 人与物场景下的检测边界:类名粒度、遮挡重叠与小目标
5.1 类名粒度:单类 person 还是 person+物体类,数据完整度说了算
这份数据集既然叫「人类目标检测」,核心类别肯定是 person。但「人与物」这个关键词暗示,标注里很可能出现了物体类别,比如工具、推车、叉车之类。类名设计不是随手定的,它直接决定模型能不能学到「什么人拿着什么东西」这类上下文。
如果物体类标注完整度不够,我建议果断砍掉,只保留 person。原因很好理解:模型把「没标注的物体」当成背景,同一物体有时出现框、有时不出现框,会教坏分类器。判断标准是标注完整度——物体类被标注的比例如果明显低于 person 类,宁可不要这个类,也不留一个半吊子类别。
反过来,如果人与物交互关系本身是核心判断信号,比如「人骑叉车」与「人走路」需要区分,那物体类就必须加进来。此时要注意类别不平衡:人的样本量可能几倍于物体类,训练时模型会偏向高频类。常见做法是物体类单独用更低置信度阈值推理,或者对物体类做过采样。这几年开放词汇目标检测把「类名」的边界往后推了不少,但在这份数据集对应的 YOLO 训练链路里,类名还是由标注决定的,想扩展类别只能补标注后合并重训。
5.2 人叠人与重影:按类别分开设置信度,调 NMS
工业场景里人和物重影是常态——工人站在叉车前,远景的人站在围栏后面,画面上人形轮廓相互交叠。这种情况下,单靠一个全局conf阈值解决不了问题,因为 person 类我们希望它尽量多检(漏检代价高),物体类我们希望它尽量少误报(误检代价低)。我一般做法是 person 置信度放 0.15,物体类放 0.35,两套阈值分开设。
推理端的 NMS 也需要配合调整。人对重叠场景不友好,默认 0.7 的 IoU 阈值会把两个挨着的人合并成一个大框。调到 0.45 左右,密集人群漏检明显减少,但代价是同一人的重复框变多,需要用第二层 NMS 或按置信度排重。训练端如果数据里重影样本足够多,增强参数也要收敛着调。Mosaic 和 mixup 都有用,但在人被截断严重的场景下会制造大量「半个人」的伪样本,我的习惯是保留 Mosaic 但把close_mosaic设为 10,保证最后收敛阶段用的是真实分布。
5.3 小目标与大视野:imgsz 从 640 提到 960 的真实成本
工业摄像头视野通常很大,高处俯拍的画面里,行人目标很小。用 640 训练时,小目标下采样后特征急剧丢失,对应的 AP 普遍比中目标低 20 个百分点以上。把imgsz提到 960 是立竿见影的手段,但成本要算清楚。
训练显存大约会翻倍,batch可能从 16 降到 8,训练时间增加 50% 起步;推理端同样要用 960 才能匹配训练分布,否则特征尺度不一致,精度收益会打折扣。我的习惯是:先 640 跑一版确认数据没问题,再用 960 跑一版在小目标对比,如果小目标 AP 提升超过 5 个点就切换到 960。如果数据集里小目标占比不足 10%,这 5 个点很可能不值得付出双倍显存和推理耗时。
6. 数据质量的验收动作:用混淆矩阵和失败样本决定去留
6.1 val 输出四件套里的关键信号
训练结束后,runs/person_det/val目录下会生成一组验收文件。判断这份数据集值不值得继续投入,我看这四样:
| 文件 | 重点看什么 |
|---|---|
| results.csv | mAP50-95 是否稳定上升,验证 loss 是否在后期反弹 |
| confusion_matrix.png | person 行对角线亮不亮,有没有大量样本被分到 background |
| val_batch_pred.jpg | 人挨太近时有没有并框,海报/广告牌人像有没有误检 |
| PR_curve.png | 曲线膝盖点位置,判断置信度阈值该高还是该低 |
results.csv里的metrics/mAP50-95(B)是综合指标,但如果只知道看这个数,你会漏掉最关键的信号:混淆矩阵里 person 被分到 background 的比例。这个值直接对应漏检率,工业场景里它比 mAP 更重要。
6.2 人工翻失败样本,这是最后的决策依据
指标只能告诉你「有没有问题」,不能告诉你「问题是什么」。我最后一定会做一件事:打开验证集预测图,把漏检的图片挑出来翻一遍。重点看的是三类失败样本:重叠人群是否合成一个框、远处的极小目标是否完全丢失、画面里的静态人形广告是否被当成真人。如果前两类占比高,说明数据里对应场景的标注还不够,需要补数据;如果是第三类,就要考虑在数据里注入这类负样本,让模型学会把它们当背景。
从那以后,我每次拿到新数据集,都强制自己走一遍「列目录 → 转格式 → 画框抽查 → 训练 → 翻混淆矩阵和失败样本」五步,五步走完才敢说这份数据能不能留用。数据集的坑大多不在模型训练,而在你根本没看清它长什么样。希望帮到你。
本文还有配套的精品资源,点击获取