news 2026/9/28 8:36:45

1700张猕猴桃VOC+YOLO数据集:从格式转换到姿态分桶评测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
1700张猕猴桃VOC+YOLO数据集:从格式转换到姿态分桶评测

简介:面向目标检测学习与工程实践的数据集资源,以猕猴桃盘中摆拍场景为目标,提供不同角度拍摄的标注图像,适合算法初学者验证YOLO、Faster R-CNN等检测模型,也可用于生鲜识别、农产品分拣等视觉项目的数据扩充。压缩包共2000个文件,以Pascal VOC格式的XML标注文件与YOLO格式的TXT文件为主,压缩后约74.58MB;所有图片均采用labelImg工具画矩形框标注,类别仅一项Kiwi,全包共标注5255个目标框,标注信息完整,便于直接训练和评估。目前已有106人学习,资源同时给出VOC与YOLO两种主流格式,省去自行转换的额外工作,可无缝接入常用检测框架;多角度摆拍图片覆盖不同拍摄方位与摆放姿态,能增强模型对视角变化的鲁棒性,适合作为目标检测入门或专项数据集使用。

1. 用不同角度摆拍图训练猕猴桃检测:1700 张 VOC+YOLO 数据集怎么用起来

做目标检测最怕的不是跑不通 YOLO,而是手里没有一份格式干净、能直接喂进训练管线的数据集。这份 1700 张猕猴桃数据集同时给了 VOC 和 YOLO 两种标注格式,省掉了最磨人的造数据环节,适合正在做果蔬分拣、果实计数、或者拿单类水果练手目标检测的从业者和学生。但我拿到这类压缩包的第一反应不是解压就训练,而是先做三件事:核对标注、转格式、按拍摄批次划分。

标题里那句“都是不同角度摆拍图标注”是双刃剑。好处是姿态覆盖广,模型泛化潜力好;坏处是如果训练集和验证集划分不当,验证分数会虚高,现场换个角度就漏检。这篇按“格式吃透 → 转格式 → 训练参数 → 避坑 → 姿态分桶验证”的顺序展开,目标是让你拿到压缩包后能直接照做,而不是在试错里浪费一整天。

2. 拆开双格式压缩包:VOC 与 YOLO 标注的字段差异和标签设计

这类压缩包的常见结构是 VOC 目录和 YOLO 目录分开,VOC 里放 JPEGImages 和 Annotations,YOLO 里放 images 和 labels,外加一个 classes.txt。为什么同一份数据要做两遍?因为 VOC 是给人看的,方便标注工具读写;YOLO 是给训练管线吃的,每张图配一个同名 txt。你只需要维护其中一份,另一份靠脚本生成,前提是先搞清楚两边字段的对应关系。

2.1 文件结构怎么配对:图片、XML 与 txt 的一一对应

无论压缩包内层怎么组织,配对关系永远是“同名不同后缀”:1700 张图,对应 1700 个 xml 和 1700 个 txt。常见做法是这种布局:

目录/文件作用
VOC/JPEGImages/1700 张 jpg 原图,文件名唯一
VOC/Annotations/同名 xml,PASCAL VOC 结构,人工维护源
VOC/ImageSets/Main/可选的划分文件,train.txt / val.txt
YOLO/images/训练用图片,可与 VOC 共用
YOLO/labels/同名 txt,每行一个目标
classes.txt类别清单,一行一个类名

jpg 和 txt 文件名完全一致,训练框架靠文件名索引标签。最容易出问题的是解压后 labels 目录没跟 images 目录放在同一个 data.yaml 指向的位置,导致训练时找不到标签。我一般把 VOC 和 YOLO 两套都保留,XML 作为唯一人工维护源,txt 全部由脚本生成,避免两边改着改着出现不一致。

2.2 从 VOC 到 YOLO 会丢掉哪些字段:转换前的四个确认点

YOLO txt 只保留五列,任何多余的语义信息都装不进去。这不是 bug,是设计取舍。转格式前要确认四件事:一是 classes 顺序以 classes.txt 为准,不能自己按字母序猜;二是 difficult=1 的样本是否要保留,YOLO 没有对应字段,保留的话它只会被当成普通正样本,丢弃更符合实际;三是 truncated 字段在摆拍图里通常都是 0,因为照片里的果基本完整,但如果背景里出现只露出一半的果,建议直接删掉这类框;四是 pose 字段虽然 VOC 里有,YOLO 没地方写,如果后续想做姿态分析,转格式时就要把它单独导出成 csv,丢了就真没了。

