news 2026/9/1 6:14:08

TinyPerson数据集COCO转YOLO格式完整教程与训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TinyPerson数据集COCO转YOLO格式完整教程与训练避坑指南

简介:该数据集面向目标检测、人群检测及小目标检测方向的开发者与研究者,由TinyPerson图像整理而成,共包含1532张带标注样本,标注格式覆盖VOC xml与YOLO txt两种主流类型,其中YOLO txt已划分好训练、验证、测试集,可直接用于YOLO系列模型训练,省去自行标注和格式转换的环节。压缩包总计2000个文件,以1532个txt标签文件为主体,另有467个xml标注文件与1个yaml配置文件,整体大小78.54MB,目录结构清晰,便于直接放入YOLO项目使用。当前已有2915人学习下载,资源适合正在开展目标检测课程设计、毕业设计或算法对比实验的深度学习学习者与进阶者,借助现成标注数据快速验证模型效果,并为后续小目标检测优化与调参提供可靠基础。

1. 为什么大家都在找“YOLO格式的TinyPerson数据集”

做了一段时间目标检测的人,尤其是专注小目标识别方向的朋友,大概率都听过TinyPerson数据集。这个数据集在远距离行人检测、海上搜救、岸线监控这一类场景里地位很特殊,因为是少有的专门针对“极小目标行人”设计的公开数据集。它的原始标注是COCO JSON格式,但YOLO系列训练需要的是txt标签,而且网上能直接下载到的“YOLO格式成品”质量参差不齐,有的标签类别映射是乱的,有的坐标没归一化好,拿过来直接跑,训练出来的模型大概率是废的。

这篇文章我就把“把TinyPerson数据集从COCO格式转换成YOLO格式”这件事掰开揉碎讲清楚。内容包括格式转换的核心逻辑、完整脚本、转换后如何自查、以及训练时最容易踩的坑。适合正在折腾YOLOv5/YOLOv8/YOLOv11的检测玩家,也适合准备自己做数据集、又不太清楚YOLO标签规范的新手。看完这篇文章,你不仅能拿到一份能直接跑的YOLO格式TinyPerson,还能理解背后的转换原理,以后遇到其他COCO格式数据集(比如VisDrone、SODA)也能顺手处理。

1.1 TinyPerson到底是什么

TinyPerson数据集的核心定位,一句话概括就是:专门为“远距离小目标行人检测”设计的高难度数据集。它采集的大多是海上、岸边、航拍视角下的行人画面,背景复杂、目标极小,很多目标在整张图里只有几个像素甚至更小,人眼都需要仔细凑近了才能分辨。

官方公布的数据规模大概在500张图左右,标注实例大约1600个。这个体量放在今天的大模型时代不算大,但它的难度恰恰是“目标尺寸”这一点。数据集中目标平均尺寸通常在20像素以下,有的极端场景连5像素都不到。对于YOLO这类基于锚框或中心点的检测器来说,这种目标属于“看不见就真的看不见”的级别,特征极度匮乏,非常考验数据标注质量和训练策略。

标注类别上有两个:sea_person(海上的行人/类人目标)和earth_person(陆地/岸边的行人/类人目标)。这两个类别名挺有意思,它不是简单的“人”分类,而是按场景区分,在做海上搜救这类任务时,这样的细粒度标注反而有用——你可以知道目标是在水中还是岸上,这对救援决策很有价值。

1.2 “YOLO格式”和COCO格式的差异

很多刚接触数据集转换的人容易犯迷糊:同样是标注框,为什么非要费劲转格式?直接用COCO JSON不行吗?

这里要理解YOLO系列训练脚本的工作方式。YOLO系列(v5/v8/v11)默认的标签系统是:每张图片对应一个同名txt文件,txt文件里每一行代表一个目标,格式是:

class_id x_center y_center width height

注意,这五个值全部是归一化后的浮点数。x_center y_center width height表示的是目标中心点坐标和宽高相对于图片宽高的比例,取值范围0到1。

而COCO JSON里记录的bbox是绝对像素坐标,格式是:

[x, y, width, height]

其中x, y是目标框左上角的像素坐标。从COCO转到YOLO,核心操作就是两步:把左上角坐标转成中心点坐标,然后除以图片宽高做归一化。

