柑橘成熟度语义分割数据集搭建全流程:从采集、标注到部署

发布时间:2026/10/22 2:53:43
柑橘成熟度语义分割数据集搭建全流程:从采集、标注到部署 最近一段时间我在做农业视觉方向的项目其中一个核心任务就是柑橘成熟度的像素级识别。最终整出了一个3089张图的语义分割数据集labelme格式共3个成熟度类别。这篇文章就来详细拆解一下这个数据集从头到尾是怎么落地、怎么挖坑、怎么填坑的内容涵盖采集、清洗、标注、格式转换、训练和实测中的各种细节如果你们也在做果蔬成熟度识别或者打算自建分割数据集可以直接抄作业少走不少弯路。1. 项目整体设计与思路拆解1.1 这个数据集到底要解决什么问题柑橘在果园里的成熟度判断传统方法是靠人工在采摘季巡园肉眼观察果皮颜色来估算采摘时机。这个做法效率不高而且在规模化种植场景下人工巡检很难做到每天全园覆盖。所以现在很多智慧农业项目都在做自动化的成熟度识别用无人机或巡检小车采图靠模型把不同成熟度的果实从画面里分出来再结合位置信息生成采摘地图或产量预估。这里面的关键不是“识别出画面中有没有柑橘”而是要把每一个果实的成熟状态判断准确并且知道它在图像中的具体轮廓位置。只有做到这个粒度后续的采摘机械臂或统计系统才能直接使用。我做的这个数据集就是为这类任务服务的输入一张果园场景图模型输出每个像素的类别将柑橘划分为未成熟、半成熟、成熟三类。除了采摘机器人这类数据还可以用在产量预估、果实生长周期监测、水肥管理决策参考等场景。比如通过不同成熟度果实的数量比例估算未来几周的采收峰值提前调度人工。所以“3类别分割”不是拍脑袋定的它对应了实际管理中能直接指导行动的粒度。1.2 为什么选择分割而不是分类或检测最开始我其实犹豫过成熟度识别用图像分类行不行或者用目标检测给个框行不行后来逐步验证下来分割是更匹配实际需求的方案。先说分类。图像分类只能判断“这张图里有没有成熟果”但没法告诉系统果实长在哪、有多大、数量多少。果园巡检车拍一张图往往是多棵树的果实同时入镜分类模型在这种情况下基本没有可操作性。目标检测能给出矩形框但柑橘果实形状近似椭圆边界框会圈进去大量背景——可能是树叶、枝干也可能是旁边另一个果实。如果用检测框里的颜色来判成熟度背景的绿色或暗色会直接污染颜色特征严重干扰成熟度的判断。分割则不同。像素级标注把每一个果实的前景区域精确地分割出来模型学到的颜色特征只来源于果实本身不受枝叶背景干扰。而且分割输出的轮廓信息可以直接供给采摘机械臂作为抓取规划输入。从落地角度来说分割是“一步到位”的解法虽然标注成本高但后续使用链条更顺畅。1.3 3类标签背后的农业逻辑这个数据集里的3个类别我最终定为未成熟、半成熟、成熟。标签不是随意的它跟实际采摘决策直接挂钩。如果只分“可采/不可采”两类确实标注更简单但实际果园里的情况很复杂。不同市场的收购标准不一样有的收购商要九成熟的果有的要七成熟。如果只做二分类模型就丢掉了中间态的决策空间。分成3类之后业务侧可以根据当天需求调整“采摘阈值”——比如今天要早采那就把成熟半成熟都作为目标要精品果就只采成熟那一类。4类及以上我也考虑过比如把病果、坏果单独列出来。但坏果往往和成熟度是交叉的坏果可能是成熟果也可能是未熟果混在一起会让数据分布不可控。在当前项目周期内3类是覆盖核心需求的最小方案。有个细节必须强调半成熟类是最难标注的。果皮颜色从青绿到橙红是连续过渡的处在中间状态的那一批果实不同标注员很容易给出不同判断。这一块我后面会详细讲如果不定清楚标注规范整个数据集的一致性会出大问题。2. 数据采集与清洗实录2.1 图像采集的设备选择与拍摄策略采集设备我用了三种组合地面巡检小车的摄像头、手机拍摄、无人机航拍。为什么要混着来因为最终模型要部署的场景不止一个。巡检车是主要部署端手机是人工巡园补充无人机是果园全局快速巡检的手段。训练数据必须覆盖这些视角差异否则部署时会明显掉点。拍摄角度上侧视占大头——这是地面设备最常见的视角果实与镜头基本处于同一高度轮廓完整遮挡少。俯视角度来自无人机和部分高位拍摄主要用于早期测产场景。两种角度在数据集里的比例大约在8:2。光照条件是采集时要重点控制的变量。强逆光条件下果实颜色严重偏暗成熟果看起来接近深褐色连人眼都很难准确判断这种图我直接把剔除不浪费标注量。最理想的是阴天漫射光果实颜色最接近真实。晴天则选在上午9点前或下午4点后避免顶光直射造成的过曝。我特意在不同天气条件下重复拍摄了同一片果园让数据集中包含多光照场景这会让模型的泛化能力在实地部署时好不少。2.2 原始图像筛选与质量把控原始素材我拍了大概7000多张最终进入标注流程的是3089张。筛选标准不严的话后面所有环节都会成倍返工。筛选时首先排除的是模糊图。这里的模糊不仅指手抖还包括车辆移动造成的动态模糊果实边缘已经看不清了再标也是错的。其次排除过曝和欠曝图过曝的成熟果会变成一片亮白欠曝的青果会失去纹理细节这两种图我都直接丢掉。第三是构图重复度控制——连续视频帧导出的图片构图像素级接近对模型训练没有增量信息我会保留时间间隔较大、构图有明显差异的那部分避免数据集“看起来有那么多种实际信息量很低”。统一分辨率也是这阶段做的事。我保留原始拍摄分辨率不在这一步压缩因为后面的标注环节要按原图画轮廓。尺寸太小的图长边低于1200像素也剔除了否则小果实在标注时只有不到30个像素画出精确轮廓几乎不可能标了也没意义。2.3 数据分布设计与平衡策略数据集中不同成熟度的图片数量不能太偏。如果成熟果占80%模型在未熟和半熟上的表现会很差。我在采集时有意识地按大致比例收集成熟果约40%未熟果约35%半熟果约25%。半熟果的比例难提上去因为它在自然界中的存在周期本身就是最短的从“大部分青绿”到“大部分橙红”可能只有一两周。所以后期我专门在不同时间节点补拍了半熟状态确保这个类别没有被落下。除了成熟度分布还要考虑不同果园地块、不同品种带来的样式差异。如果只拍一个园子模型换到另一个地形背景完全不同的大棚就会崩。我在项目里选了3个不同管理水平的果园做采集土壤背景、杂草种类、树形都不同这个多样性对分割模型效果的影响非常大。3. Labelme标注细节与格式拆解3.1 Labelme的标注流程与工具设置数据集的标注工具选择了labelme这是图像标注里很常见的多边形标注工具。它输出JSON格式每个标注对象的轮廓由一串点坐标构成。安装很简单pip install labelme启动图形界面后常用操作是“Create Polygons”沿着果实边缘逐点打点。工具本身不长但真正出活儿要掌握几个设置。一个非常实用的选项是“Keep Previous Annotation”开启后标注完一个果实会自动进入下一个标注不用反复点菜单批量标图时一天能省不少时间。另一个是“Edit Polygons”模式用来微调已画好的节点位置。标注多边形的时候我建议点尽量落在果实边缘有视觉特征变化的位置——比如从弧形转为平直的地方、遮挡处的起始点、高光与暗部的交界处。这样既能保证轮廓贴合又不会无谓地增加点的数量。3.2 JSON文件核心字段详解labelme保存的JSON文件长这样做了解析注释{ version: 5.2.0, flags: {}, shapes: [ { label: ripe, points: [[123.7, 456.2], [130.1, 460.0], ..., [123.7, 456.2]], group_id: null, shape_type: polygon, flags: {} }, { label: half-ripe, points: [[340.2, 210.5], ...], group_id: null, shape_type: polygon, flags: {} } ], imagePath: IMG_20231015_1432.jpg, imageData: null, imageWidth: 4000, imageHeight: 3000 }这里有个容易搞错的地方labelme的points字段存的是像素绝对坐标不是归一化坐标。很多人在自己写转换脚本时把这个忘了结果画mask时错位得离谱。imageData字段在保存时默认是null图片内容单独以同名的jpg存在外面。如果你的JSON里imageData是空的读取的时候要注意让它通过imagePath找到原始图片文件。3.3 标注规范与边界判定标准这是整个数据集质量的核心。没有一份严格的标注规范几万次鼠标点击之后数据里全是“风格化”的标注对模型训练百害无一利。我在项目里定了几条铁律简单但有效。第一标签词汇完全统一不走任何形式的简写或缩写。未熟就是unripe半熟就是half-ripe成熟就是ripe一个字母都不能差。千万不能一会儿写“halfRipe”一会儿写“half-ripe”转换到训练格式时空谈一堆问题。第二半熟的判定标准要可视化。我制作了一张色卡图把青绿色、黄绿色、黄橙色、橙红色的渐变条打印出来标注时跟果实颜色比对。凡是果皮表面超过60%区域呈现橙红标成熟30%-60%呈橙红标半熟其余标未熟。这个量化标准让多人标注的偏差大幅收敛。第三遮挡问题的统一规则。果实被树叶或树枝挡住只要可见部分还超过果实整体轮廓的一半就沿着可见边缘标注。如果遮挡超过一半那这粒果实统统一律跳过不标。同时被两个果实挤在一起时要分别画轮廓可以紧贴但不要重叠。第四边缘贴合纪律。多边形节点必须落在果实与背景的分界线上不能多带背景像素。特别是被阳光照射的果实高光部分颜色很亮很容易把背景里的白纸或亮叶画进去。宁可抠小一圈不要多带背景前者的坏影响远小于后者。我实测下来一个人标完一张含15个果实的图大约需要4到8分钟。3089张图如果单人全标大概需要200多个小时相当于一个人全职干一个月。所以实践上肯定是多人并行而多人并行最依赖的就是上面那套规范。3.4 多人协作标注的质量控制多人并行标注不能标完就完了。我给整个过程加了两道质检。第一道是双标抽验。比如10%的图让两位标注员分别标同一张图然后计算两个标注结果之间的区域IoU。平均IoU要高于0.85如果有单张低于0.7的就需要回看一下是什么情况——多半是标准没理解透要不就是漏标了。第二道是终检抽检。在全部标注完成后按每个标注员打出的量分发5%-10%的图给终检人员重点核实三类错误标签是否张冠李戴、多边形是否包含了大块背景、是否有明显的漏标。终检发现的问题图退回给原标注员改并留错误案例供全员参考。这样迭代两三轮之后错误率会明显下降。质量控制这块我吃过亏。第一次做类似数据集的时候没有做双标抽检模型训练出来之后发现半熟类的IoU特别低可视化检查了一圈才发现是标注本身不一致这个标注员觉得是半熟的果实另一个标注员标成了成熟。后面粗暴地统一标准回改了一遍浪费了大量时间。做数据集这件事一开始省下的流程工夫最后都会在模型效果上补交回来。4. 格式转换与训练数据准备4.1 labelme格式转标准训练格式labelme格式并不能被主流分割训练框架直接使用。好在转换逻辑不算复杂读取JSON里的多段多边形坐标将每一类填充为对应的像素值得到一张单通道mask。我写了一个转换脚本实现了labelme到VOC格式的转换核心代码如下import os import numpy as np import cv2 import json from labelme import utils def labelme_to_voc(json_path, img_dir, mask_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_path os.path.join(img_dir, data[imagePath]) img cv2.imread(img_path) # 类别到索引的映射 class_map { unripe: 1, half-ripe: 2, ripe: 3 } label_name_to_value {_background_: 0} for shape in data[shapes]: label_name shape[label] if label_name not in label_name_to_value: label_name_to_value[label_name] class_map[label_name] # 调用labelme自带工具生成分割mask lbl, _ utils.shapes_to_label( img.shape, data[shapes], label_name_to_value ) ...有一个点要特别当心填充mask时背景必须是0类别从1开始。很多人在读mask时容易直接把这个当成图像显示结果整张图黑乎乎的以为数据出了问题其实是背景灰度就是0。保存mask时建议用PNG格式不要用JPEG否则mask边缘会被压缩出伪彩色在训练时产生让人头大的“坏像素”。如果是转向COCO格式思路也类似通过pycocotools的mask encode/decode来做多边形转RLE。4.2 针对成熟度任务的数据增强策略3089张图对于一个分割任务来说不算充裕成熟果、半熟果中能被用来学习的有效样本更少。数据增强是必须的但要克制特别是颜色相关增强要谨慎处理。我实践的增强组合是随机水平翻转、随机小角度旋转±10度以内、随机缩放0.8到1.2倍、随机裁剪再resize到输入尺寸。这些是空间变换不影响标签的语义。颜色增强我只做了轻度亮度和饱和度扰动亮度±20%饱和度±15%。这个幅度可以让模型适应自然光照波动又不会把“成熟果”硬生生调成“半熟果”。如果颜色扰动幅度过大模型会学到颜色和标签之间不可靠的对齐关系在真实场景中输错结果。没有把握的情况下颜色增强的幅度宁可保守一点。实现上建议直接使用albumentations库它对图像和mask的同步变换封装得很好不会出现“图像变了mask没变”的经典事故。4.3 数据集划分与验证集构建划分策略比想象中重要得多。很多抠脚操作是直接把图片随机打乱按7:2:1切分这在农业数据集中往往会导致验证集严重“偏科”——因为单片果园、同一时间段的图片特征高度相似一不小心训练集和验证集就出现了数据泄漏。我的做法是先把所有图片按来源果园和拍摄日期分组然后以组为单位切分。比如从A果园的10月12日这批图里抽一部分进训练集剩下的部分进验证集。说得更严格一点验证集和测试集尽量不包含与训练集同一天、同一个园子、同一棵树的图片。用这个方式划分后模型的验证指标会比随机划分低几个点但更接近真实部署效果。划分比例我用的是train:val:test 7:1.5:1.5。训练集约2160张验证集约465张测试集约464张。这样做保证验证集不会太小评估的结果比较稳定。划分完成后建议把划分结果存成一个固定的索引清单不要每次训练都重新随机划分否则不同实验之间没法对比。5. 模型训练与评估经验5.1 模型选择与骨干网络选型数据准备好了接下来是模型选型。我评估过几个主流分割架构最终落地方案选择的是结合具体部署平台来定。如果目标是跑在高性能GPU服务器上那DeepLabV3配上ResNet50或ResNet101骨干精度表现会比较稳。但部署在采摘机器人或者是巡检小车上的话算力极其受限这种重模型往往跑不到实时帧率。我项目里最终用的是DeepLabV3结构配MobileNetV3骨干或考虑U-Net配ResNet18也能达到不错的精度速度平衡。3000张级别的数据量扛不动参数量动辄数百兆的深度模型骨干太强反而容易过拟合。先用手边算力能承载的中轻量级模型跑通全套流程之后再根据推理速度预算升级骨干这是比较务实的路径。分割模型骨干网络的“性价比排序”一般从大到大依次是ResNet101 ResNet50 MobileNetV3-Large MobileNetV3-Small大家可以根据自己的硬件和精度需求来选。5.2 训练参数与loss设计训练参数我给出一个可以参考的起始配置优化器AdamW初始学习率1e-4学习率调整Poly策略power0.9权重衰减1e-4Batch size8根据显存可调Epochs120早停连续20个epoch在验证集上无提升就停止输入尺寸512×512Loss我用的是交叉熵Dice Loss的组合权重各占50%。为什么加Dice因为分割任务中前景像素占比远小于背景果实尤其是半熟果一级在图片里只占很小面积单纯用交叉熵会偏向背景类预测小目标物体很容易被整体吞掉。Dice Loss直接优化区域重叠程度对小目标更友好。针对3个类别不均衡的问题我在交叉熵部分加了类别权重。参考权重为背景0.5未熟1.2半熟1.8成熟1.0。半熟权重最高因为它的样本量最少、边界最模糊。Dice Loss本身会天然缓解类别不平衡所以不需要在两边都无脑叠权重。5.3 评估指标与可视化检查模型效果不能只看总准确率。分割任务的评估指标我最看重mIoU、各类别IoU、Dice coefficient和Pixel Accuracy。mIoU代表所有类别上预测与真实区域的平均重合度是我判断模型整体好坏的关键分数。但干这行久了单独看一个平均分容易对自己产生错误信心。要拆开各类别看成熟果通常IoU很高未熟果和背景容易混淆半熟果分数往往最低。如果成熟类IoU已经0.9以上而半熟IoU只有0.5说明问题集中在中间态那就要针对半熟做数据补充或加权处理。最直接、最有价值的检查方式还是把验证集的分割输出用彩色叠加到原图上一张张人工看。这一步一定要做不能只看像素级分数的“表面繁荣”。常见的问题比如模型把绿色果实的阴影部分认成了背景或者把背景里的一个亮色落叶识别为成熟果这些在IoU数字上反应迟钝只有肉眼看图才能第一时间敏锐发现。我对训练过程中每个epoch结束后的验证集mIoU做了记录跟踪。训练刚开始时背景类IoU快速上升模型在“找得到了”的阶段40个epoch以后果实类IoU会稳定提升此时对应“找得准了”的阶段。6. 常见问题与排查技巧实录6.1 标注数据常见错误与修复整个流程走下来遇到的坑不少给你的建议也不是凭空来的。第一个高发问题是JSON和图片文件名不匹配。批量操作时最容易出现的是拷贝图片后改了名但JSON没跟着改。最保险的方式是在标注前就固定图片文件名标注期间不做任何重命名操作。如果出现了JSON丢失的情况优先检查同名JSON是否存在缺失的直接删除对应图片。第二个高发问题是标签词汇不一致。多人协作时一个标“half-ripe”另一个标“halfripe”转换统计类别时直接炸裂。解决思路很直接规范文件写清标签代码转换脚本里做一次标签清洁把所有意外空格和替换字符统一掉。第三个问题是多边形顶点过密。有些标注员为了追求轮廓精细沿着弧线不停地点点。单个果实用了几百个点直接把JSON撑到几MB这会让训练时的数据加载明显变慢。采样原则是弧线处每段两三像素一个点已完全够用。如果发现单图JSON异常膨胀写个脚本检查点的数量跟果实数做个除法就能迅速定位出问题。第四个问题是坐标越界。有的标注员在标边缘果实的时候一个手抖点拉到了图像外面坐标数值会超出图片尺寸。我在转换脚本里强制加了检测凡是越界的点自动裁剪回边界而不是让它留在mask里变成脏像素。6.2 类别不平衡的应对在采集季节问题下成熟果图片往往数量充足甚至过多未熟和半熟都相对稀缺。特别是半熟果生命周期短数量有限这几乎是所有成熟度数据集的天然难题。我采用的应对方案有三个。第一是重采样对半熟样本占比多的图片在数据加载阶段重复采样保证每个batch里半熟果出现的频率有所提升。数据增强里的随机裁剪也有的效果很好切出来的半熟果局部图会让模型有更均衡的输入分布。第二是损失函数加权这个上面已经说了关键的思路是在不扭曲数据分布的前提下让模型更关注少数类。第三是区域裁剪增强把包含半熟果的区域裁出来单独作为训练样本这种方式对处理“整张图半熟果只有一两个”的情况特别有效。做这一块的时候我发现与其一次性堆所有手段不如先把重采样和加权这两板斧发挥到位调整好超参数再加区域增强。弄得太复杂了反而不容易定位是哪个环节带来的提升。6.3 实地部署与实时性优化训练完模型只是第一步真正落到果园的巡检设备上经常还要过性能关口。农业现场的推理环境不可能等于训练环境。光照、白平衡、摄像头参数都可能有偏差。我给一个很直接的建议部署端要让模型跑在跟训练数据同分布的环境下。跑测试时发现前后果园的颜色偏差很大可以给输入加一个预处理把图像色温做一个统一的全局校准再喂给模型效果往往立竿见影。实时性方面MobileNetV3骨干在边缘设备上已经有不错的基础性能但如果发现推理速度不达标可以考虑做模型量化。INT8量化是相对主流且可控的取舍实测下来模型体积大概能降到四分之一速度提升明显IoU损失一般控制在2个百分点以内。另外也可以考虑把输入分辨率从512×512下调到384×384速度会涨不少代价是小果实的分割精度下降。具体怎么选得拿实测数据说话。6.4 关于3089张数据量是否够用的客观评价很多人一听到3000张分割数据第一反应是“会不会太少”。老实说对这个任务来说数量只是一个起点数据的多样性和标注一致性比绝对数量重要得多。如果场景固定、光照可控、品种单一3000张已经完全够训练出一个可用的分割模型。但如果你计划跨果园、跨品种部署那就要持续做增量采集。比较好的思路是先用这3000张把模型训到基线水平然后收集实地巡检中模型置信度低或者明显出错的图片人肉确认后补进训练集。这种主动学习式的补充远比比盲目加拍数据效率高。另外在模型训练完成之后单独对测试集做一次误差分析把错误实例分成几类目标过小、边界不清、强光干扰、遮挡严重、类别混淆。搞清楚你的数据“死在哪个环节”下一个迭代周期才有明确的优化方向。做的过程里我自己体会最深的一点是3000张图从不嫌多也不会嫌少真正决定数据价值的是你给数据定的标准以及你对标准的执行力度。我有一个伙伴当时图省事觉得把标注流程简化一点能省一半时间结果模型效果差到没法用最后全部重来。做数据这件事图省事往往是最大的成本。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询