拿到任何数据集都要做体检:打开 10 张 xml 肉眼对一遍框位置,别只信文件数量。1700 张的规模不算大,手工抽验成本很低,这步省不得。

2.3 单类还是按姿态拆类:标签设计要在转格式前定死

猕猴桃检测的常规做法是建一个 kiwi 类,因为摆拍图的标注目标就是果实本身。但“不同角度摆拍图”意味着果蒂、果脐、侧面茸毛三种形态的外观差异很大。如果只建一类,模型要自己消化类内差异;如果拆成 kiwi_stem、kiwi_side、kiwi_bottom 多类,就需要标注里本来就区分了姿态,否则重标成本很高。

我的建议是先用单类跑一版,等第 6 章那种按角度分桶评测出来,再决定要不要把短板角度拆成独立类。类名一旦定死,训练跑起来之后再改,所有 xml 和 txt 都得重来,后悔药相当难吃。无论用 labelImg 还是同类标注工具导出的 xml,结构基本都是 PASCAL VOC 那一套,classes.txt 顺序和 data.yaml 的 names 必须一一对应,这是全流程里最容易被忽略的一致性约束。

注意:YOLO 的 class_id 从 0 开始计数,classes.txt 第一行对应 id 0,不是 1。这个顺序错了,所有标签都会错位。

3. 把 1700 张跑成一次完整训练:转换脚本、分组划分与 data.yaml

格式吃透之后,进入可复现的落地流程。这一章的目标是在本地跑通“VOC → YOLO → data.yaml → 训练启动”的最小闭环,每一步都给出可直接抄的命令和脚本。

3.1 环境准备:先保证 ultralytics 管线能跑

训练框架用 ultralytics 是目前最省事的路径,它对单类自定义数据集支持很完善。先建一个干净的 Python 环境:

conda create -n kiwi python=3.10 -y conda activate kiwi pip install ultralytics python -c "import torch, ultralytics; print(torch.__version__, ultralytics.__version__)"

python 3.10 是 ultralytics 支持较好的版本。pip 装完以后跑的那行 import 不只是验证安装,更是提前暴露 torch 是否 CPU 版。如果打印出来的 torch 是 cpu 版,训练会慢得让你怀疑人生,需要先按你的显卡装对应 CUDA 版 torch,再装 ultralytics。数据目录我一般固定在项目根目录下,后续所有脚本和命令都用绝对路径,不依赖“当前在哪个目录”。

3.2 VOC 转 YOLO 脚本:XML 解析、归一化与越界兜底

这是全流程里最核心的一段脚本。VOC 的 bndbox 存的是左上角和右下角的绝对像素坐标,YOLO 需要的是归一化后的中心点坐标和宽高,换算公式很简单,但有几个边界条件必须处理:

import os import xml.etree.ElementTree as ET CLASSES = ["kiwi"] # 必须与 classes.txt 顺序一致 def convert(voc_dir, yolo_dir): os.makedirs(yolo_dir, exist_ok=True) xml_list = [f for f in os.listdir(voc_dir) if f.endswith(".xml")] for xml_name in xml_list: tree = ET.parse(os.path.join(voc_dir, xml_name)) root = tree.getroot() # 图像尺寸是归一化的分母,缺失时直接跳过 img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASSES: continue # difficult 样本在 YOLO 里没有对应语义,默认丢弃 difficult = obj.find("difficult") if difficult is not None and int(difficult.text) == 1: continue bnd = obj.find("bndbox") x1 = float(bnd.find("xmin").text) y1 = float(bnd.find("ymin").text) x2 = float(bnd.find("xmax").text) y2 = float(bnd.find("ymax").text) # 越界框裁回图像范围内,避免训练时报宽高为 0 x1, y1 = max(0, x1), max(0, y1) x2 = min(img_w, x2) y2 = min(img_h, y2) if x2 - x1 <= 1 or y2 - y1 <= 1: continue cx = (x1 + x2) / 2 / img_w cy = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{CLASSES.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") txt_path = os.path.join(yolo_dir, xml_name.replace(".xml", ".txt")) with open(txt_path, "w") as f: f.write("\n".join(lines)) # 示例调用 convert("VOC/Annotations", "YOLO/labels")

脚本做了四件必须做的事:按 CLASSES 表映射编号、把绝对坐标换成归一化中心点宽高、丢弃 difficult 样本、把越界框裁剪回图像范围内。第四点尤其重要,摆拍图里偶尔会有标注框顶点在负半轴的情况,不兜底的话训练时会出现 width=0 或 height 为负的非法 bbox,报错信息还很难查。跑完后随机抽 20 个 txt 打开看,坐标是否都落在 0 到 1 之间是肉眼体检的第一道关。