对比项COCO JSONYOLO TXT
存储方式单个JSON文件集中管理每张图一个txt文件
坐标含义像素坐标,绝对位置归一化坐标,比例值
框表示左上角 x, y, 宽, 高中心点 x, y, 宽, 高
类别表示类别ID对应JSON里的categories从0开始的整数ID
可读性人可读,但训练脚本解析麻烦机器友好,训练时直接读取

2. 动手转换前,先搞清楚这5个关键点

如果你只是随便找个转换脚本跑一下,运气好也许能跑通,但只要数据稍有变化,脚本就最容易翻车。转换这个动作虽然简单,背后的5个细节没想清楚,后面训练时才会有各种奇怪问题冒出来。

2.1 标注坐标系与归一化

第一个关键点是坐标系的转换公式。COCO的bbox是[x, y, width, height],左上角坐标加宽高。YOLO需要的是目标中心点的位置和宽高比例。

转换公式如下:

x_center = (x + width / 2) / image_width y_center = (y + height / 2) / image_height norm_width = width / image_width norm_height = height / image_height

这里有个容易忽略的细节:image_widthimage_height必须来自图片的真实尺寸,而不是JSON里某个字段。TinyPerson原始JSON里images字段会记录widthheight,按理说可以直接用,但有些第三方整理的版本在标注时对图片做过缩放、裁剪甚至补边,JSON里的尺寸可能和实际图片对不上。最稳妥的做法是转换时用cv2.imread或者PIL.Image.open读一遍真实图片尺寸,再去做归一化。虽然慢一点,但绝对不会错。

2.2 类别映射:sea_person / earth_person

TinyPerson原始JSON里的类别ID,我记得raw COCO文件里categoriesid不是从0开始的(具体可能是1和2,也可能有偏移),而YOLO训练要求类别ID必须严格从0开始连续编号,没有空洞。

所以转换时不能直接把COCO的category_id写进txt,必须先建立一个映射表:

