ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

带标签人像数据集落地YOLO:从格式清洗到训练验证

带标签人像数据集落地YOLO:从格式清洗到训练验证 简介面向YOLO系列算法目标检测任务的人数据集整合了标签文件与划分好的训练/验证/测试结构并附带data.yaml配置文件可直接用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等模型的训练与验证。资源共2000个文件以XML标注文件为主对应VOC格式同时提供YOLO格式的TXT标签两种标签均记录了目标类别索引及归一化后的中心坐标与宽高方便切换不同框架或对照校验压缩包总大小185.64MB目录内训练集、验证集与配置文件分层存放便于快速投入实验。目前已有203人学习适合正在做人形/行人检测、模型调参与数据集准备的中级开发者。通过该资源可免去自行采集与标注的繁琐流程拿到划分好的数据集与双格式标签可重点体验类别定义、数据路径配置到启动训练的完整链路。1. 2859 张带标签的人像图集它省的不是标注时间而是踩坑时间收到一个名为yolo算法-人数据集-2859张图像带标签.zip的压缩包第一反应别是“解压完直接扔进训练脚本”而是先想清楚这份数据到底能帮你解决什么。2859 张带标签的人像图集覆盖了 YOLO 算法调参前期最烧时间的两个环节找图和打标。它适合准备做行人检测、安防巡检或毕设落地的开发者尤其是那些只有碎片时间、不想从零标数据的团队。但它不等于零成本训练标签格式可能是 YOLO txt也可能是 VOC XML 或 COCO JSON甚至会出现文件名对不上、坐标越界、类别名不统一等问题。真正决定项目进度的往往不是模型选哪个而是你用什么流程把这份压缩包变成能直接喂给yolo train的数据。2. 解压前先看 zip 清单解压后先认标签格式用命令把家底摸清数据集的文件名已经写明了三个关键信息YOLO、人、2859 张带标签。但文件名不负责告诉你内部目录是images/和labels/两进制还是JPEGImages/和Annotations/的 VOC 风格。这两者的后续处理流程差别很大所以拿到 zip 的第一件事是“看”而不是“解”。2.1 解压前先用 unzip -l 看清单顺手验证 zip 完整性我一般会先列压缩包内容确认目录层级和文件数量是否符合预期。这一步能让你提前发现两类问题一是 zip 里意外混入了回收站文件二是标注文件的扩展名跟图像对不上。cd ~/downloads unzip -l yolo算法-人数据集-2859张图像带标签.zip | head -60命令里的-l表示 list只列出压缩包内文件清单不解压。输出会显示每个文件的原始大小、压缩后大小和路径。我习惯先看前 60 行主要是确认根目录下是直接散着图片还是包着一层文件夹。如果看到 2859 个.jpg和另一个目录里的 2859 个.txt基本就是 YOLO 风格的标准目录。接着做完整性校验unzip -t yolo算法-人数据集-2859张图像带标签.zip /dev/null-t会逐个文件做 CRC 校验如果返回No errors detected说明压缩包没有被截断。很多训练翻车其实是从 zip 解压不完整开始的比如某几张图解压出来只有一半字节OpenCV 能读到文件但解码失败训练时突然崩掉。这一步成本很低别跳过。确认无误后再正式解压。这里有个中文字典路径的坑很多公开数据集压缩包是在 Windows 下打的用系统默认编码命名了中文目录Linux 的unzip默认按 UTF-8 解可能解出来是一堆乱码路径。常见做法是指定 GBK 解码unzip -q -O gbk yolo算法-人数据集-2859张图像带标签.zip -d person_dataset-q是 quiet不输出每一个文件-O gbk告诉 unzip 用 GBK 解释文件名-d person_dataset指定输出目录。如果你在 Windows 环境直接右键解压也可以但后面尽量把路径保持为纯英文避免训练脚本在中文路径上踩坑。2.2 解压后先统计文件后缀再读一个标签样本解压完成后第一件事不是打开图片肉眼欣赏而是用脚本统计文件后缀和文件数量。这一步能快速判断这份“带标签”到底带的是什么标签。from pathlib import Path from collections import Counter data_dir Path(person_dataset) suffix_stats Counter() for f in data_dir.rglob(*): if f.is_file(): suffix_stats[f.suffix.lower()] 1 print(suffix_stats)正常情况你会看到两类结果。第一种是Counter({.jpg: 2859, .txt: 2859})这是最省心的 YOLO 原生格式。第二种是Counter({.jpg: 2859, .xml: 2859})说明标签是 VOC 格式需要转换。还有一种可能.jpg有 2859 个但.txt只有 2800 个那就要回到上一层目录把缺失部分排查清楚。统计完后缀再随机读一个标签文件看里面的结构import random from pathlib import Path label_files sorted(data_dir.rglob(*.txt)) sample random.choice(label_files) print(sample) print(sample.read_text(encodingutf-8, errorsignore).splitlines()[:5])如果看到类似0 0.492 0.438 0.262 0.621的行第一列是类别 id后面四列是归一化中心坐标和宽高这就是标准的 YOLO 格式。如果看到annotation和bndbox标签那是 VOC XML。如果看到一个巨大的 JSON 数组里面有images、annotations字段则是 COCO 格式。三种格式对应三种不同的处理路径别急着统一先搞清楚。2.3 图像与标签文件名比对缺失或多余都是脏数据即使后缀数量对得上也不代表每个文件名都成对出现。比如图像是frame_0001.jpg标签却叫frame_0001.txt但某几张图恰好没有 txt或者 txt 里写的类别是背景都会直接影响训练。所以配对检查这步不能省。import re from pathlib import Path def normalize_stem(p: Path) - str: return re.sub(r\.(jpe?g|png|bmp|webp|xml|txt|json)$, , p.name.lower()) img_stems {normalize_stem(p) for p in data_dir.rglob(*) if p.suffix.lower() in {.jpg, .jpeg, .png, .bmp, .webp}} label_stems {normalize_stem(p) for p in data_dir.rglob(*) if p.suffix.lower() in {.txt, .xml, .json}} print(缺标签的图像数量:, len(img_stems - label_stems)) print(无图像的标签数量:, len(label_stems - img_stems)) for s in list(img_stems - label_stems)[:10]: print(missing, s) for s in list(label_stems - img_stems)[:10]: print(orphan, s)normalize_stem的作用是把文件名的后缀剥离出来统一小写再用集合求差集。差集里的文件名要么是只有图没有标签要么是只有标签没有图。前者会让训练出现背景图后者会让标签变成孤儿文件YOLO 训练时直接报“label not in same basename”。如果这两个数字都不为 0先把数据补齐再说而不是急着改模型。3. 把标签统一成 YOLO 的归一化 txt一个转换脚本和四个边界坑如果你手头的压缩包是 VOC XML 或 COCO JSON这一章就是把所有标签统一成 YOLO 格式的具体操作。如果你已经是 YOLO txt也可以把后面的边界检查当一份质量审核清单因为“格式正确”和“坐标合理”是两回事。3.1 YOLO 标签的归一化规则先理解公式再动手写脚本YOLO 的标签文件每行代表一个目标框格式是class_id x_center y_center width height。后四个值官方文档明确说是相对图像宽高的归一化值取值范围应该是 0 到 1 之间。也就是说把一个 VOC 标注框里的像素坐标转成 YOLO 归一化需要做两步先算出框中心的绝对像素坐标再除以图像宽高。转换公式不复杂x_center (xmin xmax) / 2.0 / image_width y_center (ymin ymax) / 2.0 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height这里的xmin、ymin是框左上角像素坐标xmax、ymax是右下角像素坐标。很多人会把x_center写成xmin width/2这也没问题但要注意如果width没归一化写出来就是错的。我习惯先计算绝对像素再统一除以图像宽高这样后面检查边界时更直观。COCO JSON 的转换稍微不同因为 COCO 存的是bbox: [x, y, width, height]左上角加宽高而不是两个对角点。但归一化公式一样x_center x width/2再除以image_width。3.2 一份可复用的转换脚本XML 到 YOLO txt以一个典型的 VOC 标注目录为例JPEGImages放图像Annotations放 XMLImageSets/Main放划分文件。但这份压缩包不一定有ImageSets所以我在转换脚本里直接遍历全部 XML 文件只为“person”这一类生成标签。import xml.etree.ElementTree as ET from pathlib import Path class_map {person: 0} # 只保留 person其他类别可继续追加 annot_dir Path(Annotations) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_path in sorted(annot_dir.glob(*.xml)): tree ET.parse(xml_path) root tree.getroot() img_w float(root.find(size/width).text) img_h float(root.find(size/height).text) if img_w 0 or img_h 0: print(fskip invalid size: {xml_path.stem}) continue lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界修正 xmin, xmax min(xmin, xmax), max(xmin, xmax) ymin, ymax min(ymin, ymax), max(ymin, ymax) if xmax xmin or ymax ymin: print(fskip zero-area bbox: {xml_path.stem} {name}) continue x_c (xmin xmax) / 2.0 / img_w y_c (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) if lines: out_path out_dir / f{xml_path.stem}.txt out_path.write_text(\n.join(lines) \n, encodingutf-8) else: print(fno person box in: {xml_path.stem})这个脚本的逻辑并不复杂遍历 XML读取每张图的宽高逐个 object 解析bndbox再按归一化公式生成一行。脚本里的class_map是一个字典用来统一不同标注员的命名习惯比如“person”“Person”“pedestrian”可以都映射到类别 0。类别 id 从 0 开始这会直接影响后面data.yaml里的nc和names千万和标注文件的标签索引保持一致。3.3 转换脚本里的四个边界坑空标签、零面积、越界、别名第三个边界问题是坐标越界。有些标注工具会把xmax标到图像宽度之外比如原图宽度是 1920某个框的xmax是 1932。如果不处理转换出来的width会大于 1甚至中心点也会越过边界。更稳妥的做法是在转换后做 clamp但 clamp 的逻辑要写对# 坐标不完全 clamp而是先剪裁 bbox再算中心 xmin max(0, min(img_w - 1, xmin)) xmax max(0, min(img_w - 1, xmax)) ymin max(0, min(img_h - 1, ymin)) ymax max(0, min(img_h - 1, ymax))先把坐标裁剪到图像范围内再计算归一化值这样就不会出现中心点超界。如果你直接对归一化后的x_c做min(x_c, 1.0)虽然值不超界但框的实际位置已经被扭曲了检测头会对不准。第四个坑是类别别名。数据来自不同标注员时同一个“人”可能有三种名字person、people、Pedestrian。你需要在class_map里把所有别名都映射到同一个 id。漏掉一个别名它的标签行就会被跳过该图像缺少这个人的标注训练时模型会把它当作背景实际上是负样本污染。4. 按 8:1:1 切分数据并写好 data.yaml训练命令之前的最后一步2859 张图对 YOLO 来说属于中小型数据集如果划分不严谨训练集和验证集里出现同一场景的相邻帧模型的 mAP 会虚高上线后立刻打回原形。我一般不会直接开训而是先花十分钟把划分脚本和配置文件写清楚。4.1 三种划分方式随机、分组、按镜头切分第一种是纯随机划分适合爬虫抓取的公开图片因为每张图之间没有强关联。代码上就是用random.shuffle打乱后按比例切分。第二种是分组划分适合连续视频帧抽取的数据集你需要先按视频名或采集时间分组再把整组放进同一个集合避免同一镜头同时出现在 train 和 val。第三种是按目录划分适合原本就按类别或者场景组织好的数据直接把目录指定给 train、val、test 即可。对多数人数据集来说纯随机加固定随机种子就够了。但要注意用random.seed(42)固定结果否则每次运行划分结果都不同代码一旦重跑train 和 val 就乱套了。import random from pathlib import Path import shutil random.seed(42) split_ratios (0.8, 0.1, 0.1) image_dir Path(images) label_dir Path(labels) images sorted(image_dir.glob(*.jpg)) random.shuffle(images) n len(images) val_end int(n * split_ratios[0]) test_end int(n * (split_ratios[0] split_ratios[1])) splits { train: images[:val_end], val: images[val_end:test_end], test: images[test_end:], } for split_name, files in splits.items(): img_out Path(fsplit/{split_name}/images) lbl_out Path(fsplit/{split_name}/labels) img_out.mkdir(parentsTrue, exist_okTrue) lbl_out.mkdir(parentsTrue, exist_okTrue) for img_path in files: shutil.copy(img_path, img_out / img_path.name) lbl_path label_dir / f{img_path.stem}.txt if lbl_path.exists(): shutil.copy(lbl_path, lbl_out / lbl_path.name)我用shutil.copy而非move是因为原始数据和解压目录都还留着万一某个 split 数量不对还能重来一次。移动文件会破坏源目录重跑脚本时就会缺文件。复制多占一点硬盘但换来的是后悔药。跑完后用三条命令快速核对find split/train/images -type f | wc -l find split/val/images -type f | wc -l find split/test/images -type f | wc -l正常情况下三个数之和等于 2859。如果相差很大那就是上一步的图像与标签配对没做干净先检查再继续。4.2 写一个不出错的 data.yaml路径用绝对路径还是相对路径YOLO 的配置文件本质上是一个纯文本 YAML里面决定训练时去哪读数据。最常见的错误是train和val写了相对路径但当前工作目录跟配置里的相对路径不一致。我的建议是直接用绝对路径开始等目录结构彻底固定后再考虑相对路径。# data.yaml path: /data/person_dataset/split train: train/images val: val/images test: test/images nc: 1 names: 0: person重点解释几个字段path是数据集根目录的绝对路径train、val、test是相对于path的目录nc是类别数names是一个从 0 开始的 list。注意 YOLO 的names顺序必须和标签文件里的 class id 一一对应如果nc: 1却写names: [0, person]训练时数字类别会被当成两个类别报错信息还容易被忽略。如果你不确定这个 YAML 写没写对用 YOLO 自带命令先验证一次python -c from ultralytics.data import YOLODataset; ds YOLODataset(datadata.yaml); print(len(ds))这个命令能直接加载训练集并打印样本数。如果 yaml 路径或者标签有问题它会给出具体到某个文件名的报错比盲目开训更容易定位。4.3 训练参数怎么设batch、imgsz、epochs 针对 2859 张图的取值2859 张图不算大模型选yolov8s.pt或yolov5s.pt比较合适。yolov8x在这个数据量下容易过拟合而且训练时间成倍增长。我一般会用预训练权重而不是从零开始yolo detect train modelyolov8s.pt datadata.yaml imgsz640 batch16 epochs100 device0 pretrainedTruebatch 这个参数在 YOLO 里默认是 16如果你显卡显存只有 8GBimgsz640加batch16很可能直接 OOM。可以改成batch-1让程序根据显存自动调出最大 batch或者手动降到 8。epochs对 2859 张图100 轮已经能看到明显收敛不用一上来就 300 轮。patience也可以设成 3030 轮内验证集没有提升就提前停止省时间。另一个值得调的是workers。Windows 上workers8常导致 DataLoader 崩溃Linux 一般没问题。如果你训练时发现 CPU 占用极低但有明显卡顿多半是 workers 设置和系统的共享内存不匹配。稳妥起见workers4。5. 训练期常见问题排查zip 伪加密、空标签、显存溢出和分数错位数据集从压缩包到训练产物中间最容易卡人的不是模型代码而是那些看起来很小的数据工程问题。我把自己在多人数据集上遇到过的几个高频问题按“现象、原因、解决”写在这里。5.1 unzip 提示输入密码但文件名又能正常列出伪加密现象在 Linux 下执行unzip -l能看到完整文件清单但unzip解压时提示需要密码输入常见密码后还是失败。原因这是典型的 zip 伪加密也叫 zip 伪加密。文件头的加密 flag 被改动过实际上原始 zip 可能根本没有压缩内容的加密只是让 unzip 以为它加密了从而弹出密码提示。这类 zip 包在公开数据集里不算少见尤其是二手转传出来的资源。解决先不要碰来路不明的密码绕过工具安全第一。正确做法是用zipinfo -v查看压缩条目的加密方式如果是伪加密文件属性里会显示异常或者直接找发布者重新打包一份。如果你只是想确认数据能不能用可以用python3 -c import zipfile; zzipfile.ZipFile(person.zip); print(z.namelist()[:5])看看 Python 的 zipfile 能否直接列出文件名。列出不代表能解出伪加密包在解压时仍可能报错。最稳的处理是要求数据源给一个没有加密标记的新 zip同时对旧包先做一次 CRC 校验避免解压到一半文件全坏。5.2 训练报错 assert标签文件为空现象用 YOLO 训练大概十几个 batch 后程序突然中断报错信息里带有labels ...为空或者assert相关的提示。日志里往往能看到WARNING: empty label file前面已经出现过多次。原因某些公开数据集的标签文件夹里包含没有被成功转换的空 txt 文件。图像确实存在但 txt 文件字节数为 0YOLO 在加载标签和匹配图像时会出现空标签触发了训练脚本里的断言。解决训练前先全局扫描空标签文件把它们从labels目录中清走同时把对应的原图也移出数据目录。命令很简单find split -name *.txt -size 0 -print | wc -l find split -name *.txt -size 0 -delete但只删 txt 不够你还得同步删除对应图像否则 YOLO 虽然能加载图像却没有标签可配依然会报警告。正确做法是写一个循环先收集空 txt 的 stem再删除同 stem 的图片。这是数据清洗不能由模型参数代偿。5.3 图像路径含中文OpenCV 读取返回 None现象模型训练正常但某个 epoch 突然出现AttributeError: NoneType object has no attribute shape定位后是某张 JPEG 读取失败。图片在文件管理器里双击能打开但cv2.imread返回None。原因常见的是路径包含中文字符或特殊符号。OpenCV 的imread在 Windows 下会把路径按系统编码处理如果路径里有中文即使文件存在也可能读不到。另一类是单个图片文件在 zip 解压时损坏CRC 校验没报错但解码失败。解决数据集统一放到纯英文路径下例如/data/person_dataset。如果还是不行用PIL或者imageio做一个读取回退先用cv2.imread读失败后用Image.open再读并转成 numpy 数组再交给 YOLO。这种做法在极端情况下能救回来但我不建议长期依赖正确解法是让所有路径只有 ASCII 字符。5.4 Loss 不降所有预测都是背景类别 id 从 1 开始导致错位现象训练了 50 轮box_loss一直在 1.0 以上验证集的 mAP50 是 0。查看预测结果每一张图都没有检测框。原因这不是模型坏了而是标签里的 category id 和数据字典对不上。比如标注软件导出时把person写成类别 1你却在data.yaml里写了nc: 1且names: [person]。类别 1 已经超出了合法范围YOLO 的检测头在处理时把该标签当成背景或越界忽略最终学不到任何目标。解决用一行脚本统计所有标签文件里的 class id 最大值确保它小于nc。例如awk {print $1} labels/*.txt | sort -n | tail -1输出应该是 0。如果输出是 1就把data.yaml里的nc改成 2或者把标签里的 1 批量改成 0。这个错位很隐蔽loss 曲线看起来像在收敛但干瞪眼发现它根本检测不到人。5.5 显存溢出OOM 中断训练现象训练刚起步终端输出CUDA out of memory程序退出。即使断点续训也还是 OOM。原因batch16、imgsz640、yolov8s三者在 8GB 显存下很容易撑满。尤其是一开始用默认配置输入图像尺寸大再加上 YOLOv8 的 mosaic 增强会把四张图拼成一张大图峰值显存远高于实际看到的 batch 占用量。解决把batch降成8同时用ampTrue混合精度。也可以直接让 YOLO 自动找 batch 大小yolo detect train modelyolov8s.pt datadata.yaml imgsz640 batch-1 device0 ampTrue workers4batch-1会自动做一次显存探测找到当前显卡能装下的最大 batch。对 2859 张图的规模batch8依然足够稳定只是收敛速度会比 16 慢一点。另一个可行方案是把imgsz降到 480检测精度在人体这种大目标上不会明显下降但显存占用能省 30% 以上。6. 用混淆矩阵和难例集做上线前验证别只盯着 mAP训练结束后很多人只看一眼mAP50就宣告完事这个习惯会在夜间或拥挤场景里把你坑惨。人体检测最终要回答的是“我在真实环境里会不会漏人”。mAP 是整体指标但漏检往往集中在那一刻光线不好、人只露出半个身子、或者后面有人贴得很近的场景。我习惯做的第一件事是直接跑验证集把混淆矩阵导出来看yolo detect val modelruns/detect/train/weights/best.pt datadata.yaml splittest imgsz640训练结束后runs/detect/train里已经带了confusion_matrix.png如果我想在分开的测试集上重新验证就用上面的命令。混淆矩阵里除了person类还有一个空白背景列如果背景那一列数值过高说明模型把大量背景框当成了目标这时候不能只看 mAP要回到训练数据里查负样本质量。下一步是把预测置信度阈值从默认 0.25 调到 0.5再做一次可视化检测把漏检样本收集成难例集from ultralytics import YOLO from pathlib import Path model YOLO(runs/detect/train/weights/best.pt) hard_examples [] for img_path in sorted(Path(split/test/images).glob(*.jpg)): result model.predict(str(img_path), conf0.5)[0] if result.boxes is None or len(result.boxes) 0: hard_examples.append(img_path) print(置信度 0.5 下的漏检数:, len(hard_examples))这段代码把所有在conf0.5下完全没检出人的测试图挑出来。留下来的就是夜里、遮挡、远距离小目标之类的硬样本。我个人的习惯是把这些图单独放进一个hard_samples/目录每张图手动看一眼判断是标注漏标、图像太暗还是模型真的分不清。如果你要给同事做一个 10 分钟的 YOLO 算法讲解 PPT与其贴一堆 loss 曲线不如把这张混淆矩阵图放进去再配三张难例图比任何文字都直观。验证的目的不是追求 mAP 多涨零点几个点而是确认你在起点设下的数据划分、标签清洗、边界处理这些环节是否真的扛住了真实分布。数据集的 2859 张图只是训练原料值不值得拿来投入最终取决于你自己对这个验证闭环的执行深度。希望帮到你。本文还有配套的精品资源点击获取
返回列表