ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

熊猫数据集双格式解析:VOC与YOLO坐标转换及训练校验全流程

熊猫数据集双格式解析:VOC与YOLO坐标转换及训练校验全流程 简介这是一份以熊猫为单一类别的目标检测数据集面向计算机视觉初学者与算法工程师可直接用于训练或验证Pascal VOC、YOLO两类常见检测模型。全部图像均已通过labelImg完成矩形框标注类别标注为Panda共114个真实框标注信息与图片一一对应。资源包共332个文件其中jpg图片110张、VOC格式xml标注110个、YOLO格式txt标注112个整体大小36.34MB压缩后便于下载和快速解压使用。目前已有189人学习下载。数据集不附带权重或训练结果但标注准确、格式完整适合用来练习数据清洗、格式转换、模型迭代等完整流程同时也可作为小样本迁移学习的测试集帮助对比不同网络结构在单一类别上的表现。若需扩展类别或增加样本可参照其标注格式自行补充。1. 熊猫数据集双格式解析110 张图把 VOC 和 YOLO 的流程彻底走通这份熊猫数据集数量上非常克制110 张 JPEG 原图每张图都同时配好了 Pascal VOC 格式的 XML 标注和 YOLO 格式的 TXT 标注类别只有一个 Panda所有标注框加起来正好 114 个。它不像 COCO 那种大而全的基准更像一个特意为「跑通流程」准备的干净样本集。适合两类人一类是刚接触目标检测、想用 YOLOv5 或 YOLOv8 训练自己数据集的新手另一类是想把 VOC 到 YOLO 的坐标换算彻底吃透、以后不用再被标注格式坑的开发者。我建议你别急着把数据丢进训练脚本先花半小时把这个数据集的文件结构和坐标关系理顺后面能少踩一半的坑。2. 文件结构与格式对应110 张 JPG、110 个 XML、110 个 TXT 是怎么对齐的2.1 解压后的文件清单与数量核对拿到 zip 包之后第一件事不是双击看图片而是把文件数量统计一遍。这个数据集打包文件名里带了中文和加号在 Windows 图形界面解压一般没问题但在 Linux 服务器上我习惯用命令行操作避免某些解压工具因为文件名编码问题丢文件。unzip 动物数据集65熊猫数据集VOC格式yolo格式110张1类别.zip -d panda_dataset cd panda_dataset find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -lunzip的-d参数指定解压目标目录后面的find分别统计三类文件的个数。这份数据集的简介里写得很清楚jpg 文件 110 个、xml 文件 110 个、txt 文件 110 个所以三个命令的输出都应该等于 110。如果你发现某个数量对不上先别急着训练大概率是解压中断或者下载不完整。文件命名从项目文件列表里能看出来是firc_Panda_40.jpg、firc_Panda_26.jpg这种「前缀_类别_编号」的结构。正常用 labelImg 标注并导出时XML 和 TXT 会与 JPG 保持同名只差后缀。但我不建议靠肉眼去逐对核对直接用一个 Python 脚本检查更稳妥。import glob, os imgs glob.glob(**/*.jpg, recursiveTrue) xmls glob.glob(**/*.xml, recursiveTrue) txts glob.glob(**/*.txt, recursiveTrue) img_names {os.path.basename(p).replace(.jpg, ) for p in imgs} xml_names {os.path.basename(p).replace(.xml, ) for p in xmls} txt_names {os.path.basename(p).replace(.txt, ) for p in txts} print(只有图片没有标注:, img_names - xml_names - txt_names) print(只有XML没有图片:, xml_names - img_names - txt_names) print(只有TXT没有图片:, txt_names - img_names - img_names)这段脚本先把三类文件的完整路径都找出来再去掉扩展名得到主文件名最后用集合的差集找出“对不上”的那些。输出为空说明配对正常。这里要特别注意一个细节数据集的说明里写了“不包含分割路径的 txt 文件”也就是说这 110 个 txt 都应该是真正的标签文件如果你统计 txt 数量时发现多了一个很可能工作目录里还有个classes.txt这类文件是 labelImg 自动生成的类别清单不是标注不用管它。2.2 同一张图的两种标注视图XML 与 TXT 的字段对照解压完成后随便挑一张图比如firc_Panda_3.jpg把同名的 XML 和 TXT 分别打开看一眼。这是理解双格式最直接的办法。cat firc_Panda_3.xml cat firc_Panda_3.txtXML 是典型的 Pascal VOC 结构里面会有filename、size、object这样几组关键信息而 TXT 在 YOLO 格式下每一行只有五个数字用空格分隔第一列是类别 id后四列分别是归一化后的目标中心点 x、中心点 y、宽度 w、高度 h。两者的关系可以简单理解成同一份标注用两种坐标系写了两遍。XML 字段TXT 中的对应说明filenamefirc_Panda_3.jpg/filename文件名对应图片名sizewidth分母图片像素宽度sizeheight分母图片像素高度objectnamePanda/name第一列 0类别名到类别 id 的映射bndbox里的 xmin、ymin、xmax、ymax后四列像素坐标换算成归一化坐标为什么要同时看两个文件因为只看 XML你很难直观理解 YOLO 为什么要把坐标归一化只看 TXT你又不知道 0.5 这样的数字到底对应图上哪个位置。两边对照着看几张图换算逻辑基本就清楚了。2.3 为什么只有 110 张图也值得把双格式保留见过不少数据集只给 VOC 格式或只给 YOLO 格式。只给 VOC 的你想训练 YOLO 必须先写转换脚本而转换脚本本身又是最容易出错的环节只给 YOLO 的你想用 labelImg 复查标注、或者想用可视化工具看框又得从归一化坐标反推像素坐标很麻烦。这份数据集两边都给最大的价值是你可以随时做交叉验证XML 和 TXT 相互对拍任何一边出了问题都能马上发现。对新手来说这 110 张图就是一个天然的对照教材。XML 里是百分百的像素坐标TXT 里是归一化坐标每一对文件都演示了一次坐标换算。把其中十张图手动算一遍你对 YOLO 标签格式的记忆会比背十遍文档都牢。3. 坐标换算与格式拆解VOC 的 bndbox 怎么变成 YOLO 标签里的一行五个数字3.1 XML 里真正有用的字段filename、size、objectVOC 格式的 XML 里并不是每个字段都有用。对这个数据集来说folder、path、segmented基本可以忽略真正决定标注内容的是三组信息filename告诉你这张图叫什么size告诉你图片原始宽高object可能有一个或多个每个 object 内部的name是类别名bndbox是矩形框。下面是读取并打印这些字段的脚本。import xml.etree.ElementTree as ET tree ET.parse(firc_Panda_3.xml) root tree.getroot() print(filename:, root.find(filename).text) size root.find(size) print(image size:, size.find(width).text, x, size.find(height).text) for obj in root.findall(object): name obj.find(name).text 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) print(name, xmin, ymin, xmax, ymax)这个脚本用ElementTree解析 XMLfind按字段名定位节点。需要特别提醒的是bndbox里的 xmin、ymin、xmax、ymax 都是像素绝对值取值范围是 0 到图片宽高之间而不是 0 到 1。很多人第一次写转换脚本时在这里栽跟头以为 YOLO 的归一化是直接在 XML 数值上除以 255 之类完全不是一回事。另外这个数据集的简介里写了“总框数 114”而图片是 110 张说明至少有 4 张图里有两个或以上的熊猫框。一个 XML 里出现多个object是正常的遍历的时候用findall(object)而不是find(object)否则只会取到第一个框标签数量就会对不上。3.2 从像素坐标到归一化坐标公式与代码实现把 VOC 转成 YOLO 格式核心公式就四个全部依赖size里的图片真实宽高x_center (xmin xmax) / 2 / image_widthy_center (ymin ymax) / 2 / image_heightbox_width (xmax - xmin) / image_widthbox_height (ymax - ymin) / image_height注意image_width和image_height是整张原始图片的尺寸不是标注框的尺寸也不是你打算 resized 到 640x640 之后的尺寸。YOLO 标签文件里存的永远是相对原始图片的归一化坐标训练时数据加载管线会自己处理缩放。import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, image_width, image_height, class_map): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: continue class_id class_map[name] 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) x_center (xmin xmax) / 2.0 / image_width y_center (ymin ymax) / 2.0 / image_height w_norm (xmax - xmin) / image_width h_norm (ymax - ymin) / image_height lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) return lines class_map {Panda: 0}这个函数的参数里class_map是类别名到 id 的映射目前只有Panda: 0。输出采用 6 位小数对 110 张图这个量级完全够用不会因为精度丢失导致框偏移。我在实际项目中通常会把image_width和image_height直接写成从 XML 的size里读取而不是作为外部参数传入这样可以少暴露一个出错的口子。3.3 类别映射与扩类准备为什么只有 0 也要维护好 classes 列表单类别是最容易麻痹大意的场景。这个数据集所有 txt 的第一列都是 0因为你只有 Panda 一个类。但即使如此我强烈建议你从一开始就把类别映射文件维护好。YOLOv5 和 YOLOv8 的 data yaml 里会写names列表训练时类别 id 就是列表下标。如果写成names: [Panda]那么 id 0 对应 Panda没问题但有一天你想加一个 Bear并且把它放在列表第一位就变成 id 0 对应 Bear原有标注全部错位。常见的翻车操作是在 labelImg 里给某几张新图标了类但保存后只更新了 XML没有重新生成 TXT导致同一个框在两种格式下类别不一致。这类错位训练时不会报错只会表现为验证集指标莫名其妙变差。所以我养成的习惯是不管数据集有没有自带 txt都要用 3.2 的脚本基于 XML 重新生成一遍 txt再和原始 txt 做 diff。这样改任何标签都不会留下两头不同步的隐患。4. 训练前校验三板斧核对 114 个框、排除越界标注、划好 train/val4.1 用 XML 统计每个类别的框数先和 114 对一对拿到数据集不做任何校验直接开训等于把一个黑匣子交给训练脚本。我一般会先写一个十几行的统计脚本把每个类别的框数算出来。import glob import xml.etree.ElementTree as ET xmls glob.glob(**/*.xml, recursiveTrue) counter {} for xml_path in xmls: tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() counter[name] counter.get(name, 0) 1 print(counter)预期输出是{Panda: 114}。如果输出不是这个数就需要排查少框了可能是 XML 解析时漏了某些object多框了可能是 labelImg 打标过程产生了重复框。这一步的价值在于把“标注文件本身的质量”和“训练效果”解耦。如果框数都不对后面训练出来的 mAP 再好看也没有意义。4.2 越界检测坐标超出图片范围或宽高为负数YOLO 训练过程中出现 loss 为 nan或者验证集完全不检框很多时候不是模型问题而是标注数据里有越界 box。最常见的情况是标注框的 xmax 大于图片宽度或宽高等于 0。归一化之后这些值会变成大于 1 或者是负数模型在计算损失时就会算出异常值。from PIL import Image import glob import xml.etree.ElementTree as ET def validate_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text img Image.open(filename) img_w, img_h img.size for obj in root.findall(object): 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) if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(f坐标越界: {xml_path} - {xmin}, {ymin}, {xmax}, {ymax}) if xmax xmin or ymax ymin: print(f退化框: {xml_path} - 宽或高小于等于 0) for xml_path in glob.glob(**/*.xml, recursiveTrue): validate_xml(xml_path)这个脚本里我用 PIL 打开对应图片拿到真实宽高再逐个框检查。xmax 大于图片宽度或者 xmin 小于 0 都属于越界xmax 小于等于 xmin 属于退化框。退化框在校验后应该直接删掉而不是保留在数据里。特别注意PIL 的Image.open需要 XML 同级目录下有对应的 jpg 文件如果脚本报找不到图片说明文件名配对本身就出了问题这也是一个间接检查。4.3 划分训练集与验证集并搭出 YOLO 需要的目录结构110 张图不算多但完全不划分验证集也不行。我一般按 9:1 划分也就是 99 张训练、11 张验证。这个比例对百张量级的数据集比较合理验证集太小的话指标波动会非常剧烈每个 epoch 之间的 mAP 可能相差 0.2。import glob import os import random import shutil imgs glob.glob(images/*.jpg) random.seed(42) random.shuffle(imgs) split int(len(imgs) * 0.9) train_imgs imgs[:split] val_imgs imgs[split:] for img in train_imgs: base os.path.basename(img).replace(.jpg, ) shutil.copy(img, fdataset/images/train/{base}.jpg) shutil.copy(flabels/{base}.txt, fdataset/labels/train/{base}.txt) for img in val_imgs: base os.path.basename(img).replace(.jpg, ) shutil.copy(img, fdataset/images/val/{base}.jpg) shutil.copy(flabels/{base}.txt, fdataset/labels/val/{base}.txt) print(train:, len(train_imgs), val:, len(val_imgs))这段脚本对图片和标签用的是shutil.copy而不是move原目录的数据被完整保留。这样做的原因是划分比例和随机种子是可以反复调整的如果把原始数据移动了之后想重新划分就要重新解压。训练时使用的 data yaml 通常长这样train: dataset/images/train val: dataset/images/val nc: 1 names: [Panda]这里train和val指向图片目录YOLO 训练时会自动在同级的labels目录下寻找同名 txt。如果你自己把 txt 放在了别的路径需要在 yaml 或数据集类里显式指定否则会一直提示找不到标签。5. 常见问题与避坑小样本训练最容易翻车的五个场景5.1 文件缺失、配对错位与数量对不上现象用find统计后jpg 只有 108 个xml 却有 110 个。或者 jpg、xml、txt 数量都是 110但打开某张图发现框标注到了另一只熊猫身上。原因前一种情况多半是 zip 下载不完整或解压过程跳过了个别文件后一种情况通常是自己写的配对脚本有问题比如按列表索引一一对应而列表排序时把firc_Panda_10.jpg排到了firc_Panda_9.jpg前面导致后缀替换后错位。解决下载后先用 2.1 的脚本统计三类文件数量再按文件名而不是列表顺序建立映射。文件名相同的才是一对任何靠“第几个文件”配对的做法都应该抛弃。5.2 坐标归一化除以了 resize 之后的尺寸现象训练时 loss 前几个 epoch 下降正常但验证集的 mAP 一直是 0像是模型完全没学到东西。原因你写 VOC 转 YOLO 脚本时把图片先 resize 到 640x640 再读取宽高用 640 做了归一化分母。但数据加载器实际读取的是原始图片导致标签和图像内容错位。解决归一化永远基于 XMLsize里的原始宽高。YOLO 训练时对图片的缩放发生在数据加载阶段标签会同步变换不需要人工把标签改成 640 输入尺寸的坐标。写转换函数时把size直接冷冻在 XML 解析结果里不要允许外部传入自定义宽高。5.3 labelImg 打标遗留的退化框与小目标框现象一张图里某个熊猫只占十几个像素标注框宽度只有 3 像素。训练若干轮后 loss 变成 nan或者该目标永远被漏检。原因过小或退化的框在模型下采样过程中特征图上的响应可能小于一个像素损失计算不稳定。labelImg 手动打标时如果鼠标拖拽幅度太小很容易生成宽高几乎为 0 的框。解决在训练前用越界检测脚本跑一遍过滤掉宽或高小于 2 像素的框。这个阈值可以按你的输入分辨率调整如果训练分辨率是 640低于 2 像素的框基本没有学习意义删掉反而更稳定。5.4 对 110 张图的训练效果产生不切实际的预期现象训练 300 轮mAP0.5 停在 0.5 上下怎么看都觉得是自己代码写错了。原因这个数据集只有 110 张图、114 个框类别也只有一种能覆盖的熊猫姿态、背景环境、光照条件非常有限。模型在小数据集上很容易过拟合训练集验证时换一批照片效果马上掉下来。数据集描述里特意写了“不对模型精度作任何保证”就是在提醒你它的定位是标注准确的流程验证集不是刷分数据集。解决把它当 pipeline 验证用跑通训练、验证、导出、推理全流程就够了。想提升实际效果应该继续扩充数据或者用预训练权重做迁移学习把学习率调低配合马赛克增强等策略而不是责怪数据集。5.5 zip 解压路径混乱与中文目录名带来的问题现象解压出来所有图片散落在多层嵌套目录里YOLO 的 glob 匹配不到或者在 Windows 上解压后路径带中文和空格在 Linux 训练时路径解析失败。原因zip 包内可能有嵌套顶层目录不同解压器对中文文件名的处理方式也不一致。直接写死相对路径的代码换一个环境就失效。解决解压后先find . -type f | head看清楚实际目录层级再决定 glob 的匹配模式。所有 yaml 和脚本里统一用正斜杠相对路径不要用反斜杠避免在 Linux 服务器上翻车。如果项目目录本身带空格训练数据路径建议放在没有空格的纯英文目录下。6. 让这份双格式数据集变成自己的标注模板画框验证与滚动更新最后一个技巧也是我每次拿到新数据集必做的一步把 XML 里的框画回原图上用眼睛确认标注质量。这一步能发现所有统计脚本发现不了的问题比如框偏移了半个身体、框住了背景树枝、框的大小明显不符合熊猫体型。import cv2 import xml.etree.ElementTree as ET img_path firc_Panda_3.jpg xml_path firc_Panda_3.xml img cv2.imread(img_path) tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): bbox obj.find(bndbox) xmin int(float(bbox.find(xmin).text)) ymin int(float(bbox.find(ymin).text)) xmax int(float(bbox.find(xmax).text)) ymax int(float(bbox.find(ymax).text)) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, Panda, (xmin, max(0, ymin - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imwrite(check.jpg, img)这个脚本把 XML 坐标转换成整数后用 OpenCV 在图上画出绿色矩形框并标上类别名。输出check.jpg后人工抽查十张图基本就能确认数据集能不能进入训练流程。你也可以顺手把 3.2 的xml_to_yolo函数集成进来让每个被保存的 XML 都同步重新生成一次 TXT这样双格式永远保持一致。我自己做目标检测项目已经有几年了最大的教训就是格式化转换和人工校验这两步省不掉。这些数据集大多来自手动标注不同标注工具、不同标注员的习惯都会造成细微差异而训练脚本不会告诉你异常在哪个文件里。从那以后我每次拿到任何来源的数据集都强制走一遍“数量核对 → 越界检测 → 画框抽查”这套流程然后才允许自己打开训练命令。这套流程跑下来通常不超过十分钟但能避免你为一个错误标注浪费一整天训练时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表