注意:CLASSES 列表顺序必须与 classes.txt 逐行一致,YOLO 的 class_id 从 0 开始。转换时如果发现某个 xml 里 name 不在 CLASSES 里,不要静默跳过,把它单独打印出来,大概率是标注时打错了类名。

3.3 训练集划分:按拍摄批次分组而不是按文件随机抽

1700 张数据,常见划分是 85/15 或者 80/20。水果检测类别单一,验证集太小看不出差距,15% 左右比较合理。但划分方式比比例更重要:摆拍图通常按拍摄批次成组产生,同一批次的光线、背景、摆放方式高度相似。如果按文件随机抽,同一批照片会同时出现在训练集和验证集,模型靠记忆背景就能把得分刷得很高,验证集数字就失去意义。

import os import random from collections import defaultdict IMG_DIR = "YOLO/images" VAL_RATIO = 0.15 SEED = 42 groups = defaultdict(list) for name in os.listdir(IMG_DIR): if not name.endswith(".jpg"): continue # 按文件名前缀代表拍摄批次,实际命名规则可能不同,按你的情况改 prefix = name.split("_")[0] groups[prefix].append(name) keys = list(groups.keys()) random.Random(SEED).shuffle(keys) val_count = max(1, int(len(keys) * VAL_RATIO)) val_keys = set(keys[:val_count]) with open("train.txt", "w") as ft, open("val.txt", "w") as fv: for prefix, names in groups.items(): for name in names: line = os.path.join(IMG_DIR, name) + "\n" (fv if prefix in val_keys else ft).write(line)

这个脚本假设文件名前缀能代表拍摄批次。如果压缩包里的命名看不出批次,就退一步按目录分,或者手工把明显同一场景的照片归到同一组。SEED 固定成 42,保证每次复现同一套划分,方便对比不同超参数的效果。划分结果写入 train.txt 和 val.txt,每行一条图片绝对路径。注意不要写相对路径,训练命令从不同目录发起时相对路径很容易失效。

3.4 data.yaml 与首次验证:确认标签真的被加载了

ultralytics 靠 data.yaml 定位数据和类别信息,写法如下:

path: /你的绝对路径/kiwi_dataset train: /你的绝对路径/kiwi_dataset/train.txt val: /你的绝对路径/kiwi_dataset/val.txt nc: 1 names: - kiwi

train 和 val 指向 txt 文件时,ultralytics 会把 txt 里每一行当成图片路径,并自动在图片同级的 labels 目录找同名 txt 标签。如果你把 labels 放在别的路径,需要在 yaml 里写清楚,或者让 labels 和 images 保持兄弟目录关系,这是最容易踩的路径坑。写完后先跑一行验证命令,而不是直接开训练:

yolo detect val model=yolov8n.pt data=data.yaml imgsz=640

数据加载正常的话,输出会显示图片数量和标签数量;如果出现 label 相关的警告或者指标全是 0,通常是标签没找到,回到 3.2 的目录配对检查。第一次跑只为了验证数据链路,模型用官方预训练权重即可,不用改参数。

4. 小数据集的训练参数:预训练权重、增强幅度和收敛判断

1700 张在目标检测里属于小样本。直接上完整训练流程不是不行,而是会浪费大量时间在盲目调参上。这一章给出我在这类单类水果数据集上的参数策略,以及判断模型有没有在收敛的依据。

4.1 从 COCO 预训练权重起步,不要从零训练

1700 张连 COCO 训练集的零头都不到。从零训练一个检测头,骨干网络学不到有判别力的纹理特征,尤其在猕猴桃这种茸毛反光、果形椭圆的细粒度外观上,几百张图很容易让模型学到背景颜色而不是果实本身。常见做法是直接使用预训练权重:yolov8n.pt 或 yolov8s.pt 都是在 COCO 上训好的,拿来接着训自己的单类数据,等于把骨干网络对形状、边缘、纹理的先验迁移过来。

选 n 还是选 s:摆拍图场景相对简单、目标大且居中,n 足够;如果打算做移动端部署,或者果实在画面里占比较小,用 s 更保险一点。两者在 1700 张规模上的训练成本差异不大,但推理速度差不少,先想清楚部署场景再选。

4.2 必调参数:imgsz、batch、mosaic 与学习率

参数建议值理由
imgsz640摆拍图单果占比大,640 够用;512 会损失果柄细节
batch16小于 8 时 BN 统计抖动厉害,容易触发 loss 变 NaN
lr00.005COCO 预训练权重默认学习率偏大,小数据集降到一半更合适
freeze10冻结骨干前段,保护预训练特征不被小数据带偏
patience50单类任务 200 epoch 内通常能收敛,提前停住避免过拟合
mosaic0.5保留拼接增强但不过度,避免破坏椭圆果形