categories_map = { 1: 0, # sea_person -> 0 2: 1 # earth_person -> 1 }

如果你有特殊需求,比如只关心“有没有人”,不关心是在海上还是岸上,可以把两个类别合成一个person,都在txt里写成0。这个选择和你的实际任务强相关,没有绝对的对错,但决定了模型最后的输出粒度。

2.3 训练集/验证集划分策略

TinyPerson官方其实提供了划分好的train/val列表,但网上流传的版本不一定带这层信息。如果你拿到的JSON里没有划分,建议自己做。

划分时要注意:一定要按图片维度划分,而不是按标注实例划分。也就是同一张图的所有目标必须全部进训练集或全部进验证集,不能一张图的一半个框在训练集、另外半个框在验证集。后者会导致数据泄漏,验证集loss会异常低,但实际泛化能力一塌糊涂。

推荐比例:训练集80%,验证集20%。TinyPerson总共500多张图,验证集100多张,量级合理。

3. 一步步把COCO转成YOLO格式

说了一堆原理,现在上实操。我直接给一份我用过很多次的转换脚本,它不光是给TinyPerson用的,任何COCO格式的数据集都能用同一个逻辑处理。

3.1 完整转换脚本

import json import os import random from collections import defaultdict def convert_coco_json_to_yolo( coco_json_path, output_dir, image_dir=None, train_ratio=0.8 ): """ COCO JSON -> YOLO txt coco_json_path: 原始COCO标注文件 output_dir: 输出目录,会在里面生成 images/train, images/val, labels/train, labels/val image_dir: 图片目录,如果不传则默认使用JSON里的file_name字段 train_ratio: 训练集比例 """ with open(coco_json_path, 'r', encoding='utf-8') as f: coco = json.load(f) # 建立COCO类别ID -> YOLO类别ID的映射 cat_id_to_yolo_id = {} categories = sorted(coco['categories'], key=lambda x: x['id']) for yolo_id, cat in enumerate(categories): cat_id_to_yolo_id[cat['id']] = yolo_id print("类别映射:", {cat['name']: cat_id_to_yolo_id[cat['id']] for cat in categories}) # 建立 image_id -> 图片信息 的索引 images_info = {} for img in coco['images']: images_info[img['id']] = img # 建立 image_id -> 标注列表 的索引 annotations_by_image = defaultdict(list) for ann in coco['annotations']: annotations_by_image[ann['image_id']].append(ann) # 打乱图片顺序后划分train/val,确保一张图的所有标注进同一集合 image_ids = list(images_info.keys()) random.shuffle(image_ids) split_idx = int(len(image_ids) * train_ratio) train_ids = set(image_ids[:split_idx]) val_ids = set(image_ids[split_idx:]) # 创建目录结构 for split in ['train', 'val']: os.makedirs(os.path.join(output_dir, 'images', split), exist_ok=True) os.makedirs(os.path.join(output_dir, 'labels', split), exist_ok=True) train_count, val_count = 0, 0 issue_count = 0 for img_id, img in images_info.items(): # 判断当前图片属于哪个集合 if img_id in train_ids: split = 'train' train_count += 1 else: split = 'val' val_count += 1 # 图片文件名:优先用JSON里的file_name,没有的话用image_dir+id if 'file_name' in img: file_name = os.path.basename(img['file_name']) else: file_name = f"{img['id']:012d}.jpg" src_path = os.path.join(image_dir, file_name) if image_dir else file_name dst_img_dir = os.path.join(output_dir, 'images', split, file_name) # 优先用真实图片尺寸,避免JSON虚标 try: from PIL import Image with Image.open(src_path) as im: img_w, img_h = im.size except Exception: img_w, img_h = img.get('width'), img.get('height') if not img_w or not img_h: print(f"[跳过] 无法获取图片尺寸: {file_name}") issue_count += 1 continue # 复制图片到目标目录 import shutil if os.path.exists(src_path): shutil.copy2(src_path, dst_img_dir) else: print(f"[警告] 图片不存在: {src_path}") label_lines = [] for ann in annotations_by_image.get(img_id, []): # 过滤无效标注 if 'bbox' not in ann: continue x, y, w, h = ann['bbox'] if w <= 0 or h <= 0: issue_count += 1 continue # 归一化转换 x_center = (x + w / 2) / img_w y_center = (y + h / 2) / img_h norm_w = w / img_w norm_h = h / img_h # 类别ID映射 yolo_id = cat_id_to_yolo_id.get(ann['category_id']) if yolo_id is None: print(f"[跳过] 未知类别ID: {ann['category_id']}") issue_count += 1 continue label_lines.append(f"{yolo_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}") # 写入txt标签 txt_path = os.path.join(output_dir, 'labels', split, os.path.splitext(file_name)[0] + '.txt') with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(label_lines)) print(f"转换完成: train {train_count} 张, val {val_count} 张, 问题标注 {issue_count} 条") print(f"输出目录: {output_dir}") if __name__ == '__main__': convert_coco_json_to_yolo( coco_json_path='tiny_person_coco.json', output_dir='tiny_yolo', image_dir='tiny_person_images', train_ratio=0.8 )

3.2 脚本怎么用

先把官方标注文件命名为tiny_person_coco.json,图片放在tiny_person_images目录下,然后直接运行脚本。跑完会在tiny_yolo下生成完整目录:

tiny_yolo/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

代码里有一点我刻意加了:尝试用PIL读取真实图片尺寸。跑TinyPerson这类数据时,图片是原始尺寸还好说,但如果有些版本图片被预处理过,这一步就是保命用的。宁可多花几秒钟读图,也不要为了省事直接信JSON里的尺寸字段。

3.3 生成Dataset配置文件

转换完目录,还要给YOLO训练写一个data.yaml。拿YOLOv8举例,在tiny_yolo目录下新建data.yaml

path: ./tiny_yolo train: images/train val: images/val names: 0: sea_person 1: earth_person

如果你的类别顺序和我不一样,一定先看labels里第一列的数字对应什么名字,再改names。这一步错位,模型训练完输出全是乱的。

4. 转换后的自查与可视化

转换脚本跑完不代表万事大吉。我自己第一次转完直接拿去训练,结果模型完全学不到东西,回头检查才发现小问题一堆。转换完必须做一轮自查,这步省不了。

4.1 数据分布检查

我先说几个自查的指标:

标注数量分布:统计每个txt文件的行数,看是否有全部为空的标签文件。TinyPerson里有些极端难例目标实在太小,标注员可能漏标,但也不能出现大量空标签把训练带偏。空标签文件如果超过总样本的10%,建议检查JSON原始文件是不是解析出问题了。

目标面积分布:YOLO训练时如果目标归一化宽度小于0.01(即目标宽度不到图片宽度的1%),损失函数里目标框的权重会非常小,模型很容易忽略这类目标。记录一下转换后目标宽高的min/median/max,做到心里有数。

import os import numpy as np label_dir = 'tiny_yolo/labels/train' sizes = [] empty_count = 0 for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file), 'r') as f: lines = f.readlines() if len(lines) == 0: empty_count += 1 for line in lines: parts = line.strip().split() if len(parts) == 5: w, h = float(parts[3]), float(parts[4]) sizes.append((w, h)) sizes = np.array(sizes) print(f"空标签文件数量: {empty_count}") print(f"目标数量: {len(sizes)}") print(f"归一化宽度: min={sizes[:,0].min():.4f}, median={np.median(sizes[:,0]):.4f}, max={sizes[:,0].max():.4f}") print(f"归一化高度: min={sizes[:,1].min():.4f}, median={np.median(sizes[:,1]):.4f}, max={sizes[:,1].max():.4f}")

