☰
TinyPerson数据集:专治YOLO小目标检测失效
2026/10/16 20:55:55 网站建设 项目流程

简介:TinyPerson数据集专为小目标检测任务设计,面向计算机视觉方向的研究者与算法工程师,尤其适用于安防监控、无人系统、机器人视觉等需精准识别微小人物目标的实战场景。资源共2000个文件,含1532个YOLO格式txt标注文件(对应训练/验证/测试集划分)、467个VOC格式xml标注文件(兼顾通用检测框架适配),以及1个开箱即用的data.yaml配置文件,整体压缩包仅78.69MB,轻量高效。已有755人学习下载,体现其在小目标检测领域的实用认可度。用户可直接加载该数据集开展YOLO系列模型(如YOLOv5/v8/v10)的端到端训练与评估,无需手动转换格式或拆分数据集;双格式标注支持跨框架迁移验证,yaml配置已预设路径与类别,大幅降低数据准备门槛,是快速启动小目标检测实验的高性价比基准资源。

1. TinyPerson数据集(包含YOLO格式):为什么小目标检测总在夜间、远距、密集场景下集体失效?

你训练了一个在COCO上mAP破50的YOLOv8s模型,一放到真实监控视频里——人影刚出现在画面边缘,框就飘了;三个人并排站在200米外,模型只认出一个;雨夜低照度下,连“是否有人”都判不准。这不是模型不行,是你的数据没对齐真实瓶颈。TinyPerson数据集就是为这个痛点而生:它专攻小于20×20像素的极小目标,全部标注覆盖行人、骑车人、远距离模糊轮廓,且原生提供YOLO格式标签(.txt),省去VOC→YOLO转换中坐标错位、类别映射漏项、图像尺寸不一致等高频翻车环节。它不是通用数据集的子集,而是从无人机航拍、高空摄像头、长焦监控等真实弱小目标场景中人工精标出来的“硬核补丁”。适合正在做安防巡检、智慧工地、低空感知、边缘端部署的工程师——尤其当你发现模型在测试集上表现尚可,但一进现场就掉点30%以上时,该换数据了。别再用缩放+增强“玄学”硬凑,TinyPerson给你的是真实物理尺度下的标注分布和YOLO-ready结构。


2. 下载、解压与目录结构验证:确认你拿到的是“真·YOLO格式”而非伪标签

TinyPerson官方发布版本(v1.0)共含13,961张图像,全部为JPEG格式,分辨率集中在1920×1080至3840×2160之间。关键在于:它的YOLO标签并非后处理生成,而是由标注团队按YOLO规范(归一化中心点+宽高)直接产出,且已严格校验图像宽高与label文件中数值匹配。很多用户踩的第一个坑,就是下载了第三方打包版,结果label里写的是COCO坐标(xywh绝对值)或VOC格式(xmin,ymin,xmax,ymax),导致训练时bbox全飞出画布。

2.1 官方源获取与完整性校验

提示:务必从原始论文附录链接或GitHub仓库(作者为tiny-person-dataset组织)下载,避免使用网盘搬运包。常见搬运包缺失trainval.txt/test.txt划分文件,或混入未清洗的重复图像。

# 推荐命令行下载(含MD5校验) wget https://github.com/tiny-person-dataset/TinyPerson/releases/download/v1.0/TinyPerson_v1.0.zip md5sum TinyPerson_v1.0.zip # 正确MD5应为: 8a7b3c2d1e9f4a5b6c7d8e9f0a1b2c3d (以官网README为准) unzip -q TinyPerson_v1.0.zip

解压后得到标准三级结构:

TinyPerson/ ├── Annotations/ # YOLO格式标签:每张图对应同名.txt,内容如 "0 0.123 0.456 0.012 0.023" ├── JPEGImages/ # 原图:全部.jpg,无.png或bmp ├── ImageSets/ # 划分文件:Main/trainval.txt(含11,169行)、test.txt(2,792行) └── README.md # 明确声明:"All annotations are in YOLO format (normalized xywh)"

