简介面向猕猴桃目标检测任务这套数据集提供1838张真实图像的标注结果同时采用Pascal VOC和YOLO两种通用格式适合目标检测初学者验证流程也适合农业视觉项目快速构建猕猴桃检测训练集。数据统一标注为mihoutao单一类别矩形框标注总数达19278个由labelImg标注完成标注信息覆盖果实位置与类别可平滑接入YOLOv5、YOLOv8、Faster R-CNN等常见模型的训练、迁移学习与精度对比。资源整体为7z压缩包大小约84MB文件总数2000个核心文件以图像、xml标注文件和txt标签文件为主其中xml面向VOC格式读取txt面向YOLO系列训练使用目录结构便于按图片索引。目前已有725人学习/下载可作为果园巡检、农产品分拣等场景的预研数据数据集保证标注准确合理模型最终精度需结合实际训练策略评估。1. 猕猴桃检测数据集1838 张 VOCYOLO 双格式先验货再训练做果园产量预估或分选线项目时最磨人的往往不是模型结构而是数据。你下载到一个 7z 压缩包里面是 1838 张已经标注好的猕猴桃图片同时给了 VOC 和 YOLO 两种标注格式类别只有 1 个。对 yolo 入门的朋友来说这种小规模、单类别、双格式的数据集是理想的起步材料数据量不大标注齐全能快速把一条训练链路跑通。但我的经验是解压完千万别急着写训练脚本先花十分钟验货把目录结构、标签配对和坐标系检查一遍不然后面所有报错基本都是从这里长出来的。下面直接按解压、校验、转换、训练、验证的顺序把 1838 张猕猴桃数据真正用起来。2. 看懂 VOC 与 YOLO 两类标注目录结构、字段含义和转换原理2.1 VOC 的 XML 里到底存了什么从 JPEGImages 到 Annotations 的一一对应PASCAL VOC 的老玩家都知道VOC 格式不是一个文件而是一整套目录约定。常见布局是这样dataset/ ├── JPEGImages/ │ ├── kiwi_0001.jpg │ └── kiwi_0002.jpg ├── Annotations/ │ ├── kiwi_0001.xml │ └── kiwi_0002.xml └── ImageSets/ └── Main/ ├── train.txt └── val.txt这套布局里 JPEGImages 放原始图片Annotations 放每张图对应的 XML 标注ImageSets/Main 放划分好的训练验证名单。如果解压后看到的是这套结构说明作者是按 pascal voc 惯例整理的如果只有 JPEGImages 和 Annotations 两个目录也没关系很多 YOLO 项目根本不依赖 ImageSets直接用脚本按比例划分。一个典型 XML 标注文件长这样annotation folderkiwifruit/folder filenamekiwi_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namekiwifruit/name bndbox xmin100/xmin ymin80/ymin xmax210/xmax ymax190/ymax /bndbox /object /annotation字段含义filename 是图片名size 里的 width 和 height 是图片的像素宽高object 代表一个目标实例每个 object 带 name类别名和 bndbox外接矩形。xmin、ymin 是矩形左上角坐标xmax、ymax 是右下角坐标这四个值单位是像素不是比例。如果一张图里有两颗猕猴桃annotation 里就会有多个 object 节点这是区分“1 类别”和“单张多个目标”最直观的位置。这组字段是所有转换的源头。多数标注工具在 VOC 模式下导出的 XML 字段基本一致但细节有差异有的软件把 filename 写成不带扩展名有的把 bndbox 写成 x1/y1/x2/y2有的会自动生成 segmented 标签。这些差异本身不致命致命的是拿到手没有抽查默认它就是标准格式然后转换脚本一跑坑全冒出来。这种双格式封装在农业类数据集里很常见因为标注成本高导出方希望一份数据能同时照顾评测和训练两类用户所以看懂两套格式的对应关系比记住几个命令更值钱。2.2 YOLO 的 txt 为什么更适合丢给模型训练归一化坐标与类别索引YOLO 系列的训练原生吃纯文本标注每张图一个 txt文件名和图片完全同名。每一行的格式是固定的五个值class_id cx cy w hclass_id 是类别索引从 0 开始cx 和 cy 是目标中心点的横纵坐标w 和 h 是宽和高。关键点是 cx、cy、w、h 都不是绝对像素值而是归一化到 [0, 1] 区间的相对值用绝对坐标除以图片宽高得到。换算例子假设图宽 1920、高 1080框 xmin100、ymin80、xmax210、ymax190中心点 x (100210) / 2 155归一化后 cx 155 / 1920 ≈ 0.0807cy (80190) / 2 135135 / 1080 0.125宽 210-100 110w 110 / 1920 ≈ 0.0573高 190-80 110h 110 / 1080 ≈ 0.1019。对应的一行 YOLO 标注就是 0 0.0807 0.1250 0.0573 0.1019。这些数字是随手算的看公式即可。如果手上只有 VOC XML 需要转成 YOLO常见做法是用这个 Python 函数# voc_to_yolo.py import xml.etree.ElementTree as ET def voc_xml_to_yolo_line(xml_path, class_names): 把单个 VOC XML 转成多行 YOLO 标注 root ET.parse(xml_path).getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines逻辑说明先读 XML 里的 size 作为归一化分母再遍历每个 objectname 不在 class_names 里的目标直接跳过类别白名单可控。class_names 是类似 [kiwifruit] 的列表name 拼写必须和 classes.txt 完全一致否则 class_names.index(name) 会抛 ValueError。参数说明最容易错的是读取 size 的顺序width 和 height 读反会导致 cx 变成 cy框的位置全部跑到对角线上去。这个猕猴桃数据集同时给 VOC 和 YOLO 两份训练时直接读 YOLO txt 就行VOC 那份的用途是互验把两种格式用脚本对拍一遍能过滤掉相当一部分标注错误。两种格式的差异我用这张表记对比项VOC XMLYOLO txt坐标基准绝对像素值相对图片宽高的比例类别表达字符串 name整数索引 class_id存储形式一个 XML 多个 object一个 txt 每行一个目标训练适用性需转换或额外解析YOLO 系列原生读取典型坑filename 缺扩展名、size 与实际图不符坐标越界、索引错位3. 从 7z 解压到数据校验三条命令与五个训练前必看项3.1 Linux 下解压 7z 的正确姿势x 和 e 不能混用这类数据集压缩包大多用 7z 打包后缀 .7z 说明它走的是 LZMA2 压缩算法压缩率通常比 zip 高但解压工具在 Linux 上不一定自带。Debian/Ubuntu 系先装工具再解压sudo apt install p7zip-full # Debian/Ubuntu 安装 7z 命令行工具 7z t kiwifruit_dataset.7z # 测试压缩包完整性 7z x kiwifruit_dataset.7z -odataset/ # 解压到 dataset 目录并保留内部目录结构命令说明7z t 只做完整性测试不释放文件。我拿到陌生压缩包的习惯是先跑一遍 t网络传输导致压缩包损坏的概率不低解到一半报错再排查更难受。-o 指定输出目录注意 -o 和目录名之间没有空格写成 -o dataset 会报未知选项。7z x 保留压缩包内部目录层级7z e 会把所有文件平铺到一个目录这个数据集内部大概率是按 JPEGImages/Annotations/labels 组织的用 e 解压会把结构毁掉后面脚本全废。另外作者打包时通常会用 -mx9 这类高压缩比参数所以解压速度会比普通 zip 慢一些这是正常现象不用怀疑机器性能。Windows 上图形界面装 7-Zip 右键解压到指定文件夹即可需要在脚本里批量解压时用 py7zr 在 PyCharm 里一条龙处理import py7zr with py7zr.SevenZipFile(kiwifruit_dataset.7z, r) as archive: archive.extractall(dataset)逻辑说明with 语句确保解压完文件句柄正常释放extractall(path) 的 path 是解压目标目录目录不存在会自己建。py7zr 的好处是能和后面的校验脚本合在同一个 Python 工程里跑不用来回切终端。3.2 目录结构与配对校验三行 Python 找出缺失标签解压完第一件事看目录结构。常见做法是 tree 或 find 先过一遍看清楚图片、XML、txt 三类文件到底摆在哪些目录。双格式数据集常见两种摆法一种是把 VOC 系目录和 YOLO 系目录并列放另一种是两个格式的文件揉在同一个平铺目录里。先看清再写脚本比对着猜测的路径一顿操作要省事。配对校验是整个流程里性价比最高的一步用文件名去掉扩展名做键值三方对照# check_dataset.py from pathlib import Path img_dir Path(dataset/images) # 按实际布局调整 xml_dir Path(dataset/annotations/voc) # VOC XML 目录 txt_dir Path(dataset/annotations/yolo) # YOLO txt 目录 imgs {p.stem: p for p in img_dir.glob(*.jpg)} xmls {p.stem: p for p in xml_dir.glob(*.xml)} txts {p.stem: p for p in txt_dir.glob(*.txt)} print(图片数:, len(imgs), XML数:, len(xmls), TXT数:, len(txts)) print(缺XML:, len(set(imgs) - set(xmls))) print(缺TXT:, len(set(imgs) - set(txts)))逻辑说明glob 按模式收集文件p.stem 是文件名去掉扩展名的部分用三个集合做差集可以快速统计“有图没标”和“有标没图”的数量正常应全部为 0。参数说明glob 模式里的扩展名要按实际数据改成 .png 或 .jpeg如果目录结构不同只改前三行路径即可。图片损坏也顺带查一遍压缩包在传输过程中偶尔会截断图片尾部用 PIL 的 verify 全量扫一次from PIL import Image bad [] for stem, path in imgs.items(): try: with Image.open(path) as im: im.verify() except Exception: bad.append(stem) print(损坏图片:, len(bad), bad[:10])说明im.verify() 只校验文件头和数据完整性不解码全部像素速度比 load 快一个量级适合 1838 张这种规模。发现坏图后建议把对应文件名从划分名单里剔除不要直接删文件保留原样方便溯源。3.3 类别文件和数据划分classes.txt 和 train.txt/val.txt 里写了什么配对没问题之后看两个文本文件cat dataset/classes.txt wc -l dataset/train.txt dataset/val.txtclasses.txt 决定类别名和索引的对应关系单类别数据只有一行。把这一行内容和 XML 里的 字段比对两处必须一致。wc -l 统计划分名单里的图片行数1838 张按 8:1:1 分配的话train 大约 1470、val 和 test 各约 184比例相差太远就要警惕是不是划分脚本有 bug。到这里目录层级、文件配对、图片完整性、类别文件、划分名单五个必看项全部过了一遍训练前的验货步骤才算结束。很多人直接跳过这些开始写配置文件然后被 FileNotFoundError 和 All labels are empty 轮番教育根子都在这一关。4. 避坑1 类别数据集训练前最容易翻车的五个细节4.1 类别索引整体错位XML 里是 kiwifruitYOLO 里却成了 1现象训练正常启动但验证阶段分类损失一直下不去画出来的预测框类别名对不上打开 YOLO txt第一列写的是 1而 classes.txt 里只有一个类别。原因VOC 转 YOLO 时把 object 序号直接当 class_id或者 class_names 列表顺序和 classes.txt 不一致索引整体平移。解决先确认 classes.txt 行数一个类别只能一行再全量检查所有 txt 的 class_id。单类别最小检查脚本# check_class_id.py from pathlib import Path for txt in Path(dataset/labels).glob(*.txt): for line in txt.read_text().splitlines(): if not line.strip(): continue cls int(line.split()[0]) if cls ! 0: print(类ID异常:, txt.name, cls)逻辑说明line.split()[0] 取每行第一个空格分隔字段并转 int单类别数据里所有 cls 必须为 0任何非 0 输出都是错位索引。参数说明多类别场景把 if cls ! 0 改成 cls num_classesnum_classes 从 classes.txt 行数读取。4.2 XML 的 filename 不带扩展名配对失败从源头蔓延现象VOC 转 YOLO 的脚本跑完生成的 txt 为空或用 3.2 节的配对脚本一跑缺 XML 的数量等于图片总数。原因标注工具写的是 kiwi_0001 没带 .jpg或者磁盘上叫 kiwi_0001.JPGXML 里写 kiwi_0001.jpg大小写不一致。解决配对不要信 XML 里的 filename以磁盘文件列表为准转换前统一补扩展名# fix_filename.py import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(dataset/annotations/voc) for xml_path in xml_dir.glob(*.xml): root ET.parse(xml_path).getroot() name root.find(filename) if name is not None: text name.text.strip() if not text.lower().endswith((.jpg, .jpeg, .png)): name.text text .jpg ET.ElementTree(root).write(xml_path, encodingutf-8, xml_declarationTrue)说明这段脚本原地改写 XML属于有侵入性的操作跑之前务必保留一份原始未解压副本给操作留后悔药。参数说明xml_declarationTrue 保留 XML 头部声明一般不影响后续解析扩展名可按数据集实际情况改。4.3 图片被缩放后 size 没更新归一化坐标整体漂移现象把标注框画回原图上框的位置明显偏左上或框大小对不上果实轮廓训练到一半 mAP 稳定在低水平但数据集肉眼看不出问题。原因有人压缩数据把 1920×1080 的图缩成 960×540忘了同步 XML 里的 size 字段归一化时分母还是旧宽高cx、cy、w、h 整体偏移。解决加一条尺寸比对随机抽 20 张图验证 XML size 和真实尺寸是否一致不一致就回源修正 XML。校验时用 PIL 读图宽高与 ET 解析出的 size/width、size/height 逐项比较跑完输出不一致的文件名清单。这个动作只花十秒但能避免整个训练白跑。4.4 自带 train.txt 写死路径换目录就全盘失效现象解压后训练读 train.txt 报 FileNotFoundErrorvim 一看里面写的是 /home/author/dataset/images/kiwi_0001.jpg 或 D:\old_path\kiwi_0001.jpg。原因作者在自己机器上生成的划分名单存了绝对路径或旧环境相对路径压缩包换机器后自然失效。解决别用自带划分用固定随机种子重新生成 train/val/test只存相对数据集根的路径。直接给完整脚本# make_splits.py import random from pathlib import Path img_dir Path(dataset/images) # 实际图片目录 root_dir img_dir.parent imgs sorted(img_dir.glob(*.jpg)) random.seed(42) random.shuffle(imgs) n len(imgs) val imgs[: int(n * 0.1)] test imgs[int(n * 0.1): int(n * 0.2)] train imgs[int(n * 0.2):] for split_name, split_imgs in [(train.txt, train), (val.txt, val), (test.txt, test)]: lines [p.relative_to(root_dir).as_posix() for p in split_imgs] (root_dir / split_name).write_text(\n.join(lines))逻辑说明先排序让输入稳定再用固定 seed 打乱保证每次运行结果一致。按 8:1:1 切成三份写入内容用 p.relative_to(root_dir).as_posix() 转成如 images/kiwi_0001.jpg 的相对路径交给模型配置时由 path 字段拼出完整路径。参数说明seed 定了就不要换换 seed 等于换数据集划分指标不可复现img_dir 按实际目录改。提示任何原地改写 XML 或 labels 的操作都建议保留一份原始压缩包未解压的副本。初始状态一旦被覆盖想回头只能重新下载。4.5 YOLO 坐标越界cx 大于 1 或 w 为 0现象训练 loss 出现 nan或预测框完全超出图片范围打开某个 txt 看到 1.2567、-0.0034 这类值。原因标注框画到画布外、bndbox 被旋转框格式污染、或脚本把 xmin/xmax 顺序读反导致 w 为负。YOLO 格式要求每个值落在 [0, 1]宽度高度必须大于 0。解决全量检查越界一条脚本抓出来# check_bounds.py from pathlib import Path for txt in Path(dataset/labels).glob(*.txt): for line in txt.read_text().splitlines(): parts line.split() cls int(parts[0]) values [float(v) for v in parts[1:]] if not all(0 v 1 for v in values): print(越界:, txt.name, cls, values)说明parts[1:] 把 cx、cy、w、h 一起取出来做范围判断任何值超出 [0, 1] 都打印文件名和具体数值方便回源修 XML。边界情况1.000001 这类浮点余数可以容忍建议写成 v 1.0001如果出现 0.0 的 w 或 h必须删掉这条标注重标。提示浮点越界不要只改 txt要回头修 XML。YOLO txt 是标注的导出结果源头错了改一百遍 txt 也没用。5. 用 1838 张数据跑通一次 YOLO 训练yaml 怎么配、loss 怎么判、onnx 怎么验5.1 最小训练配置PyCharm 里三行命令跑起来我用 yolo 训练自己的数据集最常用的一条命令是在 PyCharm 的终端里执行的。先建虚拟环境装 ultralytics再准备 data.yamlpath: D:/datasets/kiwifruit train: train.txt val: val.txt names: 0: kiwifruitpath 指向数据集根目录train 和 val 用上一章生成的 txt 列表names 的索引必须和标签 txt 的 class_id 对齐这个数据集只有 1 类所以只有 0。然后用最小命令启动yolo detect train datadata.yaml modelyolov8n.pt epochs50 imgsz640 batch16参数说明yolov8n 是 nano 版本1838 张单类别数据用 n 或 s 足够上大模型只会加速过拟合imgsz640 是默认输入尺寸batch16 在 8G 显存也能跑显存小就降到 8。5.2 用损失曲线和 onnx 导出完成验收训练完打开 runs/detect/train 下的 results.csv 或曲线图重点看三个损失项box_loss 是框回归损失cls_loss 是分类损失dfl_loss 是分布焦点损失。单类别数据集里 cls_loss 通常掉得非常快这正常真正要盯的是 box_loss 和 dfl_loss 是否平稳下降。如果最后十几个 epoch 还在剧烈震荡优先把学习率降到 0.0005 再试。验证模型能否落地习惯先导出 onnx 再跑一张测试图yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 yolo predict modelruns/detect/train/weights/best.pt sourcetest.jpg导出 onnx 能提前暴露输入尺寸写死、动态轴不兼容这类部署问题predict 则是看框位置和置信度是否合理。两件事做完这个数据集才算真正被用顺手。我拿到任何新数据集第一个习惯永远是先画十张原图加标注框叠起来看一眼坐标对不对、类别对不对一眼就能看出来。这个步骤不花时间但能挡住后面一整晚的调参。文本标注永远是数据的最终解释权别让转换脚本替你背锅。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?