如果发现中位数已经到0.01以下,说明这数据集确实够“小目标”,训练时要针对性地调整策略。

4.2 可视化验证

脚本跑完必须抽几张贴框图看看。用OpenCV把归一化坐标换算回像素坐标,画在图上,肉眼确认框的位置是否贴合目标。

import cv2 import os def draw_yolo_boxes(image_path, label_path): img = cv2.imread(image_path) h, w = img.shape[:2] with open(label_path, 'r') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) x_c, y_c, box_w, box_h = map(float, parts[1:]) x1 = int((x_c - box_w / 2) * w) y1 = int((y_c - box_h / 2) * h) x2 = int((x_c + box_w / 2) * w) y2 = int((y_c + box_h / 2) * h) color = (0, 255, 0) if cls_id == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 1) return img sample = 'tiny_yolo/images/train/000000.jpg' sample_label = 'tiny_yolo/labels/train/000000.txt' result = draw_yolo_boxes(sample, sample_label) cv2.imwrite('check_visual.jpg', result)

画出来的图如果框整体偏移、框大小明显不对,多半是归一化时图片尺寸和真实尺寸不一致。如果框偏移只出现在特定图片,检查那张图是不是分辨率和其他图不一样,或者JSON里的尺寸字段写错了。

5. 训练时最常见的坑与排查

转换完数据,喂给YOLO训练,又会遇到新一轮问题。这一节我把训练TinyPerson时最常见的问题和排查思路整理成表格,基本都是我实际踩过的。

5.1 常见问题速查表

现象可能原因排查方式
loss直接飞NaN标签里有inf或nan值写脚本扫描txt,检查是否出现非数字字符
mAP一直在0附近标签类别ID与data.yaml不一致打印一个txt文件看第一列数字,核对names
一张图只检测出一个框标签文件被覆盖/部分丢失确认图片和txt文件一一对应,无重名覆盖
训练正常但小目标完全检不出输入分辨率太小或anchors不合适调高输入尺寸(如640到1280),关闭mosaic
验证时框位置整体偏移归一化时用了错误图片尺寸重新读取真实图片尺寸,再次转换
训练集loss很低,验证集mAP为零数据泄漏,同一张图被分到train和val检查是否有重叠文件,重新划分数据集

5.2 类别ID错位的典型案例

这个坑我印象太深了。早期转完TinyPerson后没仔细看,直接拿YOLOv5默认的COCO预训练权重训练,发现模型输出一直在“预测人”,但精确定位全错。排查了半天,最后发现是转换脚本里把类别映射写反了,sea_person对应1,earth_person对应0,而data.yaml里写的是反过来。模型不是学不会,是学了一套类别错位的标签。

处理这类问题,核心原则就一条:转完先抽查5-10个标签文件,确认第一列数字对应的类别和图片里实际目标一致,再进训练流程。哪怕检查花20分钟,也比训练一晚上后发现白跑强。

5.3 小目标训练的参数调整建议

TinyPerson数据集的特殊性在于目标极小,用YOLO默认参数训练效果很一般。我在实验里试过几个参数调整,实测有效:

开启SAHI切片推理:训练时用原图(或切图训练),推理时用SAHI这类切片工具把大图切成若干小图分别检测,再合并结果。TinyPerson原图里目标太小,整图推理容易漏检,切图后小目标的相对尺寸变大,检测率提升明显。

调高输入分辨率:YOLO默认输入640x640,但TinyPerson的原始图片分辨率通常在1024x1024甚至更高,直接resize到640把目标缩得更小。有几个方案可以考虑:训练时用1280x1280输入;或者保留原分辨率,只在训练时随机裁剪局部区域(相当于放大目标)。显存不够的话,用切图训练更实际。