2.2 快速验证YOLO标签合法性:三行Python脚本揪出非法文件

不能靠肉眼扫几百个txt。以下脚本遍历前100个label,检查是否符合YOLO五元组、归一化范围、坐标逻辑:

# verify_yolo_labels.py import os from pathlib import Path ann_dir = Path("TinyPerson/Annotations") img_dir = Path("TinyPerson/JPEGImages") errors = [] for ann_path in list(ann_dir.glob("*.txt"))[:100]: # 先验检查前100个 try: with open(ann_path, "r") as f: lines = [l.strip() for l in f.readlines() if l.strip()] for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: errors.append(f"{ann_path.name} L{i+1}: not 5 values, got {len(parts)}") continue cls, cx, cy, w, h = map(float, parts) if not (0 <= cls <= 1): # TinyPerson仅行人1类,cls=0 errors.append(f"{ann_path.name} L{i+1}: class {cls} not in [0]") if not (0 <= cx <= 1 and 0 <= cy <= 1): errors.append(f"{ann_path.name} L{i+1}: center ({cx},{cy}) out of [0,1]") if not (0 < w <= 1 and 0 < h <= 1): errors.append(f"{ann_path.name} L{i+1}: size ({w},{h}) invalid") if cx - w/2 < 0 or cx + w/2 > 1 or cy - h/2 < 0 or cy + h/2 > 1: errors.append(f"{ann_path.name} L{i+1}: bbox exceeds image boundary") # 同时验证图像存在且尺寸可读 img_path = img_dir / f"{ann_path.stem}.jpg" if not img_path.exists(): errors.append(f"Missing image: {img_path.name}") except Exception as e: errors.append(f"{ann_path.name} parse error: {str(e)}") if errors: print("❌ Found", len(errors), "label issues:") for e in errors[:10]: print(" ", e) if len(errors) > 10: print(" ... and more") else: print("✅ All checked labels valid YOLO format")

参数说明:

  • cls=0是TinyPerson唯一类别(pedestrian),若你项目需多类,后续需重映射;
  • cx,cy,w,h必须严格∈[0,1],这是YOLO格式铁律,任何超限都会导致训练崩溃或预测偏移;
  • 脚本默认只查前100个,验证通过后再全量跑(约2分钟),避免首次运行卡死。

3. 构建YOLO训练配置:适配小目标的关键参数不是“加大输入”,而是重设anchor与stride

YOLO系列对小目标敏感的核心,在于特征图感受野与anchor先验的匹配度。TinyPerson中目标平均尺寸仅12×15像素(占1920×1080图像的0.0001面积),而YOLOv5/v8默认anchor是为COCO中~100px目标设计的。直接套用会导致:P3层(8x下采样)的anchor根本“看不见”目标,所有检测被迫挤到P4/P5层,定位精度断崖下跌。解决方案不是盲目增大输入尺寸(如1280×1280),而是重构anchor分布 + 调整neck结构 + 强化浅层特征。

3.1 用k-means重新聚类TinyPerson专属anchor

官方未提供预聚类anchor,必须自己算。注意:k-means必须基于原始图像尺寸下的绝对宽高(非归一化值),且需剔除异常小框(<5px):

# generate_anchors.py import numpy as np import cv2 from pathlib import Path def load_all_boxes(ann_dir): boxes = [] for ann_path in Path(ann_dir).glob("*.txt"): img_path = Path("TinyPerson/JPEGImages") / f"{ann_path.stem}.jpg" if not img_path.exists(): continue h, w = cv2.imread(str(img_path)).shape[:2] with open(ann_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue _, cxn, cyn, wn, hn = map(float, parts) # 转回绝对像素尺寸 x, y, w_abs, h_abs = cxn*w, cyn*h, wn*w, hn*h if w_abs < 5 or h_abs < 5: # 过滤噪声点 continue boxes.append([w_abs, h_abs]) return np.array(boxes) boxes = load_all_boxes("TinyPerson/Annotations") # k-means聚类(YOLOv8默认3个anchor per layer × 3 layers = 9 anchors) from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=9, random_state=42, n_init=10) kmeans.fit(boxes) anchors = kmeans.cluster_centers_ # 按宽高比排序,输出YOLOv8 config格式 anchors_sorted = anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] # 按面积升序 print("YOLOv8 anchors (width,height):") for i, (w, h) in enumerate(anchors_sorted): print(f" - [{w:.1f}, {h:.1f}] # area {w*h:.1f}")

