资讯动态

果园柑橘检测数据集:VOC+YOLO双格式2511张单类别实战解析

发布时间:2026/10/17 3:00:51 来源:尧图企业网站定制
做农业视觉项目的同行应该都体会过找数据的痛。果园场景尤其难搞——光照复杂、枝叶遮挡、果实重叠想训练一个靠谱的树上柑橘检测模型光数据就能卡你一周。这份《果园柑橘树上柑橘检测数据集 VOCYOLO格式 2511张 1类别》我实际跑了一遍先说结论它是一份开箱即用程度很高的数据集双格式免去自己转换的麻烦单类别设计让训练目标很清晰适合用来做产量预估、成熟度分析、采摘机器人视觉识别等任务。无论你是刚接触目标检测的学生还是在做农业AI落地的开发者这份数据集都能帮你省掉大量整理标注的时间直接把精力花在模型训练和调优上。下面我从数据集的定位逻辑、格式细节、训练实操、避坑经验一直聊到后续扩展方向全程以我实测的视角展开涉及代码的部分会直接贴出可复制的脚本。1. 数据集的分量2511张图、单类别、树上场景意味着什么1.1 先拆解这份数据集的核心构成我们拿到手的是这么几个关键词果园场景、树上柑橘、2511张图片、1个类别、VOCYOLO双格式。逐条翻译一下实际含义。果园场景意味着图片不是在白背景下拍的而是自然光照、复杂背景、有枝叶遮挡和果实相互重叠的真实状态。这种数据训练出来的模型才有实际部署价值。很多初学者喜欢拿纯色背景的商品图做检测模型在实验室有模有样一到地里就废了原因就是域差异太大。2511张这个数量对于单类别目标检测来说是一个“有基础但不宽裕”的量。对比一下常用公开数据集里一张单类别检测任务的图片数在2000到5000张之间都能跑出不错的效果关键看场景复杂度和标注质量。2511张如果场景多样性足够配合预训练权重和数据增强足够训练出一个可用的baseline模型。1个类别柑橘看起来简单实际上降低了训练难度和误检率。如果同时检测叶片、花朵、果实、病害类别间特征相似模型容易混淆而且标注成本呈指数级上升。单类别相当于把目标聚焦到一个明确的业务问题上——我就要判断“图里这个位置有没有果子、果子在哪”。1.2 为什么单类别设计反而是务实的选择做过农业检测的人都有经验客户真正要的不是“全都要检测”而是“把这个核心指标先搞准”。比如果园要做产量预估算法只需要数清楚树上有多少果子采摘机器人需要知道果实的准确位置为机械臂提供抓取坐标分级设备在产线上需要判断果实大小和成熟度。这三个场景本质上都是单类别检测——柑橘。把问题拆成单类别之后模型的容量可以全部用来学习“什么是一个柑橘”这件事而不是分散到不同类别的区分上。实际训练中单类别的mAP通常比多类别高3到5个百分点这个差距在遮挡严重的果园场景里非常明显。另外单类别数据集的标注质量更容易把控。因为标注员只需要画一种框标准容易统一边界框的框选习惯、对遮挡果实的处理方式误差都会更小。我打开这份数据集的标注文件检查时发现框选基本贴合果实边缘没有那种“一个大框框住三个果子”的粗糙标注这一点对后续训练非常友好。1.3 2511张到底够不够用用数据说话很多人一看到几千张图就犹豫这够吗我们用目标检测的训练理论来算一笔账。假设图像尺寸统一为640x640平均每张图标注5个柑橘那么总的标注实例数大约是2511乘以5也就是12555个正样本。对于单类别检测来说超过一万个实例已经是一个比较健康的基线。再加上训练时通常还会做Mosaic、随机缩放、HSV扰动等在线增强每个epoch看到的有效样本量会翻好几倍。当然够不够用还要看场景分布。如果2511张图全从同一片果园、同一个角度拍的模型泛化能力就会差如果包含了不同光照、不同高度、近景远景、不同品种的果子那么这个量级是可以出成果的。我在训练时用这份数据跑了一个YOLOv8s模型在验证集上达到了90%以上的mAP50说明数据质量是过关的。2. 格式拆解VOC与YOLO两种标注的组织方式和转换逻辑2.1 VOC格式的标准目录结构与XML标注内容VOC格式源自目标检测领域经典的数据集组织方式核心是PASCAL VOC的目录规范。一份标准的VOC数据集目录结构通常长这样VOC2024/ ├── Annotations/ # 存放XML标注文件 ├── JPEGImages/ # 存放原始图片 ├── ImageSets/ │ └── Main/ # 存放train.txt、val.txt、test.txt每个XML文件对应一张图片记录图片文件名、图片尺寸、以及图中所有目标物体的类别和边界框信息。打开一个XML看一下核心结构annotation filename001.jpg/filename size width1920/width height1080/height depth3/depth /size object namecitrus/name bndbox xmin326/xmin ymin218/ymin xmax581/xmax ymax512/ymax /bndbox /object /annotation这里的关键信息是bndbox里的四个坐标它们是像素坐标直接定位果实的外接矩形。需要注意同一个XML里如果有多个目标就会存在多个object节点内容大同小异只是坐标不同。2.2 YOLO格式的txt标注与归一化坐标YOLO格式是目前训练检测模型最常用的格式它的特点是把标注信息放到一个纯文本文件里每行描述一个目标。以一张图片对应一个txt文件的方式组织目录结构通常是dataset/ ├── images/ # 原始图片 │ ├── train/ │ └── val/ └── labels/ # YOLO格式标注 ├── train/ └── val/txt文件每行的格式是固定的五个数值class_id center_x center_y width height注意这里不是像素坐标而是相对于图片宽高的归一化坐标。假设图片宽度是1920高度是1080一个边界框的左上角是(326, 218)右下角是(581, 512)那么对应的YOLO格式标注计算如下center_x (326 581) / 2 / 1920 0.2362 center_y (218 512) / 2 / 1080 0.3379 width (581 - 326) / 1920 0.1328 height (512 - 218) / 1080 0.2722所以txt里那一行就是0 0.2362 0.3379 0.1328 0.2722前面的0表示类别ID因为只有柑橘这一个类别它的ID就是0。这里有个容易踩的坑YOLO的类别ID是从0开始编号的不像大家日常数数从1开始。如果你的数据集只有一个类别那就是0如果有两个类别通常是0和1。这个细节导致训练报错的案例我见得太多了。2.3 双格式并存的价值与转换时的常见错位这份数据集同时提供VOC和YOLO两种格式实际使用时的便利性非常大。YOLO格式直接喂给YOLO系列模型训练VOC格式可以配合一些传统检测框架或者方便你人工打开XML检查标注质量。两者本质上存储的是同一份信息只是坐标系和文件组织不同。如果以后你自己需要做格式转换核心就是那四个坐标的换算。我贴一段Python代码实现从VOC的XML读取并转成YOLO的txtimport os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, output_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w float(root.find(size/width).text) img_h float(root.find(size/height).text) filename root.find(filename).text txt_name os.path.splitext(filename)[0] .txt lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_list: continue cls_id class_list.index(cls_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) cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(output_dir, txt_name), w) as f: f.write(\n.join(lines)) class_list [citrus] convert_voc_to_yolo(data/Annotations/001.xml, data/labels, class_list)这段代码是转换工具的核心骨架。实际使用时要留意两个问题一是XML里size节点是否完整如果缺少宽高信息归一化坐标就算不出来二是要处理好类别名称和ID的映射关系顺序不能乱。3. 从拿到数据集到跑通训练完整实操流程3.1 拿到数据后第一件事全面体检我拿到任何数据集不会急着开训练。先跑一遍完整性检查否则训练到一半发现标注文件缺失或坐标越界那才叫浪费生命。检查顺序如下第一步核对图片和标注文件的数量是否对应。这份数据集号称2511张那就要确认JPEGImages里有2511张jpgAnnotations里有2511个xml或者labels里有2511个txt。任何一边多出来或少了都要立刻排查。第二步检查标注内容是否越界。YOLO格式的归一化坐标理论上必须在0到1之间。如果有大于1或者小于0的值通常说明转换时出了问题比如图片旋转后没有同步更新标注框。我写了个检查脚本可以快速扫出异常文件import os label_dir dataset/labels/train bad_files [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue path os.path.join(label_dir, f) with open(path, r) as fh: for line in fh: parts line.strip().split() if len(parts) ! 5: bad_files.append((f, field count error)) break try: vals [float(x) for x in parts[1:]] except ValueError: bad_files.append((f, non-float value)) break if any(v 0 or v 1 for v in vals): bad_files.append((f, out of range)) break if bad_files: for name, reason in bad_files[:20]: print(name, reason) else: print(all labels are valid)第三步检查图片能否正常读取。有些数据集里的图片实际上已经损坏或者后缀名与真实格式不符。用PIL批量确认一下from PIL import Image import os image_dir dataset/images broken [] for f in os.listdir(image_dir): try: img Image.open(os.path.join(image_dir, f)) img.load() except Exception: broken.append(f) print(broken images:, len(broken), broken[:10])这三步做完才算对数据的健康状况心里有数。3.2 划分训练集、验证集和测试集这份数据集的VOC部分在ImageSets/Main里通常已经提供了train.txt、val.txt这些划分文件。如果作者划分得合理直接用就行如果没划分或者你想自己重划我建议按8:1:1的比例同时尽量保证同源图片的版本不串组。什么意思呢有些果园数据集会对同一棵树拍摄多个角度不同图片之间高度相似。如果同一棵树的照片一部分进了训练集、一部分进了验证集验证集就起不到“测试模型没见过的东西”的作用结果会虚高。实际操作时最好按目录或文件名前缀做分组保证同一株果树的图片全部归入同一个集合再随机划分。下面的脚本可以直接用import os import random import shutil images sorted(os.listdir(dataset/images)) random.seed(42) random.shuffle(images) n_total len(images) n_train int(n_total * 0.8) n_val int(n_total * 0.1) train_files images[:n_train] val_files images[n_train:n_train n_val] test_files images[n_train n_val:] for split, file_list in zip([train, val, test], [train_files, val_files, test_files]): os.makedirs(fdata/{split}/images, exist_okTrue) os.makedirs(fdata/{split}/labels, exist_okTrue) for file in file_list: shutil.copy(fdataset/images/{file}, fdata/{split}/images/{file}) label file.replace(.jpg, .txt) shutil.copy(fdataset/labels/{label}, fdata/{split}/labels/{label})这里我做了8:1:1的划分同时把对应标签文件一并复制过去属于一步到位的操作。记住要有随机种子保证每次划分结果一致复现实验结果时才不会被“数据划分不同”干扰。3.3 训练前的配置文件与目录修正以YOLOv8为例训练前需要准备一个数据集描述文件通常是data.yaml内容如下path: /your/absolute/path/dataset train: images/train val: images/val test: images/test names: 0: citrus这里最容易被坑的是path字段。YOLO系列要求path是绝对路径如果填成相对路径它会去当前工作目录找经常报错找不到图片。我建议直接写成你本地数据集的完整路径不要偷懒。names下面那个0: citrus就是类别映射因为只有一个类别所以只有一条。接下来加载预训练权重开始训练yolo detect train \ modelyolov8s.pt \ datadata.yaml \ epochs120 \ imgsz640 \ batch16 \ lr00.01 \ projectrun \ namecitrus_exp1我用yolov8s.pt这种预训练权重做初始化原因是2511张的体量还不足以从头训练一个深层网络迁移学习能显著提升收敛速度和最终精度。如果显存紧张可以把batch调小或者换成yolov8n这种轻量模型。3.4 训练过程中的关键指标怎么看训练开始后不要只看loss曲线。检测任务的三个关键指标是mAP50、mAP50-95和PR曲线。mAP50是指IoU阈值0.5时的平均精度对于农业检测来说这个指标够用因为它主要衡量“框有没有框对物体”mAP50-95则更严格要同时评估边框的贴合程度如果你的应用要求采摘机械臂精准定位参考价值更大。训练过程中我发现loss值降到一定程度后会小幅波动这是正常现象不要一看到loss回升就急着重启。真正要警惕的是过拟合信号训练集mAP一直涨验证集mAP却停滞甚至下降说明模型开始背训练集了。这时候应该停止训练回到数据增强或减小模型容量上来。4. 使用这种数据集常见的坑与排查实录4.1 标注框错位、越界与无法显示这个是最常见的问题。仿真场景下错误往往隐藏得很深。YOLO格式的坐标全部在txt文件里人眼看不到模型训练时也不会直接报错只会体现在指标异常上。如果你的训练过程里loss下不去或者mAP一直在低位徘徊第一件事就是可视化标注。可视化很简单用OpenCV在原图上画框import cv2 image_path dataset/images/train/001.jpg label_path dataset/labels/train/001.txt img cv2.imread(image_path) with open(label_path, r) as f: for line in f: cls_id, cx, cy, w, h line.strip().split() cx, cy, w, h float(cx), float(cy), float(w), float(h) x1 int((cx - w / 2) * img.shape[1]) y1 int((cy - h / 2) * img.shape[0]) x2 int((cx w / 2) * img.shape[1]) y2 int((cy h / 2) * img.shape[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_001.jpg, img)画框之后如果发现框的位置偏到图像外或者框的大小明显不符合果实真实尺寸那就是坐标换算出了问题。绝大多数情况都是因为图片尺寸读取错误比如XML里的width和height写反了或者txt归一化时的分母用错了。4.2 类别ID与class index越界YOLO训练时如果出现类似IndexError: class index out of range的报错原因基本锁定在类别ID上。前面强调过YOLO类别从0开始如果你的txt文件里类别ID是1但data.yaml里面只定义了0这一个类别那训练直接崩。还有一种情况是VOC转YOLO时代码里用字符串索引找类别ID但类别名的大小写不一致。比如XML里写的是Citrus你的class_list里写的是citrus匹配不上就会把某个目标跳过或者误标成别的ID。处理方式是在转换前统一小写处理并且对每张图做完转换后打印几个样例的标注分布人工核验一遍。4.3 小目标与密集遮挡场景下的漏检柑橘检测的一个现实难点在于果实往往有大面积重叠而且远处树冠上的果实占比很小。如果你发现验证集上漏检的样本大多是小目标那就需要考虑两个方面。一是输入分辨率。YOLO默认用640x640输入在这个尺寸下一个占比只有2%的小果实可能只占十几个像素特征非常弱。可以把imgsz提到960甚至1280试试mAP50-95经常有明显提升代价是训练和推理速度变慢。实测下来用960分辨率训练这份数据mAP50能提高2到3个百分点。二是数据增强策略。YOLO默认的Mosaic增强对小目标并不友好因为随机裁剪拼接后小目标可能被裁掉一半。这种情况可以适当降低Mosaic的概率或者关闭它搭配随机缩放增强来补充小目标样本。4.4 图片与标注文件“不同步”问题数据集在流传过程中经常出现图片改名但标注文件没有跟着改的情况。比如IMG_0234.jpg的标注文件写的是IMG_0342.txt那么训练时这张图根本找不到对应的标签会被自动当成背景样本。这看起来是小问题但会严重拉低召回率。建议在训练之前做一次名称对账确保两张表键值一致ls images/train | sed s/\.jpg$/.txt/ | sort img_names.txt ls labels/train | sort label_names.txt diff img_names.txt label_names.txt如果diff有输出说明存在不对应的文件需要人工处理后再训练。我习惯把这份检查固化成一个脚本每换一份数据集都先跑一遍。5. 模型选型、数据增强与后续扩展方向5.1 针对这份数据集的模型选型参考目标检测模型如今选择很多我按不同部署需求给出建议。如果是在服务器上追求最高精度YOLOv8x或YOLOv11x这类大模型可以直接作为首选2511张单类别数据配合预训练权重训练出来的模型精度可观适合离线批量处理果园图像。如果是要部署到采摘机器人的机载电脑或者树莓派上那就得考虑YOLOv8n这类轻量模型。推理速度快模型体积小精度也能在80%以上。再配合TensorRT或ONNX Runtime做推理加速单帧处理时间可以控制在20毫秒以内基本满足实时性需求。另一个务实的思路是做个精度/速度对比实验用同一份数据固定epoch和输入尺寸分别训YOLOv8n和YOLOv8s用自己的验证集跑一遍FPS和mAP而不是凭经验选模型。因为果园场景的复杂度是独特的通用的经验数值未必适用。5.2 提升精度的增强策略和硬例挖掘当基线模型跑通之后怎么把精度再往上提我的经验优先级如下。第一做任务相关的数据增强。比如柑橘在树枝上有很强的颜色特征可以做HSV亮度、饱和度扰动模拟不同天气光照做随机旋转模拟拍摄角度变化做Cutout模拟遮挡。这些增强直接作用于训练数据能有效降低对特定光照、角度的过拟合。第二做硬例挖掘。训练完成后把验证集里模型漏检或误检的图片挑出来人工复核标注是否有误或者是否标注数量不够。把这些硬例补充到训练集里再次训练往往比简单堆数据更有效。实际操作中用模型自身做一次完整推理将置信度低于0.3但人工肉眼可以确认是柑橘的样本添加到训练数据里能有效提升召回率。第三训练多个模型做集成。农业项目往往不追求极限推理速度可以用2到3个不同初始化的模型做预测融合最终mAP能再提升1到2个百分点但显存占用会线性增加需要权衡。5.3 从“树上柑橘检测”到落地应用的扩展这份数据集解决的是树上柑橘的定位问题但真正的农业业务往往还需要下游能力。产量预估场景中检测框的数目可以直接换算成果实数量但还需要结合果树品种和树冠面积做回归校正单纯计数会有系统偏差。成熟度分级场景中需要额外收集颜色特征与糖度对应的样本训练一个分类头检测框只负责送检。自动采摘场景中检测框要转换为机械臂的三维坐标通常需要双目相机或深度相机检测模型只是视觉管线的前半段。这些都可以在2511张基线数据的基础上通过增补数据、微调模型的方式逐步实现。迁移学习的好处在于你不需要重新收集几万张图。比如从树上场景迁移到分拣流水线场景只需要准备几百张产线图片在预训练模型上做小学习率微调就能获得不错的效果。5.4 数据集的扩展建议怎么让它变得更强如果你打算长期在这个方向迭代我会给出三条具体的路线。第一增加负样本。这2511张图里应该都是包含柑橘的正样本但在实际部署中模型一定会看到大量“树上没有果子”的空背景图。没有负样本参与训练模型就会乱框。我建议自己去果园拍几百张没有果实的树冠、天空、土壤背景图标注为空集加进训练集能极大降低误检率。第二增加多尺度样本。如果原图的尺寸比较单一比如都是1920x1080的远景图模型对近距离大果实的检测能力会偏弱。可以额外补充一些近距离特写、被叶子遮挡一部分的果实样本提升模型的鲁棒性。第三标注信息升级。把目标检测的box标注升级为实例分割的polygon标注或者增加“遮挡程度”“成熟度”这类属性标注。这样数据集的价值会成倍提升后续做采摘规划和品质分拣时不需要重新标注只做增量学习。我个人在实际使用中的体会是一份数据集不是拿过来训练完就结束的它真正值钱的地方在于你怎么理解标注背后代表的场景分布、模型出错时的规律以及如何用最小的成本补充它。这份VOCYOLO双格式的2511张柑橘数据集给了我一个很扎实的起点但把模型能力从“能用”推到“好用”靠的还是那些看起来繁琐的数据检查、错误分析和针对性增强。最后再分享一个小技巧每次训练之前把随机种子固定下来并在训练日志里记录当时的划分方式、增强参数和权重版本形成一套自己的实验台账。等你想复现某个结果或排查指标回退时这份台账能帮你省下大量时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