目标检测入门:COCO JSON转YOLOv8训练成人小孩数据集全流程解析
发布时间:2026/9/28 6:39:43来源:尧图网络
简介面向计算机视觉任务构建的成人与小孩检测数据集包含1738张原始JPG图片覆盖多种拍摄场景下的成年人与儿童样本为训练和评估识别模型提供了真实数据基础。压缩包内附带3个JSON注解文件按COCO标准格式组织标注信息包括目标类别、边界框等关键内容可直接投入YOLO、Faster R-CNN等主流检测框架使用极大减少数据预处理环节的工作量。资源包整体大小为91.49MB文件总数为1741个图片与标注一一对应数据划分灵活可用于训练集、验证集与测试集的自行构建也便于后续数据增强和扩充。当前已有1998人学习下载适合机器学习初学者、算法工程师以及高校数据科学课程实验亦能作为相关竞赛或落地项目的起步数据。数据集的识别率基线约70.9%能够胜任常见成人与小孩识别需求使用者可在其基础上继续调参、优化模型结构或引入更多数据以进一步提升准确率和泛化能力。1. 成人和小孩数据集1738 张图能做什么70.9% 够不够用如果你要做安防监控、门店客流分析或者儿童走失预警第一道坎永远是同一个手里没有带年龄标签的人像数据。这个成人和小孩数据集1738 张原始图片、COCO JSON 格式标注、验证集识别率 70.9%看起来是“开箱即用”但 1738 张对目标检测来说只是起步量级70.9% 也只是一个可以被复现的基线。它更适合的场景是你想先跑通一条“COCO JSON 标注 → 目标检测训练 → 误差分析 → 数据增强”的完整链路而不是直接上线生产系统。我接下来按这个顺序讲先教你把 COCO JSON 读懂、校验、转成训练格式再讲用 YOLOv8 训练的关键参数然后教你怎么把 70.9% 拆开看最后列几个一定会踩的坑和往上提分的手段。适合人群是目标检测入门三个月左右的工程师或者刚拿到这份数据想做年龄分组检测的算法同学。2. 读懂 COCO JSON 标注从文件结构到能用的训练集很多人拿到标注文件第一反应是打开 JSON 看一眼然后被嵌套结构劝退。COCO JSON 是被检测、分割、关键点任务共用的一套格式你要做检测只需要关心三个顶层字段images、annotations、categories。这三个字段的对应关系理顺了后面所有转换和训练都不会迷路。2.1 COCO 标注的顶层结构三段式决定了你的训练集长什么样COCO JSON 顶层是一个字典常见的键有info、licenses、images、annotations、categories。前两个是描述信息训练脚本直接忽略images是图片清单annotations是目标框清单categories是类别清单。images里的每个元素是一条图片记录核心字段是id、file_name、width、height。annotations里每个元素是一条目标记录核心字段是id、image_id、category_id、bbox以及可选的area和iscrowd。categories里每个元素是id和name的映射比如{id: 1, name: adult}、{id: 2, name: child}。这里有一个容易误会的点COCO 官方数据集的categories里id1通常是 person但这个数据集不是官方数据它是自定义的成人和小孩两类所以category_id的顺序完全取决于作者怎么写的你拿到手第一步应该是打印categories而不是默认它就是{0: adult, 1: child}。另外注意bbox的格式是[x, y, width, height]单位是像素坐标原点是图片左上角不是中心点。很多新手在转 YOLO 格式时把 x、y 当成中心点直接用结果训练出来的框全部偏到角落这是最典型的翻车现场之一。字段类型作用易错点images.idint图片唯一编号annotations 里的image_id必须能匹配上images.file_namestr图片文件名可能带子目录前缀annotations.bboxlist[float][x, y, w, h]像素坐标x,y 是左上角不是中心annotations.category_idint指向 categories.id不一定是 0/1 这种连续编号categories.namestr类别名先打印确认 adult/child 谁是谁2.2 第一个脚本解析并校验标注先分清图片和框谁对不上拿到数据不要急着训练先写个几十行的脚本统计一下图片数、类别数、每个类别的框数、有多少图片没有任何标注、有多少框的坐标越界。这个检查能帮你省掉后面排查数据问题的半天时间。import json from collections import Counter # 打开 COCO JSON注意编码Windows 上务必指定 utf-8 with open(data/annotations.json, r, encodingutf-8) as f: coco json.load(f) images coco[images] annos coco[annotations] cats {c[id]: c[name] for c in coco[categories]} print(图片总数:, len(images)) print(标注目标总数:, len(annos)) print(类别映射:, cats) # 统计每个类别的框数量 cat_counter Counter() for a in annos: cat_counter[cats[a[category_id]]] 1 print(每个类别的框数量:, dict(cat_counter)) # 检查 image_id 是否都能匹配到图片以及 bbox 是否越界 image_ids {img[id] for img in images} missing 0 out_of_range 0 for a in annos: if a[image_id] not in image_ids: missing 1 continue img next(img for img in images if img[id] a[image_id]) x, y, w, h a[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: out_of_range 1 print(image_id 匹配失败的标注数:, missing) print(bbox 越界的标注数:, out_of_range) # 找出没有任何标注的图片这些图训练时会被忽略但建议单独看一遍 annotated_img_ids {a[image_id] for a in annos} empty_images [img[file_name] for img in images if img[id] not in annotated_img_ids] print(无标注图片数:, len(empty_images))这段脚本的逻辑分三层先看统计总量再看类别分布最后做交叉校验。missing大于 0 说明 JSON 里写坏了要回炉out_of_range大于 0 说明原标注带越界框后面转 YOLO 时得处理empty_images多说明这份标注漏标情况严重裸训练会让模型把无目标的图学成背景。脚本里的next()遍历找图片虽然慢但 1738 张图完全够用不需要优化成字典查找。2.3 把 COCO JSON 转成 YOLO 训练格式1 个脚本带走YOLOv8 官方推荐的数据格式是每张图对应一个同名.txt每行写class_id cx cy w h前两个是归一化后的中心点坐标后两个是归一化后的宽高。转换脚本的要点有三个category_id重映射、坐标归一化、越界框裁剪。另外注意类别 ID 必须从 0 开始连续编号YOLO 不认id1,2这种从 1 开始的写法。import json import os def coco_to_yolo(coco_path, out_dir): with open(coco_path, r, encodingutf-8) as f: coco json.load(f) # 把原始 category_id 重映射为 0,1,2... cats coco[categories] cat_id_map {c[id]: i for i, c in enumerate(cats)} print(类别重映射:, cat_id_map) # 每张图片一个标注文件 anno_by_img {} for a in coco[annotations]: anno_by_img.setdefault(a[image_id], []).append(a) os.makedirs(out_dir, exist_okTrue) for img in coco[images]: img_id img[id] base_name os.path.splitext(img[file_name])[0] txt_path os.path.join(out_dir, base_name .txt) lines [] for a in anno_by_img.get(img_id, []): cat_id cat_id_map[a[category_id]] x, y, w, h a[bbox] # 修正越界框把超过图片边界的部分裁掉 img_w, img_h img[width], img[height] x2 min(x w, img_w) y2 min(y h, img_h) w max(x2 - x, 1) # 最小宽度 1px防止除零 h max(y2 - y, 1) # 归一化cx cy 是中心点相对图片宽高的比例 cx (x w / 2) / img_w cy (y h / 2) / img_h nw w / img_w nh h / img_h # 极端情况兜底所有值 clamp 到 [0, 1] cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) nw min(max(nw, 0.0), 1.0) nh min(max(nh, 0.0), 1.0) lines.append(f{cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(完成标注文件输出到:, out_dir) coco_to_yolo(data/annotations.json, data/labels)这段代码有三个边界处理要记住。第一是负坐标原标注可能带了少量负的 x 或 y这个版本没有处理负坐标如果你在校验阶段发现负值要在x2 min(x w, img_w)之前先做x max(x, 0)否则归一化后可能出现负数中心点。第二是宽高为 0 的框这类要直接过滤掉否则训练时 loss 会先出现 NaN。第三是cat_id_map用的是enumerate它的顺序取决于 JSON 里categories数组的排列顺序所以转之前一定先去打印这个映射表。3. 用 YOLOv8 在 1738 张图上训练从划分到跑通最小命令数据格式理清之后就到了训练阶段。1738 张图属于典型的小数据集训练本身不会超过半小时单张 2080Ti 级别显卡但真正决定 70.9% 能不能复现、能不能超过的是你在训练前的数据划分和类别重采样。这章讲透划分与参数这两件最重要的事。3.1 数据划分先按场景切再随机分 train/val顺序不对全白做很多人直接train_test_split一下按 8:2 切完就训练最后 mAP 很好看但换到真实视频上一塌糊涂。原因只有一个同一段视频的连续帧、同一个人的不同照片被同时分进了 train 和 val模型在训练时已经“见过”验证集里的人验证分数虚高。这个坑在行人、人脸、年龄分类数据集里尤其常见因为采集时往往是一个人多张连拍。正确做法是先按“人”或者“场景”分组再切分。如果原始数据没有人物 ID 分组退而求其次是按图片文件名前缀分组。假设文件名形如scene01_001.jpg可以把scene01作为分组键。import os import random from collections import defaultdict random.seed(42) # 按文件名前缀分组例如 scene01_001.jpg - scene01 img_dir data/images groups defaultdict(list) for name in os.listdir(img_dir): if not name.endswith(.jpg): continue prefix name.split(_)[0] # 按你的实际命名规则调整 groups[prefix].append(name) group_names list(groups.keys()) random.shuffle(group_names) # 80% 场景作为 train20% 场景作为 val split_idx int(len(group_names) * 0.8) train_groups set(group_names[:split_idx]) val_groups set(group_names[split_idx:]) # 生成 train.txt 和 val.txt内容为图片绝对路径 with open(train.txt, w) as f: for g in train_groups: for n in groups[g]: f.write(os.path.join(img_dir, n) \n) with open(val.txt, w) as f: for g in val_groups: for n in groups[g]: f.write(os.path.join(img_dir, n) \n) print(f训练图片数: {sum(len(v) for k, v in groups.items() if k in train_groups)}) print(f验证图片数: {sum(len(v) for k, v in groups.items() if k in val_groups)})这段切分脚本的要点不是代码本身而是“分组键”的选择。scene01这种前缀分组只能保证同一场景不串集不能保证同一个人不出现在两个场景里。所以分组粒度越细越好最好是采集时记录的人物 ID。如果你手里的数据只有图片没有 ID 信息就看文件名里能不能挖出“日期地点人物编号”这类字段宁可分组粗一点切出来的验证集偏低也不要随机裸切让结果虚高。3.2 跑通训练的最小命令与必调参数数据划分好之后需要准备一个 YOLO 格式的dataset.yaml文件指定 train/val 路径和类别列表。注意 YOLOv8 的类别列表顺序必须和标签文件里的class_id顺序一致这里就是 0 和 1 分别对应成年人和小孩。# dataset.yaml path: data train: train.txt val: val.txt names: 0: adult 1: child训练命令用yolo子命令就能跑起来。对 1738 张图的小数据集我建议从yolov8n起步不要一上来就yolov8x小模型泛化能力在小数据集上反而更稳训练速度也快方便你多轮试错。yolo detect train \ datadataset.yaml \ modelyolov8n.pt \ epochs80 \ imgsz640 \ batch16 \ lr00.005 \ patience15 \ projectruns/detect \ nameadult_child \ seed42训练参数里epochs80和patience15是一组配合小数据集容易在 40 个 epoch 附近过拟合patience 设为 15 意味着连续 15 轮验证指标不涨就提前停能省时间又不会漏掉最佳权重。batch16是显存约束下的默认值如果你的卡是 24G可以提到 32 甚至 64。lr00.005相比默认的 0.01 打了对折这是小数据集的血泪经验——数据量小的时候默认学习率经常导致 loss 在前期震荡后面收敛到很差的局部最优。seed42固定种子方便你对比不同实验。这里有个容易困惑的地方modelyolov8n.pt是官方在 COCO 上预训练好的权重它会自动把 COCO 的 80 类映射到你的 2 类。这个映射不是简单的“只保留 person 类”而是把整个检测头替换掉、重新随机初始化前面的 backbone 和 neck 保留 COCO 学到的特征。这正好是迁移学习的意义模型知道什么是“人形轮廓”你只需要教会它区分“大的人形”和“小的人形”。3.3 results.csv 里的识别率mAP50 和 mAP50-95 哪个才是 70.9%训练完成后在runs/detect/adult_child/下会生成results.csv里面每一行是一个 epoch 的指标。YOLOv8 默认记录多个指标包括mAP50和mAP50-95。标题里说的识别率 70.9%在目标检测语境下几乎可以肯定是mAP50也就是 IoU 阈值 0.5 时的平均精度。这个指标对框位置的精度要求比较宽松框稍微偏一点也不算错。mAP50-95则是从 0.5 到 0.95 每隔 0.05 取一个阈值算十个 IoU 下的 mAP 再取平均。对成人小孩检测这种任务mAP50-95能到 45%-55% 就已经不错了。我见过不少新手拿着mAP500.709很开心然后发现mAP50-95只有 0.40开始怀疑模型坏了其实不是是它们俩口径不同、难度不同。看results.csv时还要留一个心眼最后一行的 mAP 不一定是最佳的。YOLOv8 会选择验证集上mAP50-95最高的那一轮保存为best.pt所以你要看的是best.pt对应那一行的指标而不是last.pt的。真正部署时加载best.pt后续提到验证也默认用best.pt推理。4. 把识别率 70.9% 拆开看性能验证与误差分析70.9% 是一个平均值平均值最大的问题就是掩盖了类别差异。如果成年人的 AP 是 82%小孩的 AP 只有 43%那 70.9% 并不能说明模型对两类都友好。这章教你用验证集输出做两类指标的拆解以及从预测图上定位误差来源。4.1 把 70.9% 拆开成人 AP 和小孩 AP 分别多少用best.pt跑一次验证YOLOv8 会在输出目录里生成混淆矩阵confusion_matrix.png也会在终端打印每个类别的 AP。如果终端输出被刷掉了去runs/detect/adult_child/下找results.csv是没有分类的分类 AP 需要看confusion_matrix.png或者用下面的命令重新验一遍。yolo detect val \ modelruns/detect/adult_child/weights/best.pt \ datadataset.yaml \ imgsz640 \ batch16 \ save_jsonTrue \ save_txtTruesave_jsonTrue会生成predictions.json里面每条预测记录包含category_id、bbox和score。你可以用json模块按类别拆出来逐类计算 AP但更快的做法是直接看训练输出目录下的confusion_matrix.png横轴是真实类别纵轴是预测类别对角线越亮越好。这张图一眼就能看出哪些child被预测成了adult而这类混淆往往是年龄边界造成的——一个 12 岁的少年到底算小孩还是算成年人标注员自己都未必一致。做这个拆解的意义在于决定后续策略。如果小孩类的 AP 明显更低优先做的是数据增强和重采样如果两类差距不大但整体不高说明 1738 张图的样本量本身不足以支撑更复杂的特征学习优先做的是扩展数据而不是调参。4.2 从预测图里找翻车点漏检、误检、框偏移各说明什么分类 AP 拆完还要看可视化预测结果。用best.pt对验证集做一次带框输出的推理把带有预测框的图保存下来挑 20 张左右肉眼看一下按失败模式归类。yolo detect predict \ modelruns/detect/adult_child/weights/best.pt \ sourcedata/val \ imgsz640 \ conf0.25 \ saveTrue \ projectruns/predict \ nameval_viz看预测图时重点找三类错误。第一类是漏检图上明明有小孩但模型没出框常见原因是目标太小在 640x640 分辨率下小于 32x32 像素或者目标被严重遮挡。第二类是误检把背景里的广告牌人像、树影当成成人这类通常是因为训练数据里缺少“假人”负样本。第三类是框偏移检测到了人但框明显偏大或偏小这类是回归精度问题对小数据集很常见因为标注框本身可能就不够紧。这三类错误的修正方向完全不同。漏检偏多加小目标增强把训练图缩放到不同尺度误检偏多收集“有人形但非真人”的负样本图框偏移偏多回到数据标注这层把标注框外扩或收紧 10% 再看。不要一上来就堆模型复杂度先把错误归类搞清楚。4.3 类别不均衡是 70.9% 的主因先做数据增强还是先重采样在 2.2 节我们统计过每个类别的框数量。成人多、小孩少这在年龄检测数据集里几乎是必然的。类别不均衡会直接把 mAP 拉高或拉低取决于“多”的那一类是否容易学习。成人样本多、特征稳定AP 高小孩样本少、遮挡多、年龄跨度大AP 低。平均之后 70.9% 就被成人类抬上去了。处理不均衡有两条路实践顺序是先重采样再增强。重采样最简单的方式是给 loss 加类别权重YOLOv8 的class_weights参数需要手动传但对二分类问题更直接的做法是训练时在 dataset 上做 oversample把小孩图片复制一份混进训练集让两类的样本量接近 8:2 变成 6:4。注意是复制图片而不是复制标签因为 YOLO 训练时的mosaic增强会在每轮随机裁剪同一张图复制后会被裁出不同的子图效果类似额外增加样本。做完重采样再看小孩类的 AP 是否上升。如果上升不明显说明小孩类的难度瓶颈不在样本量而在类内差异太大——1 岁和 12 岁的小孩外观差异比成年人和小孩的差异还大这时候单纯加样本是无效的需要做的是把小孩类按年龄段再拆分或者接受当前模型的边界能力。5. 成人小孩数据集避坑记录5 个你八成会踩的问题训练目标检测小数据集翻车点高度集中。下面五条是我在类似数据集上反复踩过的每一条都按现象、原因、解决来写直接对着排查。5.1 验证集 mAP 挺高换一段新视频就疯狂漏检现象在 val 上 mAP50 有 0.70但把训练集里没出现过的视频喂进去小孩漏检超过一半。原因数据划分时用了全局随机切分同一场景的连续帧同时出现在 train 和 val模型见过的场景直接暴露给了验证集分数虚高。解决回到 3.1 节按场景分组再切分。如果已经训完了把 val 里跟 train 同场景的图片全部剔除重训一次。这个坑最隐蔽因为它不报错只是让你的模型自我感觉良好。5.2 bbox 出现负值YOLO 训练直接 loss 为 NaN现象转换完标签训练第一个 epoch loss 就是 nan或者 loss 巨大且不下降。原因COCO JSON 里存在 x、y 为负的标注框归一化后变成负坐标YOLO 的损失函数对负坐标敏感。解决在转换脚本里补上x max(x, 0)和y max(y, 0)同时过滤掉裁剪后宽或高小于 2 像素的框。这类脏数据在人工标注的数据里很常见尤其在图片边缘处标注时鼠标稍微拖出边界就会产生负坐标。5.3 模型把成年人误检成小孩尤其是青少年现象混淆矩阵里adult → child的误检比例明显偏高。原因年龄边界是主观的标注员对“小孩”的定义可能到 14 岁也可能到 12 岁模型学到的边界自然模糊。解决在数据标注阶段统一标注规则比如“小孩0-12 岁”然后重新过一遍边界样本。如果你没有权限改标注规则就在后处理里对预测结果加一个基于 bbox 尺寸的启发式过滤——同一张图里框面积明显更大、且置信度处于临界区间的目标倾向于判为成人。5.4 训练到 30 轮就停best.pt 反而比 last.pt 差现象patience15但 30 轮就提前停了而且当时的 mAP50 不是最高点。原因学习率下降太快lr0 太大导致后期震荡验证集指标波动剧烈patience 误判为“不再提升”。解决把 lr0 降到 0.001-0.003或者把cos_lrFalse换成固定学习率让后期训练更平稳。小数据集本身容易过拟合学习率必须保守这也是我之前强调 lr0 打折的原因。5.5 标签文件的类别 ID 和图不对应推理结果两类全乱现象训练正常结束但推理时明明图里是小孩模型偏标成 adult 或反过来。原因COCO JSON 里的categories数组顺序不是[adult, child]而是[child, adult]或者夹杂了其他类。转换脚本里enumerate直接用了数组顺序类别就错位了。解决转换后打印cat_id_map并且随机抽三张标签 txt人工确认0对应的到底是 adult 还是 child。这个坑通常只在第一次接触新数据集时出现但一旦出现就是全盘皆输排查起来最费时间。6. 把 70.9% 往上推增强、二次标注与迁移学习的三个技巧数据增强是 1738 张图最直接的“后悔药”但不要全开。mosaic1.0在 YOLOv8 默认开启对小数据集我建议保留mixup0.2可以加上帮助模型平滑类别边界hsv_h0.015、hsv_s0.7这类颜色增强要克制因为监控场景色调相对固定开太大会引入训练分布外的颜色。真正值得调的是scale0.5和fliplr0.5它们直接增加目标尺度变化对小孩这种小目标最有效。二次标注是我更推荐的手段。把best.pt跑在未标注的新视频帧上导出带框结果再用 CVAT 或 labelimg 这类标注工具打开只需要人工复核修正而不是从零画框。1738 张图扩展到 3500 张一天半的复核时间就能完成效果比任何调参都明显。注意复核时优先修正两类框置信度在 0.3-0.5 之间的难例以及边框与目标轮廓明显不符的样本。最后一个技巧是迁移学习的层次控制。yolov8n.pt已经学到通用人形特征如果你发现模型在真实场景泛化差尝试冻结前 10 层只训练检测头和后几层训练 20 个 epoch 后再解冻全部层微调。这个做法能防止小数据集破坏底层特征。我自己的习惯是先跑一轮基线再看混淆矩阵决定要不要动迁移策略——70.9% 不是终点它只是告诉你这份数据和这套流程跑通了往上的空间在于数据扩展而非模型堆砌。希望这些经验能帮你少走几趟弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网