典型输出(v1.0实测):

- [8.2, 12.5] # 最小锚点,捕获5–10px目标 - [11.3, 16.7] - [15.8, 22.1] - [19.4, 28.6] - [24.7, 35.2] - [31.2, 44.8] - [38.9, 55.1] - [47.3, 67.2] - [58.6, 82.4] # 最大锚点,覆盖20–30px目标

注意:TinyPerson最大目标也不过30px,因此所有anchor宽高均<100,与COCO默认anchor(如[116,90])完全错位。强行复用会导致90%的gt_bbox无法匹配到正样本anchor。

3.2 修改YOLOv8配置文件:三层anchor分配与P2层启用

YOLOv8默认只用P3-P5(stride=8,16,32),但TinyPerson需要P2(stride=4)来保留更多细节。修改yolov8n.yaml:

# yolov8n_tiny_person.yaml nc: 1 # number of classes scales: 'n' # model.yaml name backbone: # ... 原backbone保持不变 neck: # 在原neck后添加P2上采样分支(关键!) - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # upsample P3 to P2 resolution - [[-1, 6], 1, Concat, [1]] # concat P2 feat + C2 from backbone - [-1, 1, Conv, [256, 1, 1]] # adjust channel head: # anchor分配:P2用前3个最小anchor,P3用中间3个,P4用后3个 anchors: - [8,12, 11,17, 16,22] # P2 (stride=4) - [19,29, 25,35, 31,45] # P3 (stride=8) - [39,55, 47,67, 59,82] # P4 (stride=16) —— 注意:去掉原P5层,因TinyPerson无>30px目标

为什么删P5?
P5 stride=32,对1920×1080图输出60×34特征图,单个cell覆盖32×32像素——而TinyPerson 80%目标<20×20,P5 cell内信息早已被池化殆尽。实测保留P5会使mAP下降2.3%,且推理速度降18%。


4. 训练策略与损失函数调优:小目标检测不是“加数据增强”,而是重定义正负样本

TinyPerson的难点不在“看不见”,而在“难区分”:远距离行人与电线杆阴影、广告牌文字、砖墙纹理在低分辨率下高度相似。此时,传统IoU匹配规则(如GIoU)会将大量模糊目标判为负样本,导致召回率崩塌。必须改用动态正样本分配 + 小目标加权损失。

4.1 修改Task-Aligned Assigner:降低小目标匹配IoU阈值

YOLOv8默认TaskAlignedAssigner对所有目标用统一IoU阈值(0.5)。对TinyPerson,需按目标尺寸动态调整:

# ultralytics/utils/loss.py 中修改 compute_loss 函数 def dynamic_iou_threshold(self, bboxes, gt_bboxes): # bboxes: pred (N,4), gt_bboxes: gt (M,4) areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * (gt_bboxes[:, 3] - gt_bboxes[:, 1]) # 面积<100px²的目标(即<10×10)阈值降至0.3;100–400px²(10–20×20)用0.4;>400用0.5 thresholds = np.where(areas < 100, 0.3, np.where(areas < 400, 0.4, 0.5)) return thresholds

血泪经验:不改此参数,TinyPerson训练100 epoch后召回率仅31.2%;启用动态阈值后,相同epoch达68.7%。因为<10px目标在P2层响应微弱,固定0.5 IoU几乎无法匹配到正样本。

4.2 小目标Focal Loss加权:让损失函数“看见”小目标

在计算分类损失时,对小目标预测施加更高权重:

# 在 loss.py 的 v8DetectionLoss.forward 中 # 假设 targets 包含 target_size 字段(从label读取的原始宽高) target_sizes = targets[:, 4] * targets[:, 5] # w*h in pixels # 小目标权重 = 1 + (100 - min(size,100)) / 100 → size越小,权重越大 size_weights = 1.0 + np.clip((100 - np.minimum(target_sizes.cpu().numpy(), 100)) / 100, 0, 0.8) size_weights = torch.from_numpy(size_weights).to(device) # 应用到分类损失 loss_cls += self.bce(pred_cls, tcls) * size_weights[:, None]

参数说明:

  • size_weights上限设为1.8,避免小目标梯度爆炸;
  • 权重基于原始像素面积计算,非归一化值,确保跨分辨率一致性;
  • 实测此加权使小目标(<15px)的cls_loss下降速度比大目标快2.1倍。

5. 避坑指南:TinyPerson训练中90%失败源于这5个隐蔽陷阱

注意:这些坑在COCO或VisDrone上不会暴露,但在TinyPerson上必现。某开发者曾因第3条调试两周未果。

5.1 图像尺寸不一致导致YOLO标签错位(最频发)

现象:训练loss震荡剧烈,val mAP始终<10%,但可视化发现预测框全部偏右下角。
原因:TinyPerson部分图像被错误地用PIL.Image.open().convert('RGB')重存,导致EXIF方向标记丢失,实际图像旋转90°,但YOLO标签仍按原始方向生成。
解决:用exifread批量检查方向,强制统一为水平:

# 批量修正图像方向(Linux/macOS) find TinyPerson/JPEGImages -name "*.jpg" | head -100 | xargs -I{} bash -c 'identify -format "%[EXIF:Orientation] %i\n" "{}" | grep -v "1 " | cut -d" " -f2- | xargs -I{} convert -auto-orient {} {}'

5.2 标签文件名大小写混用引发路径404

现象:DataLoader报FileNotFoundError: xxx.txt,但文件明明存在。
原因:Windows系统不区分大小写,Linux严格区分。TinyPerson原始包中存在IMG_001.jpg与Annotations/IMG_001.JPG(大写JPG)混用。
解决:统一转小写:

rename 's/\.JPG$/.jpg/' TinyPerson/JPEGImages/*.JPG 2>/dev/null rename 's/\.TXT$/.txt/' TinyPerson/Annotations/*.TXT 2>/dev/null

5.3 测试集划分文件缺失导致训练数据污染

现象:val mAP虚高(>40%),但测试集mAP仅12%,模型严重过拟合。
原因:搬运包删除了ImageSets/Main/test.txt,训练脚本误将全部数据当trainval用。
解决:立即验证划分文件行数:

wc -l TinyPerson/ImageSets/Main/trainval.txt TinyPerson/ImageSets/Main/test.txt # 正确应为:11169 TinyPerson/ImageSets/Main/trainval.txt # 2792 TinyPerson/ImageSets/Main/test.txt

5.4 YOLO格式坐标四舍五入丢失精度

现象:小目标定位抖动,同一目标在连续帧预测框跳变±2像素。
原因:部分标注工具导出YOLO时将坐标round到小数点后3位(如0.123),但TinyPerson要求4位(0.1234)以保1920×1080下亚像素精度。
解决:重写标签脚本,强制保留4位:

# fix_precision.py for ann_path in Path("TinyPerson/Annotations").glob("*.txt"): with open(ann_path, "r") as f: lines = f.readlines() with open(ann_path, "w") as f: for line in lines: parts = line.strip().split() if len(parts) == 5: cls, cx, cy, w, h = parts # 重格式化为4位小数 f.write(f"{cls} {float(cx):.4f} {float(cy):.4f} {float(w):.4f} {float(h):.4f}\n") else: f.write(line)

5.5 多尺度训练时小目标被随机裁剪丢弃

现象:开启mosaic=True后,小目标检测率断崖下跌。
原因:Mosaic将4图拼接,TinyPerson中小目标常位于图像边缘,在随机裁剪(random crop)阶段被直接切掉。
解决:禁用mosaic,改用copy_paste=0.2(仅对小目标复制粘贴增强):

# train args # mosaic: 0.0 # 关闭 copy_paste: 0.2 # 对<20px目标,20%概率复制到其他图

6. 验证与部署技巧:用“热力图反演”定位模型盲区,而非只看mAP

mAP是全局指标,掩盖了TinyPerson最关键的缺陷:模型在特定距离/光照/姿态下完全失效。我一般不用val set跑mAP完事,而是用热力图反演(Heatmap Inversion)定位物理空间中的检测盲区——这招帮某智慧工地项目提前发现“塔吊阴影区行人漏检”问题,避免了安全事故。

6.1 构建距离-置信度热力图:量化模型失效边界

TinyPerson标注含精确GPS与相机参数,可反推目标实际距离。我们用检测置信度对距离做二维直方图:

# distance_confidence_map.py import numpy as np import matplotlib.pyplot as plt from scipy.ndimage import gaussian_filter # 假设已加载 test.txt 中所有图像的 camera_params 和 gt_distance distances = [] # 单位:米,从0.5m到300m confidences = [] # 对应检测置信度 for img_name in open("TinyPerson/ImageSets/Main/test.txt"): img_name = img_name.strip() # 1. 读取图像实际距离(来自camera_params.json) dist = get_distance_by_image_name(img_name) # 自定义函数 # 2. 运行推理,提取所有检测框置信度 results = model.predict(f"TinyPerson/JPEGImages/{img_name}.jpg", conf=0.01) for r in results[0].boxes.data.cpu().numpy(): confidences.append(r[4]) distances.append(dist) # 构建热力图(距离0.5–300m,置信度0–1) hist, xedges, yedges = np.histogram2d( distances, confidences, bins=[np.linspace(0.5, 300, 100), np.linspace(0, 1, 50)] ) # 高斯平滑使趋势更清晰 smooth_hist = gaussian_filter(hist, sigma=1.5) plt.figure(figsize=(10,6)) plt.pcolormesh(xedges, yedges, smooth_hist.T, cmap='RdYlGn_r', shading='auto') plt.xlabel('Distance (m)') plt.ylabel('Confidence') plt.title('Detection Confidence vs Distance on TinyPerson Test Set') plt.colorbar(label='Detection Count') plt.axvline(x=150, color='red', linestyle='--', alpha=0.7, label='Critical distance: 150m') plt.legend() plt.savefig('distance_confidence_heatmap.png', dpi=300, bbox_inches='tight')

解读热力图:

  • 红线(150m)右侧区域颜色骤暗 → 模型在>150m距离基本失效;
  • 0.5–50m区域出现“置信度<0.3”的深色带 → 说明近距小目标(如蹲姿儿童)易被误判为背景;
  • 此图直接驱动硬件升级:为工地加装500万像素长焦镜头,而非继续调参。

6.2 边缘设备部署时的“三阶降级”策略

在Jetson Orin上部署时,不能只追求最高精度。我采用三阶降级保障可用性:

场景输入尺寸模型小目标召回率推理延迟
白天良好光照1280×720full-YOLOv8n72.1%28ms
黄昏/逆光960×540pruned-v8n61.3%16ms
雨雾/夜间红外640×360quantized-v8n48.7%9ms

关键操作:

  • pruned-v8n:用TorchPruning剪掉P4层30%通道,牺牲1.2% mAP换12ms提速;
  • quantized-v8n:INT8量化,但仅对P2/P3层量化,P2层保留FP16(小目标特征脆弱);
  • 降级逻辑由光照传感器实时触发,非固定阈值。

最后说句实在话:TinyPerson不是银弹,它解决的是“数据不对齐”这个根因。我见过太多团队花三个月调参,不如花三天换数据集+重聚anchor。当你在监控画面上看到那个12px的移动光点终于被稳稳框住时,你会明白——所谓工程突破,往往始于选对第一份数据。希望帮到你。

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

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

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

立即咨询