关闭或降低mosaic增强:mosaic对常规目标检测很有用,但对极小目标可能起反作用——4张图拼在一起,小目标被进一步缩小,而且拼接边界容易把目标截断,导致标注失真。实测下来,对TinyPerson这类数据,关闭mosaic,改用简单的平移、翻转、色彩抖动,训练稳定性反而更好。

6. 除了转换,这个数据集还能怎么用

最后顺便聊一下拿到YOLO格式TinyPerson之后,除了直接训练,还能做点什么。这个数据集的真正价值不只是当训练集,它还适合用来做数据增强验证、小目标检测器对比评测,以及配合其他数据集做联合训练。

TinyPerson最大的问题是数据量不大、场景单一(海上为主),单独训练出来的模型换到城市街道、园区监控这些场景,泛化能力会比较有限。我建议把它和常规行人数据集混合使用,比如在通用行人数据集上先预训练,再用TinyPerson微调,这样能兼顾通用场景和小目标专门场景。

另一个思路是类别合并。如果你最终任务只是“检测人”,不太关心人在海上还是岸上,训练时把sea_personearth_person合并成单一person类别会更好。类别多意味着每个类别的正样本更少,对只有1600个实例的小数据集来说,合并类别能让模型学到更鲁棒的行人特征,而不是纠结于场景区分。

我自己在实际使用中的体会是:TinyPerson是个非常适合用来检验小目标检测能力的“压力测试”数据集,模型在它上面的表现基本能反映真实小目标场景中的下限水平。如果你做的项目涉及高空监控、无人机视角、远距离行人识别这些方向,花一个下午把数据格式搞定,后面能少走很多弯路。最后再分享一个小技巧:转换脚本和检查脚本别删,整理成一个数据预处理的小工具库,下次碰到COCO格式的数据集,改个路径直接就能复用。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 6:13:59

基于C#的在线SPC质量监控系统开发实战

简介&#xff1a;本资源是一套基于统计过程控制&#xff08;SPC&#xff09;理论开发的在线质量监控系统C#源码&#xff0c;面向计算机、自动化及相关专业本科生毕业设计与课程实践需求&#xff0c;解决制造业场景下生产数据实时采集、过程稳定性分析与异常预警等核心问题。压缩…

作者头像 李华
网站建设 2026/9/1 6:11:06

EPLAN Electric P8实战:锂电池生产线电气设计全流程解析

大家好&#xff0c;我是专注于工业电气设计领域的技术博主。最近在参与一个锂电池PACK&#xff08;电池包&#xff09;生产线的电气设计项目时&#xff0c;深刻体会到一套高效、规范的电气设计软件对于项目质量和效率的决定性影响。面对复杂的工艺流程、海量的设备信号和严格的…

作者头像 李华
网站建设 2026/9/1 6:06:35

AI辅助剪辑视频:从素材整理到工作流搭建的落地指南

AI辅助剪视频这件事&#xff0c;我最近连续跑了很多条口播、录屏和课程的素材&#xff0c;最大的感触是&#xff1a;工具本身真的不难&#xff0c;难的是怎么把信息喂给AI&#xff0c;怎么把工具组合起来。只要前置信息清楚&#xff0c;AI粗剪、字幕、去空白这些杂活确实能省下…

作者头像 李华
网站建设 2026/9/1 6:06:08

构建标准化引擎测试Demo:从设计原则到可复现实践

在开发过程中&#xff0c;无论是评估一个新的计算引擎、验证算法逻辑&#xff0c;还是向团队演示技术方案&#xff0c;构建一个清晰、可复现的“Demo场景”都是至关重要的第一步。然而&#xff0c;很多开发者会陷入两个极端&#xff1a;要么搭建的Demo过于简陋&#xff0c;无法…

作者头像 李华
网站建设 2026/9/1 6:00:44

大模型 API 成本评估指南:拆解计费结构、沉淀可复现对比流程

在实际项目的模型选型讨论中&#xff0c;经常会出现一句话把成本问题一笔带过的现象&#xff1a;“某个模型更便宜&#xff0c;直接换过去就行。”比如当大家看到“GLM-5.3 成本仅为 FABLE 5 的八分之一”这类结论时&#xff0c;不要急着把它当既定事实写进方案。大模型 API 的…

作者头像 李华