YOLOv9成人小孩检测实战:从1738张图到70.9%的调参与标注全流程
发布时间:2026/9/12 23:27:00 锦皓数字建站

简介面向计算机视觉与深度学习入门开发者这份成人小孩识别数据集包含1738张原始图片及配套的YOLOv9格式标注文件可用于训练和评估“成人/小孩”检测模型。虽然整体识别率约70.9%但数据来自真实场景且标注完整适合作为算法验证、模型调优或课程设计的基准数据。包内共2000个文件以1738个txt标注文件为主体另含261张jpg原始图片和1个yaml配置文件压缩包大小91.41MB结构简洁解压后即可按YOLOv9规范读取训练。已有167人学习下载可作为快速上手YOLO系列数据集的实践样本帮助理解标注格式、类别配置与训练流程。1. 1738张成人小孩图片yolov9标注格式下的70.9%是什么概念接到一个真实场景的任务用一份1738张原始图片的成人小孩数据集训练一个能区分成人和小孩的检测模型最终识别率卡在70.9%。这个数字如果放在二分类目标检测任务里说实话并不算好看——如果只是做“图里有没有小孩”的分类正确率做到90%以上是常见目标但如果按yolov9的mAP0.5去算70.9%意味着有近三成的目标框没被正确识别。问题往往不在模型结构不够新而在于这份数据集的标注一致性、类别平衡和训练参数。这篇文章就直接从这1738张图和yolov9格式标注出发把从原始图片到70.9%这条路上每一步的关键操作和调参逻辑拆开讲适合接手过yolo系列但还没系统调过公开或私有数据集的人也适合想把二分类检测精度往上拉的工程师。2. 成人小孩数据集的原始图清理与类别平衡2.1 1738张图里先做一次人工核对拿到任何yolov9格式数据集第一件事不是急着训练而是把图片本身过一遍。1738张原始图片听起来不多但人工快速翻阅一遍远比直接训练划算。常见做法是用文件管理器按缩略图浏览或者写一个简单的OpenCV脚本循环展示图片和对应标签。重点检查三类问题图片是否损坏、是否重复、是否有明显误标。很多数据集的图片来自网络爬取会有少量文件打不开或者解码失败。这类坏图如果混进训练集轻则训练中断重则导致loss异常。一张图片一个文件不需要全部打开用一段脚本就能全量体检import cv2 import os from pathlib import Path img_dir Path(images) bad_imgs [] for p in img_dir.glob(*.jpg): img cv2.imread(str(p)) if img is None: bad_imgs.append(p.name) print(f损坏或无法解码: {p.name}) print(f共 {len(list(img_dir.glob(*.jpg)))} 张图坏图 {len(bad_imgs)} 张)这段脚本用cv2.imread读取图像返回None就代表文件损坏。实际操作中我会再叠加一个检查读出来的img.shape里宽高不能为0并且通道数要是3。有些图片是灰度图或者带透明通道yolov9训练时虽然能强行读入但会造成颜色空间的隐性偏差。对成人小孩这种依赖服饰颜色、肤色特征的二分类以及后续训练稳定性最好统一转成RGB三通道JPG再喂给模型。2.2 用脚本统计类别分布和标签异常yolov9的标签文件是txt每行格式是class_id x_center y_center width height其中中心坐标和宽高都是相对于图片宽高的归一化数值。拿到标签后第一步统计每个类别的目标数量以及每张图的目标数分布。这一步能直接暴露出数据集的平衡问题。from pathlib import Path import numpy as np label_dir Path(labels) stats {adult: 0, child: 0} per_img_obj_count [] for lbl in label_dir.glob(*.txt): with open(lbl, r) as f: lines [line.strip() for line in f.readlines() if line.strip()] per_img_obj_count.append(len(lines)) for line in lines: parts line.split() cls_id int(parts[0]) if cls_id 0: stats[adult] 1 elif cls_id 1: stats[child] 1 # 检查坐标范围 x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and w 0 and h 0): print(f非法坐标: {lbl.name}: {line}) print(类别目标数:, stats) print(每张图目标数分布: 均值 {:.2f}, 最大 {}.format(np.mean(per_img_obj_count), max(per_img_obj_count)))这段代码同时做了两件事统计类别数量并检查每个标注框的坐标是否在合法范围。我见过不少数据集的标注框出现中心点大于1或者宽高为负的情况通常是在转换坐标时没有除以图片实际宽高或者混入了未归一化的绝对坐标。一旦出现这类问题跑训练时loss会剧烈震荡最后mAP卡在60%左右上不去。2.3 常见误标小孩脸部露半张、成人背影成人小孩检测的难点不在大目标而在小目标、遮挡和模糊。在实际清理中高频出现的误标有三类。第一类是画面中只有一个半身背影标注为“adult”或“child”时完全靠体型判断但6岁小孩和偏瘦成年人的背影在远距离低分辨率下几乎无法区分。第二类是脸部被口罩、帽子、围巾大面积遮挡仅剩裸露额头或眼睛这类特征不足以支撑模型学出稳定边界。第三类是人群密集场景比如游乐场多个小孩挤在一起标注框互相重叠甚至一个框里框住了两个小孩——这种框会让模型在训练时产生梯度冲突。对这三类情况我的处理办法很直接不抱侥幸心理全部剔除。剔除标准是“如果让我只看这个框我能确定它是成人还是小孩吗”。不确定就删。1738张图删掉100到200张完全正常因为训练目标是把剩余干净样本的识别率拉到更高而不是用脏数据凑数。如果你需要量化判断可以统计一下框的宽高比分布如果出现大量极宽或极高的异常框多半是标注框没贴住目标。检查项常见异常处理建议图片完整性文件损坏、单通道、分辨率异常删除或重编码标注坐标中心点越界、宽高为负、未归一化统一换算后校验类别平衡成人2000框、小孩300框收集小孩数据或做重采样框质量框太小、重叠严重、包含多目标剔除或重新标注3. 用yolov9格式做成人小孩标注从VOC或COCO转换成txt3.1 yolov9标注的内在结构class cx cy w hyolov9的标签格式和yolov5、yolov8完全一致属于Ultralytics系列通用的txt格式。每行五个数值类别ID0为成人1为小孩或根据你自己的class顺序、归一化中心点x坐标、归一化中心点y坐标、归一化宽度、归一化高度。注意这里的归一化是除以图片的原始宽度和高度不是除以边界框的实际像素。很多新手会忽略这点直接用像素坐标写进去导致模型训练时预测框几乎不会命中。如果你手里的原始数据是LabelImg导出的VOC XML格式或者COCO的JSON格式都需要转成上述txt。转换过程本身不复杂但坑在于路径对应关系。yolov9训练时图片和标签放在两个并列目录下只要文件名相同后缀不同训练脚本会自动匹配。所以转换后一定要确保每张图片都有一个同名的txt文件哪怕这张图没有目标也要生成一个空txt。3.2 用Python脚本批量转换XML到YOLO下面这段脚本适用于最常见的VOC XML格式转换到yolov9标签。假设你的XML文件都放在xml_dir对应的图片在img_dir脚本会读取每个XML里所有的object把bndbox坐标转换成归一化的cx, cy, w, h。import xml.etree.ElementTree as ET from pathlib import Path def convert_xml_to_yolo(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() labels [] for obj in root.findall(object): name obj.find(name).text.strip() if name adult: cls_id 0 elif name child: cls_id 1 else: print(f未知类别 {name} 在 {xml_path.name}跳过) continue bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 裁剪到图片边界 xmin max(0, xmin); ymin max(0, ymin) xmax min(img_w, xmax); ymax min(img_h, ymax) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h labels.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path out_dir / (xml_path.stem .txt) with open(out_path, w) as f: f.write(\n.join(labels)) return len(labels) xml_dir Path(xmls) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_file in xml_dir.glob(*.xml): # 实际项目中应读取图片尺寸这里用常量示例 img_w, img_h 1920, 1080 n convert_xml_to_yolo(xml_file, out_dir, img_w, img_h) print(f{xml_file.name}: {n} 个目标)逻辑说明脚本先解析XML里的每个目标根据类别名映射到0或1然后对bndbox坐标做边界裁剪再转换为YOLO要求的格式。边界裁剪这一步不是可有可无有些标注框会稍微超出图片范围如果不裁剪归一化坐标会大于1训练时容易出现边界框溢出。参数说明img_w和img_h必须与每张图片的实际尺寸一致但在批量转换时很多XML里其实自带了size节点。我更推荐从XML里读取而不是写死常量size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text)用XML里记录的尺寸最稳妥因为某些XML文件头里的尺寸和实际图片可能有一两个像素的偏差但归一化计算只需要相对比例所以允许微小误差。如果转换后发现标注框整体偏移优先去核对XML里的尺寸信息是否是图的原始尺寸。3.3 转换后必须做的标注可视化转换完成不等于标注正确。最有效的检查办法是把标注框直接画到图片上人眼扫一遍而不是去读txt里的数字。用下面这段代码快速生成可视化结果import cv2 from pathlib import Path img_dir Path(images) label_dir Path(labels) vis_dir Path(visualized) vis_dir.mkdir(exist_okTrue) colors {0: (0, 255, 0), 1: (0, 0, 255)} # adult绿色child红色 for img_path in list(img_dir.glob(*.jpg))[:50]: # 抽查前50张 img cv2.imread(str(img_path)) h, w img.shape[:2] lab_path label_dir / (img_path.stem .txt) if not lab_path.exists(): print(f缺少标签: {img_path.name}) continue with open(lab_path) as f: lines [line.strip() for line in f if line.strip()] for line in lines: cls, cx, cy, bw, bh line.split() cls int(cls); cx float(cx)*w; cy float(cy)*h bw float(bw)*w; bh float(bh)*h x1 int(cx - bw/2); y1 int(cy - bh/2) x2 int(cx bw/2); y2 int(cy bh/2) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls], 2) cv2.putText(img, child if cls1 else adult, (x1, max(0, y1-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[cls], 2) cv2.imwrite(str(vis_dir / img_path.name), img)这段代码读入图片后把归一化的坐标还原成了像素坐标然后用不同颜色绘制成人框和小孩框。我会把可视化结果按图片文件名排序后在文件管理器里快速滚动查看重点看三点框是否紧贴目标上下边缘、成人小孩的颜色分配是否符合预期、是否存在两个不同类别框大面积重叠。如果某个类别的框频繁画偏说明转换脚本里那个类别的ID映射错了如果所有框都扩大了一圈说明归一化时除以的尺寸和实际图片尺寸不一致。4. 在Ultralytics YOLOv9上训练成人小孩检测器4.1 准备数据目录与data.yamlyolov9在Ultralytics框架下训练数据目录结构遵循固定约定。两种方式一种是图片和标签分别放在images/和labels/下每个集合下再分train/和val/另一种是全部放在一起用txt文件列表指定划分。我推荐前者因为目录结构清晰不容易在数据集变动时漏掉某个文件。datasets/ ├── adult_child_yolo/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/划分比例一般按80/20做随机切分但要注意先对图片做随机打乱避免暗光照片全部落进验证集。对于1738张图的规模验证集大概在300到400张之间足够稳定评估。如果训练样本总数过少可以改成85/15但验证集不能少于200张。data.yaml是训练时最重要的配置文件内容如下path: datasets/adult_child_yolo train: images/train val: images/val nc: 2 names: 0: adult 1: child参数说明path指向数据集根目录train和val是相对根目录的子目录。注意不要写成绝对路径否则换机器跑需要改文件。nc是类别数量这里是2。names字典里的顺序必须与标签txt里的class_id一致如果这里写反了模型会把成人当小孩、小孩当成人训练出来的loss照样会收敛但预测结果完全错误。4.2 选择预训练权重与训练命令Ultralytics官方提供yolov9的预训练权重包括yolov9-c.pt、yolov9-e.pt等。对于成人小孩这种二分类任务不需要用最大的模型yolov9c足够推理速度也更快。如果算力有限可以退一步用yolov9s或直接使用yolov9-t但精度会下降。训练命令是yolo detect train \ modelyolov9c.pt \ dataadult_child.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns \ nameadult_child参数说明epochs100是最大训练轮数配合patience20意思是如果连续20轮mAP没有提升就提前停止。imgsz640是训练输入尺寸对于较小目标多的场景改成imgsz768或imgsz832能提升小目标召回率但会显著增加显存占用。lr00.01是初始学习率这个值对batch size敏感如果batch只有8建议把lr0降到0.005否则loss容易发散。batch16需要根据显卡显存调整6GB以下就设812GB可以设16。训练过程中终端会实时打印每个epoch的loss、precision、recall和mAP50。关注点在于训练前几个epoch的loss是否在下降如果loss一开始就乱跳需要按CtrlC停止降低lr0或者检查数据标注。训练结束时runs/adult_child/weights/best.pt和last.pt两个权重文件是核心产物后续推理、评估、部署都用best.pt。4.3 关键训练参数与70.9%的关系70.9%这个识别率如果指mAP0.5意味着模型在验证集上平均只有七成的预测框和真实框重叠度超过0.5。这个结果受几个参数直接影响。参数对成人小孩检测的影响建议值modelyolo规格越小参数越少训练快但上限低yolo9c或yolo9simgsz太低会让小目标消失太高增加显存压力640~768epochs不足会欠拟合过大配合early stop即可100~200batch过小导致BN统计不稳定过大容易内存溢出显存允许时尽量大lr0过大震荡过小收敛慢0.005~0.01mosaic增强会混合4张图对多目标位置推理有帮助默认1.0可调为0.5这几个参数里imgsz是最容易被忽略却对结果影响最明显的。成人小孩的身高差异在图像中体现为框的尺寸差异如果imgsz太小比如416那么一个在画面中只有30像素高的小孩缩放后只剩十几个像素卷积特征图上可能只剩下2x2区域完全无法表达轮廓信息。把imgsz调大到768往往能把mAP从70%拉到75%以上。5. 识别率70.9%的验证与失败拆解5.1 从results.csv和混淆矩阵看成人小孩各自的精度训练完成后Ultralytics会在runs/adult_child/results.csv里记录每个epoch的所有指标。用pandas直接读取可以快速找到训练过程中最佳epoch和对应精度import pandas as pd df pd.read_csv(runs/adult_child/results.csv) best df.loc[df[metrics/mAP_0.5].idxmax()] print(最佳epoch:, int(best[epoch])) print(mAP0.5:, round(float(best[metrics/mAP_0.5]), 4)) print(mAP0.5:0.95:, round(float(best[metrics/mAP_0.5:0.95]), 4)) print(精确率:, round(float(best[metrics/precision]), 4)) print(召回率:, round(float(best[metrics/recall]), 4))这里metrics/mAP_0.5就是我们常说的70.9%这种数字的来源。只看总数还不够要分解到每个类别。在runs/adult_child/目录下有一个confusion_matrix.png打开它就能看到成人、小孩、背景三类之间的混淆情况。confusion_matrix.png是一个二维矩阵行代表真实类别列代表预测类别。如果中间表示“child”的真实框被误判成“adult”的比例很高矩阵里对应位置的颜色就会很亮。通常二分类检测里出现高混淆集中在两类目标尺寸相近的场景以及非正面姿态下。5.2 检视val_batch0_prediction.jpg里的漏检与错检训练完成后的runs/adult_child/目录下会生成几张验证集预测结果图比如val_batch0_prediction.jpg。它会画出模型预测的框以及置信度。这时候不要只看最终数字要把这些图放大逐张看记录漏检和错检的规律。常见错检有几种模型把远处的成年人错标成小孩因为远处人体像素少体型特征模糊模型把推车里的小孩漏掉了因为被部分遮挡还有逆光条件下整个目标成为剪影模型只能给出很低的置信度。这些错检模式如果集中在某个时间段或某种拍摄场景说明训练集里这种负样本或难例不够需要考虑额外收集数据。另一个值得看的是置信度分布。从results.csv里提取val/box_p观察最终轮次的平均置信度。如果所有预测框的置信度都集中在0.3到0.5之间说明模型对特征不够自信这时候唯一有效的手段是增加数据多样性和训练轮次调置信度阈值只能改变输出框数量不能提升特征质量。5.3 哪些原因会把准确率压在70%附近根据我折腾yolo系列数据集的经验mAP0.5死活卡在70%左右通常不是模型能力问题而是三个环节出了状况。第一个是标签噪声。人工标注时主观性很强同一个目标一个标注员认为框要含住手臂另一个只框躯干成年人瘦小、小孩壮实这些主观差异相当于在训练集中注入了不一致的监督信号。核对方法是用清洗后的标签重训一次如果mAP上涨超过3个百分点说明标签噪声确实是主要瓶颈。第二个是类别不平衡。如果成人目标有4000个小孩目标只有400个模型会为了优化整体loss而偏向成人。此时不应该简单复制小孩样本会导致过拟合而应该用采样策略或损失函数调整。Ultralytics提供了class_weights但在yolov9里更直接的做法是在数据增强上偏向小孩比如使用mosaic和copy_paste增强小目标数据量的效果或者从大图中多裁剪含有小孩的子图加入训练。第三个是验证集划分不当。如果验证集和训练集来自同一个场景序列比如同一段监控视频抽帧那么相邻帧极其相似验证集精度虚高反之如果验证集包含大量不同场景精度就会被打回原形。对于1738张图正确的划分方式是按场景分组先按采集时间或拍摄地点分组再把整个组划入训练或验证而不是随机从所有图片里抽样。6. 把70.9%往上推的3个可落地方向6.1 数据增强比换模型更立竿见影70.9%如果想升到80%以上先别急着换yolov9的变体试试调整Ultralytics自带的增强参数。在训练命令里追加hsv_h0.015 hsv_s0.7 hsv_v0.4 degrees10 translate0.1 scale0.5 fliplr0.5这些参数分别控制色调、饱和度、亮度、旋转、平移、缩放和水平翻转的振幅。对于成人小孩检测scale和translate是提升明显的两个参数——缩放模拟了不同距离下的人体大小变化平移模拟了人体偏离画面中心的情况。设置后且训练轮次相应增加。6.2 调整锚点和类别权重如果模型预测的框偏大或偏小可以考虑手动调整锚点。yolov9在训练时会根据验证集自动计算锚点但在目标尺寸分布极不均匀时自动计算的锚点可能集中在大多数目标的尺寸上。你可以通过yolo detect val的plotsTrue和save_jsonTrue输出sensitivities.json查看每个尺寸区间内的目标数量然后针对性地在data.yaml里自定义anchors。不过新手常用且更稳妥的方式是直接用yolo detect train加上auto_anchorFalse再手动指定锚点表这需要先跑一次k-means聚类统计目标框宽高比。另一个有效操作是监督类别不平衡。如果你统计出小孩目标只有成人目标的1/5可以在损失函数上给小孩类别更高的权重。Ultralytics的loss_gs和loss_oi没有暴露直接的class_weight但你可以通过cls参数增加分类损失的权重比如cls0.8同时配合数据层级的小目标增强。6.3 标签清洗后重训一轮很多人在70.9%的瓶颈上反复调参其实忘了回头重看标注。我最后建议的动作是把训练结果中预测置信度低于0.4的所有框打印出来和对应的真实框画在一起列出IoU低于0.5的样本。这些样本往往是原始标注框手抖画大、画偏的地方。人工重新修正100到200个框再在同样参数下重训20轮大概率能看到mAP提升。把重训后的best.pt在验证集上重新评估输出置信度阈值调优曲线找到F1分数最高的置信度阈值。在部署阶段用yolo predict带上conf0.5 iou0.45参数再跑一次生成的预测图和验证图进行比对确认70.9%已经变成过去式新的数字才是你在这个数据集上的实绩。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。