mosaic 是这类水果数据集里最需要斟酌的增强。默认 1.0 会把四张摆拍图拼成一张,模型会看到“果旁边突现另一个果”的假上下文;保留一定比例能提升小目标鲁棒性,但比例过高会让椭圆果被旋转拼接搞得形状失真。ultralytics 默认在最后 10 个 epoch 自动关闭 mosaic,这个机制保留就行,不用自己额外处理。

4.3 收敛判断:看 loss 曲线、混淆矩阵总和不为 1 不是 bug

训练命令我一般这样写:

yolo detect train \ model=yolov8n.pt \ data=data.yaml \ imgsz=640 \ epochs=200 \ batch=16 \ lr0=0.005 \ freeze=10 \ patience=50 \ seed=42 \ cache=ram \ project=runs/kiwi name=exp1

cache=ram 会把 1700 张图一次性读进内存,1700 张 640 分辨率大约占几个 GB,内存不够就改成 cache=False。seed=42 和划分脚本保持一致,方便复现同样一套数据。训练时不要只盯着一张图看,我的判断套路是:前 10 个 epoch 看 loss 是否在下降,warmup 阶段 loss 短暂上升是正常的;50 个 epoch 后看 val loss 是否还在降,连续多轮不降就交给 patience 早停。

很多人第一次看训练生成的混淆矩阵,会发现行和列加起来不是 1,这不是 bug。混淆矩阵里有 background 类别,还有漏检的 FN,总和不会严格闭合,别在这个问题上浪费时间。说句血泪经验:单类 1700 张,只要数据链路是干净的,yolov8n 跑到 200 epoch,val 的 mAP50 通常能到 0.95 以上。如果这个分数上不去,问题八成不在网络结构,而在标签本身。

5. 猕猴桃数据集避坑记录:五个翻车现场的现象、原因、处置

这份数据集的特征是“不同角度摆拍图标注”,所以很多坑都跟拍摄姿态和划分方式相关。以下是这类数据集上最容易翻车的五个场景,每条按现象、原因、解决办法写,可以直接对照排查。

5.1 mAP 虚高但现场漏检:随机划分的锅

现象:训练完 val 的 mAP50 高达 0.98,但把另一批新拍的摆拍图喂进去,漏检率立刻上来了。原因:如果训练集和验证集来自同一个拍摄批次,背景和光线几乎一样,模型记住的不是猕猴桃而是那套背景纹理。解决:按 3.3 的方式改成按批次划分,让训练集和验证集在场景上彻底隔离。另外现场验证时不要单张图打点,拿一个角度桶里的多张图一起看 PR 曲线,才说明真实水平。

5.2 训练刚开始 label 全为空:路径与类别编号的检查顺序

现象:训练启动后屏幕刷出一堆 label 为空的警告,训练指标从第一步就是 0。原因:images 和 labels 没配对,常见是 labels 目录没解压、data.yaml 的 path 写到了不含 labels 的目录、或者 txt 里的 class_id 超过了 nc-1。解决:先跑 3.4 那行 val 验证命令,确认标签数量等于图片数量;再看 txt 里第一个数字是否从 0 开始。class_id 从 1 开始是新手最容易犯的错,YOLO 编号从 0 计,classes.txt 第一行对应 id 0,不是 id 1。

5.3 BN 崩溃导致 loss 变 NaN

现象:训练到某几个 epoch,loss 突然变成 nan,或者 loss 曲线出现断崖后不再恢复。原因:batch 太小加上学习率太大,BN 统计在小批量上抖动幅度过大,遇到摆拍图里偶尔出现的极端亮度背景就崩了。解决:batch 提到 16 以上,lr0 降到 0.005 以下,同时用 freeze=10 保护骨干前段的预训练统计量。如果已经崩了,直接删掉 runs 里的断点重新训,不要指望它自己能恢复过来。

5.4 果柄被误检成独立目标

现象:PR 曲线在某个置信度区间出现大量假正例,打开验证集可视化图,发现模型把果柄单独框了出来。原因:标注口径不统一。有人把果柄包进框里,有人从果体截断,不同角度的摆拍图里果柄投影长度又不一样,模型同时学了两套形状,干脆把果柄当成一个独立目标来处理。解决:回到 VOC 源标注,统一口径。果柄在整图里占比很小,我一般建议全部包含进果体框,或者统一截断在连接处,改完重新跑一遍转换脚本再训练。

