简介:用于目标检测任务的人类目标检测数据集,共456张真实场景图片,统一标注为单一Human类别,适合需要高一致性人员检测的开发者与研究者。数据集已划分训练集390张、验证集38张、测试集28张,采用标准YOLO格式标注边界框与类别,可直接输入YOLO、Faster R-CNN等主流框架训练。全包共914个文件,包括456张jpg图片、456个txt标注文件、1个yaml配置文件和1份docx说明文档,压缩后约25.15MB,结构清晰,解压即用。目前已有85人学习浏览。数据来源于视频监控、自动驾驶、零售门店及无人机航拍等多样场景,覆盖不同光照与背景,有助于提升模型在真实环境中的泛化能力;借助这份数据可节省大量标注时间,便于快速进行模型验证与迭代,适用于安防监控、人流统计或行人避障等落地项目。
1. 从“人类目标检测数据集.zip”开始:这份压缩包到底该怎么用
拿到一个名为“人类目标检测数据集.zip”的压缩包,第一反应不是解压标注,而是先想清楚一件事:里面的数据结构长什么样,决定了你后面三天的活法。目标检测不像图像分类那样“按文件夹名就是标签”,它依赖标注文件和图像一一对应;同样是人这个类别,VOC 格式、COCO 格式、YOLO 格式之间的差异,足以让一个看起来能直接训练的目录变成报错黑洞。这包数据大概率是别人整理好的“半成品”:图片、标注、类别文件可能都齐了,但格式未必对齐你的训练框架,更要命的是里面可能混着没打标的负样本、损坏的图片、甚至类别名不一致的标注。
我一般拿到这类 zip,按四个问题过一遍:图像本身能不能打开、标注坐标是否落在图像范围内、类别定义是否单一明确、整体数据量是否训练得动。这四个问题里任何一个是负数,这包数据就不是“解压即用”,而是要先做一轮清洗。本文不打算告诉你某个具体网盘链接怎么下,而是把这包数据当成一份待加工的原料,讲清楚从解压到训练之间的完整工序,以及那些让模型效果突然崩掉的隐藏陷阱。往下读,你能获得一套可以直接套用的检查顺序和转换脚本,踩过的坑我提前帮你踩一遍。
2. 数据集自检:用几分钟脚本代替一整天的无效训练
2.1 解压后的第一个命令:统计文件数量与格式分布
拿到 zip 先别急着双击解压到桌面。用命令行解压,顺手留一份文件清单作为底账,这在后面排查漏标、错标文件时能省很多事。
unzip 人类目标检测数据集.zip -d ./pedestrian_data cd ./pedestrian_data # 统计图片文件和标注文件数量 find . -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" \) | wc -l find . -type f \( -name "*.xml" -o -name "*.json" -o -name "*.txt" \) | wc -l # 输出所有图片格式的占比 find . -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" \) | sed 's/.*\.//' | sort | uniq -c逻辑说明:第一条命令把压缩包解压到指定目录,避免把文件散落在下载文件夹里;第二、第三条统计图像和标注的数量,数量不对等基本说明存在漏标;第四条看格式分布,因为 YOLO 训练管线对图像格式没有硬性要求,但大量图片是.png而少数是.bmp时,预处理代码里可能因后缀判断出错。参数说明:find的-o表示或条件,wc -l统计行数,uniq -c统计数量并去重。
理论上图像和标注数量应该一致。不一致时,用下面的脚本找出哪些图像没有对应标注、哪些标注没有对应图像。
ls -1 *.jpg | sed 's/\.jpg$//' > img_names.txt ls -1 *.txt 2>/dev/null | sed 's/\.txt$//' > ann_names.txt # 缺少标注的图像 comm -23 <(sort img_names.txt) <(sort ann_names.txt) # 缺少图像的标注 comm -13 <(sort img_names.txt) <(sort ann_names.txt)逻辑说明:ls提取不带后缀的文件名,comm比较两个排序后的文件,-23表示只显示第一组中有而第二组中没有的行,也就是缺标注的图。这一轮能发现标注文件命名存在前缀不一致之类的低级问题。
2.2 打开图片看一眼:排查损坏文件与全黑样本
命令行统计只能发现“文件缺失”,发现不了“文件损坏”。一张正常命名的 jpg,可能是 0 字节,也可能是只有一行像素的坏图。用 Python 批量验证最稳,顺便把没法解码的文件列出来。
import os from PIL import Image img_dir = "./pedestrian_data/images" bad_files = [] for fname in os.listdir(img_dir): if not fname.lower().endswith((".jpg", ".png", ".jpeg")): continue fpath = os.path.join(img_dir, fname) try: with Image.open(fpath) as img: img.verify() # verify 之后再 open 一次,确保能完整解码 with Image.open(fpath) as img2: _ = img2.load() except Exception as e: bad_files.append((fname, str(e))) print("损坏文件数量:", len(bad_files)) for item in bad_files[:30]: print(item)逻辑说明:Image.verify()只检查头部信息,有时候能通过但真正解码时像素数据是坏的,所以后面又做了一次img2.load()强制加载全部像素,这一步能筛出“半损坏”图片。参数说明:bad_files列表会保存前 30 个文件名和错误原因,方便定位是文件截断还是格式伪装;如果解压后有几千张图,脚本跑完会输出一个损坏清单,后续直接按清单重下或删除。
2.3 标注格式判断:VOC、COCO、YOLO 三者怎么一眼区分
目标检测的标注格式直接决定你是否要写转换脚本。拿几个标注文件打开看一眼,就能确定这包数据的“方言”。
- VOC 格式:
.xml文件,标签是<filename>、<object>、<bndbox>,框的坐标是左上角和右下角的整型像素值,像<xmin>100</xmin>这种。 - COCO 格式:单个
.json文件,最外层是"images"、"annotations"、"categories"三个数组,polygon 和 bbox 都在 annotations 里,坐标也是绝对像素值。 - YOLO 格式:每个图像对应一个
.txt,每行一个目标,格式为class_id x_center y_center width height,坐标全部相对于图片宽高的浮点数。
判断标准很简单:.xml开头是 VOC,一个超大.json是 COCO,每张图配一个.txt是 YOLO。如果这包数据里同时出现.xml和.txt文件,并不代表格式统一,很可能是别人存了一版 VOC 原稿又转了一版 YOLO,此时以哪份为准需要在训练前确认。我一般会抽查 5 到 10 个标注文件,确认同一张图的 XML 和 TXT 描述的是同一组目标,防止版本错位。
3. 把标注统一成 YOLO 格式:转换脚本与四个边界坑
3.1 VOC 转 YOLO 的转换脚本与归一化细节
YOLO 系列训练框架使用最多的还是 YOLO 自带 txt 格式,坐标归一化在 0 到 1 之间。假设这包数据是 VOC 标注,需要写个转换脚本。
import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_path, class_list): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in class_list: # 不在目标类别清单里的直接跳过,不写入 continue cls_id = class_list[cls_name] xmlbox = obj.find("bndbox") xmin = float(xmlbox.find("xmin").text) ymin = float(xmlbox.find("ymin").text) xmax = float(xmlbox.find("xmax").text) ymax = float(xmlbox.find("ymax").text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 边界裁剪,避免出现负数或超过 1 的坐标 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: with open(out_path, "w") as f: f.write("\n".join(lines)) class_list = {"person": 0} xml_dir = Path("./pedestrian_data/Annotations") yolo_dir = Path("./pedestrian_data/labels") yolo_dir.mkdir(exist_ok=True) for xml_file in xml_dir.glob("*.xml"): voc_to_yolo(str(xml_file), str(yolo_dir / (xml_file.stem + ".txt")), class_list)逻辑说明:脚本从 XML 中读取图像宽高和每个目标的边界框坐标,将左上角和右下角的像素坐标换算成中心点加宽高的归一化值。class_list字典控制类别到数字 ID 的映射,这里只有 person 和 0,如果你的数据里有 person、rider、pedestrian 这些相似类别,需要决定是合并为一个大类还是分别训练。参数说明:min(max(...))这种写法是边界裁剪,作用是防止标注框的一部分落在图像外部时归一化坐标超出 0-1 范围,例如某些数据集把行人截断在图像边缘,xmax 可能等于图片宽度,计算后 w 正好为 1.0,这是合法值;但如果原始标注的 xmax 比图片宽度还大,说明标注本身有误,这里只是强制收敛。
3.2 COCO 转 YOLO 的转换重点:处理分段标注与 crowd 属性
如果这包数据给的是 COCO 格式单 JSON 文件,转换时不太适合用上面的 XML 脚本,需要走 JSON 转 txt 的路线。COCO 里有几个字段特别容易被忽略,其中最典型的是iscrowd和segmentation。
import json from pathlib import Path coco_file = Path("./pedestrian_data/annotations.json") save_dir = Path("./pedestrian_data/labels") save_dir.mkdir(exist_ok=True) with open(coco_file, "r", encoding="utf-8") as f: coco = json.load(f) img_id_to_info = {} for img in coco["images"]: img_id_to_info[img["id"]] = img # 建立类别 ID 映射:这里假设 COCO 里真实类别名就是 "person" cat_id_to_train_id = {} for cat in coco["categories"]: if cat["name"] == "person": cat_id_to_train_id[cat["id"]] = 0 for ann in coco["annotations"]: if ann.get("iscrowd", 0) == 1: # 人群密集标注跳过,检测任务通常不训练 crowd 样本 continue cat_id = ann["category_id"] if cat_id not in cat_id_to_train_id: continue img_info = img_id_to_info[ann["image_id"]] img_w = img_info["width"] img_h = img_info["height"] bbox = ann["bbox"] x, y, bw, bh = bbox x_center = (x + bw / 2) / img_w y_center = (y + bh / 2) / img_h w = bw / img_w h = bh / img_h line = f"{cat_id_to_train_id[cat_id]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}" txt_path = save_dir / (img_info["file_name"].replace(".jpg", ".txt").replace(".png", ".txt")) with open(txt_path, "a") as f: f.write(line + "\n")逻辑说明:COCO 的 bbox 用[x, y, width, height]表示左上角起点加宽高,和 VOC 的[xmin, ymin, xmax, ymax]明显不同。这里把起点加宽高换算成了中心点加宽高的 YOLO 表达。iscrowd字段是 COCO 对密集人群的定义,标了 1 的同类目标互相遮挡严重,直接训练会引入大量难例干扰,我一般选择跳过;如果你觉得模型需要适应拥挤场景,可以单独用这些数据做难点增强而不是混进主训练集。
3.3 转完以后必做的验证:坐标是否落在图内、格式是否可被读取
转换脚本跑完,一定不能直接开始训练。先跑一轮简单的读取验证,把不合法坐标全部查出来。
import os from PIL import Image labels_dir = "./pedestrian_data/labels" images_dir = "./pedestrian_data/images" invalid_lines = [] for fname in os.listdir(labels_dir): if not fname.endswith(".txt"): continue img_path = os.path.join(images_dir, fname.replace(".txt", ".jpg")) if not os.path.exists(img_path): img_path = os.path.join(images_dir, fname.replace(".txt", ".png")) try: img_w, img_h = Image.open(img_path).size except Exception as e: invalid_lines.append((fname, "image_error", str(e))) continue with open(os.path.join(labels_dir, fname), "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: invalid_lines.append((fname, "format_error", line)) continue cls_id, xc, yc, w, h = parts xc, yc, w, h = float(xc), float(yc), float(w), float(h) if xc <= 0 or yc <= 0 or w <= 0 or h <= 0: invalid_lines.append((fname, "non_positive", line)) if xc > 1 or yc > 1 or w > 1 or h > 1: invalid_lines.append((fname, "out_of_range", line)) print("异常标注数量:", len(invalid_lines)) for item in invalid_lines[:30]: print(item)逻辑说明:最需要关注的是两类异常——格式不对(比如使用空格分割但中间有多余的空行)和坐标越界(中心点或宽高大于 1)。坐标越界多发生在原始标注的宽高比和图片实际尺寸不一致时,说明转换脚本取图像尺寸时拿错了文件。参数说明:replace(".txt", ".jpg")属于偷懒写法,如果这个目录里同时存在同名.png和.jpg,就会用错图像尺寸,更稳妥的写法是先用glob找出实际存在的扩展名。这一轮还有一个隐藏收益是验证标注文件名和图像名是否完全一致,不一致会在os.path.exists那一步报出来。
4. 数据清洗与增广策略:让“人”这个类别学得更稳
4.1 负样本与难例样本:不只是把背景图片塞进去
很多开源“人类目标检测数据集”里会混入只含背景、没有任何标注目标的图片,这是负样本的正确用法,但它们不该和被错误标注的图片混在一起。负样本的价值在于降低误检率,也就是说模型得学会“没有人时就什么都不输出”。
我的习惯是把负样本单独放一个文件夹,用images_background和空标注文件来代表。YOLO 系列训练时,一个没有 txt 内容但有对应图片的样本是可以参与训练的。但这里有一个坑:如果数据集作者留下的labels目录里根本没有对应 txt 文件,训练框架会在初始化时直接跳过还是报错,取决于你用的训练代码。ultralytics 的 YOLOv8 默认跳过了没有标注的图片,但如果你用的是自己写的 DataLoader,就需要在读取时显式判断 txt 是否存在。
mkdir -p ./pedestrian_data/images_background # 把确定无目标的图片移动到 background 目录,并生成空的 txt 文件 for img in ./pedestrian_data/image_no_person/*.jpg; do mv "$img" ./pedestrian_data/images_background/ basename "$img" .jpg | xargs -I {} touch ./pedestrian_data/labels/{}.txt done逻辑说明:touch命令创建空文件,目的是让标签目录里每个背景图片都有对应的 txt 入口,避免某些加载逻辑把它当成漏标报错。参数说明:xargs -I {} touch的{}会被前面basename输出的文件名替换,注意这里只处理 jpg,如果有 png 需要再补一条或者改用循环脚本。
4.2 Mosaic 增强与随机擦除:让模型面对遮挡和密集人群不崩
人类目标检测最常见的失败场景是遮挡和截断,也就是两个人重叠、人被栏杆挡住一半、或者人群密集到互相遮挡。仅仅依赖原始数据训练,模型会把“完整的人形轮廓”当强特征,一旦出现遮挡就召回率骤降。
常规做法是训练时开启 Mosaic 增强。YOLOv8 的augment.py里 Mosaic 会把四张图随机裁剪拼接成一张新图,目标框跟着变换,这等于人为制造了大量截断样本。随机擦除则是把图片中行人附近的矩形区域随机置灰,模拟遮挡场景。关键参数是mosaic和mixup的概率,训练初期 Mosaic 可以开到 1.0,最后 10 个 epoch 我一般会关掉或者降到 0.2 左右,让模型在接近真实分布的输入上精调,避免过度适应拼接图的“马赛克痕迹”。
另一个有效手段是软标签,也就是把类别 ID 换成类别概率分布。比如[0.9, 0.05, 0.05]而不是硬编码[1, 0, 0],对密集重叠场景下的分类噪声有一定吸收效果。不过这需要改造损失函数,对刚接触检测训练的人来说工程量有点大,有点属于“收益有但不适合新手一上来就搞”的进阶操作。
4.3 类别不均衡:人这个类别也需要细分吗
“人类目标检测”听起来是一个类别,但实际数据里往往还隐含着“行人”“坐着的人”“骑摩托车的人”“低头看手机的人”这些子形态。如果训练数据里大量是直立行走的行人,模型对骑车人和坐姿人体的检测效果通常差得离谱。
到底是继续当一类训练,还是拆分多个类,取决于任务。单类别训练的好处是简单,不会出现类别间误判;坏处是模型只会学一个平均特征,各种姿态和体型被塞进同一个分布里,边界很宽、精度不高。拆成多个类的好处是可控,但需要重新标注或者半自动聚类。
我一般会先看一下原始标注文件里是否只有person一个值。如果只有一类但图片里明显有大量骑行者,那就不要强行拆类,而是补充一批骑行人样本进来。记住一个原则:类别粒度越细,对模型能力要求越高,数据量跟不上时拆类只会让每类的样本数骤降、指标更难看。具体判断线是,某一子类样本少于全部样本的 10% 时不要拆。
5. 训练配置与踩坑实录:YOLOv8 训练自己的行人数据集
5.1 准备数据和 train/val 划分:避免同名图片串集
标签转换、清洗和增强方案确定后,下一步是把数据划分成训练集和验证集。划分的原则是:同一个场景下连续拍摄的帧必须全部进同一边,不能一部分进 train、一部分进 val,否则验证集会过于简单,模型足迹识别能力被高估。
一个实际踩坑案例:同事从某自动驾驶数据集里抽了连续视频帧做行人检测,按文件序号每 5 张抽 1 张进验证集,结果验证集和训练集有很多是相邻帧,模型做“相邻帧记忆”而不是“行人识别”,训练 mAP 高得离谱,一到自己的新场景视频直接拉胯。正确的做法是按场景或视频片段划分。
# 假设图片命名中前 6 位是场景 ID,按场景划分 ls ./pedestrian_data/images | cut -c1-6 | sort | uniq > scene_ids.txt # 随机选取 80% 场景作为训练场景 shuf scene_ids.txt | head -n $(echo "$(wc -l < scene_ids.txt) * 0.8" | bc) > train_scenes.txt # 根据 train_scenes 生成 train 和 val 文件清单 grep -f train_scenes <(ls ./pedestrian_data/images) > train_images.txt grep -v -f train_scenes <(ls ./pedestrian_data/images) > val_images.txt逻辑说明:第一步提取场景 ID,第二步随机抽出 80% 的场景作为训练场景,第三步按文件名匹配生成两个清单。cut -c1-6假设命名规则是场景 ID 在文件名前 6 位,如果你的数据是类似img_0001.jpg这种没有场景信息的,就需要依赖目录结构或视频元数据来做分组。参数说明:* 0.8用bc命令计算行数,shuf是随机打乱,grep -f表示按文件内容匹配,-v表示排除。回退方案是如果文件名无法体现场景,直接把同名前缀分组再划分,宁粗勿细。
5.2 YOLOv8 训练的核心参数:imgsz、batch、epochs 与学习率
训练自己数据集时,data.yaml是第一个要确认的文件。我的建议是先写一个只含 person 且只有一个类别的配置,排除其他类别的干扰。
# data.yaml path: ./pedestrian_data train: train_images.txt val: val_images.txt nc: 1 names: [person]参数说明:path指向数据集根目录,train和val接收的是文本文件里列出的相对路径列表,不要写绝对路径,否则换机器会全部失效。nc和names必须和标签文件里的 class_id 对应,上一部分转换脚本里person的 id 是 0,所以 names 就用[person]。如果你转换标签时用了{"person": 0},这里就是 nc=1;如果数据里有多个类别,nc 的数值必须和实际最大 class_id + 1 相等,比如 class_id 有 0、1、2,那 nc=3。
训练命令我习惯写成下面这个模板:
yolo detect train \ data=./pedestrian_data/data.yaml \ model=yolov8s.pt \ imgsz=640 \ batch=16 \ epochs=100 \ lr0=0.01 \ augment=True \ mosaic=1.0 \ patience=20逻辑说明:model=yolov8s.pt表示从预训练权重热启动,相比从零训练,能大幅减少收敛 epoch 数;imgsz=640是主流权衡点,图片都不大时 640 够了,分辨率太高显存开销成倍增长,太低则行人这种“小目标”本身像素太少,漏检率会上升。批大小 16 在常见 16GB 显存下配 640 输入刚好,显存不足优先减 batch。mosaic=1.0表示前若干轮增强概率全开,后面按默认策略自动衰减。patience=20表示连续 20 个 epoch 验证集指标没提升就提前结束训练,省时间。
5.3 常见训练崩坏问题:loss 震荡、mAP 为 0、图片不匹配
这一节集中整理训练过程中最容易碰到的三种异常,按“现象 → 原因 → 解决”给出来。
现象 1:训练 loss 正常下降,但验证集 mAP 为 0。原因是验证集的 txt 路径写错了,或者验证图片里没有标注任何目标。这类问题通常发生在data.yaml的 val 路径对应到上一个转换脚本未写成功导致标签为空文件的情况。解决:检查验证集对应的 labels 目录里是否真的存在内容量大于 0 的 txt 文件,再用上面写过的验证脚本重新跑一遍。
现象 2:训练 loss 剧烈震荡不收敛。原因是学习率 lr0 设置过大,且数据里有大量异常标注,比如宽高为 0 的框、坐标越界但没被裁剪。解决:先把 lr0 从 0.01 降到 0.001,同时重新做一轮坐标合法性和宽高非零检查,那些 w 或 h 小于 3 个像素的极端小框先删掉。
现象 3:训练集 mAP 极高,但真实场景视频一跑全是误报或漏报。原因是数据集划分泄漏,也就是前面提到的相同场景的帧被同时分进 train 和 val,模型靠记忆高相似帧混过了评估指标。解决:按场景分组划分后重训,并在评估时用完全不同时间段拍摄的视频做人工目检,而不能只看 val mAP。
6. 验证模型与进阶技巧:用“开放词汇检测”给数据挑毛病
6.1 用 Grounding DINO 检查未标注目标:发现漏标比补标更重要
训练完成之后,除了跑 mAP,我强烈建议做一次“自动化漏标检查”。传统数据清洗只能发现格式错误,发现不了“这张图里明明有人但没标框”的语义错误,而这恰恰是影响模型学习上限的隐形问题。具体做法是用开放词汇检测模型,比如 Grounding DINO,给定文本提示词person或pedestrian,让它先做一轮检测,再把结果和原始标注对比。
from groundingdino.util.inference import load_model, load_image, predict model = load_model("groundingdino_swint_ogc.py", "weights/groundingdino_swint_ogc.pth") image_path = "./pedestrian_data/images/img_0101.jpg" IMAGE, annotated_frame = load_image(image_path) boxes, logits, phrases = predict( model=model, image=IMAGE, caption="person", box_threshold=0.25, text_threshold=0.2, device="cuda", ) # boxes 的格式是 0-1 归一化数组,可用来对比原始 YOLO 标注是否存在缺失 print(len(boxes))逻辑说明:box_threshold和text_threshold分别控制框的置信度阈值和文本匹配阈值,较低的阈值会输出更多候选目标,这样才有“挑漏”的意义。caption一次可以传多个词,比如"person . rider . pedestrian",对多形态行人场景更稳。参数说明:load_image返回的是标注过的图像和原始 tensor,实际比对时不要用 annotate 后的图,而是拿boxes和原标注做 IOU 匹配,大于 0.5 的算合格,剩下的就是漏标候选。这个方法不适合点数特别多的超大图,容易把框切成碎片,先缩放到宽度 1333 再跑,和论文设定保持一致。
6.2 用误检案例反向修补数据:哪些“人”是模型学坏的
最后一步是对模型误检样本做定向观察。很多行人检测模型在“人体模特”“广告牌人形”“汽车尾部的人形贴纸”上会产生 high-confidence 误检,出现这类问题不一定是模型笨,而是训练数据里缺少这些“类人但不是人”的负样本。
做法是把误检图片收集到hard_negatives目录,和之前的 background 目录一样给它们配空的 txt 文件,或者单独建一类person_fake参与训练。重点不是让模型学会认识“假人”,而是让模型对“像人但不是人”的结构产生抑制,这种数据通常比几千张正常负样本更管用。
验证时也要看一张具体的 PR 曲线而不是只看 mAP。mAP 把各个置信度阈值下的表现平均了,真正部署时你会设置一个固定的阈值比如 0.5,这时对应精确率和召回率是多少才是你要的答案。如果召回率特别低,说明漏检严重,优先补帧、补小目标;如果精确率低,说明误检多,优先补负样本、降阈值。这组判断是调数据的唯一切入点,按这个思路迭代,数据集的每一轮修改都会回到指标上得到验证。
6.3 小目标增强的一个隐藏参数:不要只靠 imgsz
最后补充一个容易忽略的细节。行人检测里大量目标的高度可能只有 30 到 60 像素,这在小目标定义里属于极端难例。把imgsz从 640 提到 1280 能缓解一部分,但显存开销和训练时间都会翻倍。我常用的替代方案是用 SAHI 这类切图推理工具,把大图切成 640 的 patch 再分别检测,相当于在推理阶段把“小目标”放大。训练阶段如果条件允许,可以额外生成一批裁剪到行人附近的子图参与训练,用二阶段训练的方式先让模型学会小目标的基本形态,再用全图翻转精度。这一步对“人类目标检测数据集”这种“绝大多数目标尺寸小”的场景提升明显,算是我这几年调来调去最有用的一招。
希望这篇笔记能帮你把一份“人类目标检测数据集.zip”从解压到训练、再到部署验证的整条路径走通。如果里面哪个坑你也踩到了,按照对应现象排查基本能解决;如果数据本身质量太差补不回来,也不必硬撑,换数据比调模型划算。祝顺利。
本文还有配套的精品资源,点击获取