5.5 数据增强把椭圆果拉成圆

现象:训练时打开增强预览,发现猕猴桃被旋转 90 度加缩放之后椭圆比例失真,像一颗圆果;推理时对斜向摆放的果识别变差。原因:ultralytics 默认增强幅度是为通用检测设计的,旋转和随机缩放对大目标影响不明显,但对有明显长轴方向的椭圆水果特别不友好。解决:把 degrees 限到 15,scale 限到 0.3,flipud 设成 0,不要做上下翻转,让增强保留真实的椭圆形态。真实摆拍已经提供了角度多样性,增强应该做细节扰动,而不是形态重塑。

6. 用姿态分桶评测 best.pt:把摆拍角度变成可量化的短板清单

训练完不是终点。对“不同角度摆拍图”最好的利用方式,是把验证集按姿态分桶,分别做评测,得到一份短板清单。

先把验证集图片按果蒂朝上、侧面、果脐朝下三个姿态分开。常见做法是靠文件名或目录名里的关键词区分;如果数据里没带这类标记,就按拍摄批次肉眼过一次 val 的 1700 张图,把验证集分成三组。我一般给每组单独写一个 data.yaml,然后对同一份 best.pt 分别跑:

for angle in top side bottom; do mkdir -p eval/$angle grep "$angle" val.txt > eval/$angle/$angle.txt cat > eval/$angle/data.yaml <<EOF path: /你的绝对路径/kiwi_dataset train: eval/$angle/$angle.txt val: eval/$angle/$angle.txt nc: 1 names: - kiwi EOF yolo detect val model=runs/kiwi/exp1/weights/best.pt data=eval/$angle/data.yaml done

三个桶的 mAP50-95 摆在一起,短板一目了然。通常果脐朝下的那个桶分数会明显低于侧面,因为摆拍时该角度的照片数量偏少,光线也最差。找到短板后有两个处理方向:一是把该姿态的图片在训练集里做一次过采样,复制后加轻微亮度抖动再放进去;二是回头检查该姿态的标注质量。这两步都比盲目加训练轮数有效。

我现在的习惯是拿到这类摆拍数据集,先按姿态分桶做一次预检再决定训练策略,训完按桶出报告而不是只报一个总 mAP。这个习惯帮我省掉了好几次在错误方向上调参的冤枉时间,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:36:10

创维壁纸电视A10H:27.9mm贴墙厚度背后的结构与画质解析

前一阵帮朋友挑客厅电视&#xff0c;他看了一圈跟我说&#xff1a;"创维壁纸电视A10H系列发布了&#xff0c;27.9mm迄今最薄&#xff0c;这跟普通超薄电视有啥区别&#xff1f;不都是薄吗&#xff1f;"我说你这个问题问得挺典型&#xff0c;等装上你就明白了。后来机…

作者头像 李华
网站建设 2026/9/28 8:35:25

Agent训练沙箱调度、镜像加载与状态恢复机制解析

1. 从一次Agent训练任务大面积超时说起如果你正在做Agent方向的开发&#xff0c;尤其是需要跑大规模强化学习或者批量推理训练&#xff0c;大概率遇到过这种场景&#xff1a;凌晨两点&#xff0c;训练集群里几百个Agent任务同时卡住&#xff0c;日志里刷屏的是超时和资源等待&a…

作者头像 李华
网站建设 2026/9/28 8:34:39

基于B/S架构的工艺品展示与交流平台设计与实现

最近刚把一个基于Java Web的工艺品展示系统做完&#xff0c;从需求梳理到数据库设计&#xff0c;再到最后的部署上线&#xff0c;前后改了三版。这个题目乍一看就是个普通的信息管理系统&#xff0c;但真正动手才发现&#xff0c;手工艺品展示跟普通商品展示完全是两码事——它…

作者头像 李华
网站建设 2026/9/28 8:34:16

Draco 元数据解码(Metadata Decoder)格式规范详解

图形学3D渲染 【免费下载链接】draco Draco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/draco1/…

作者头像 李华
网站建设 2026/9/28 8:31:44

Python Flask餐饮美食点餐管理系统:设计与实现解析

Python Flask 酒店餐饮美食点餐管理系统上周有个开小餐馆的朋友找我&#xff0c;说店里还是手写菜单、人工传菜那套老流程&#xff0c;高峰期经常出错。我花了两个晚上用 Flask 帮他搭了一套点餐管理系统&#xff0c;从菜品展示、用户下单到订单处理、后台管理全部跑通。今天把…

作者头像 李华