☰
智慧农业香蕉成熟度识别:LabelMe分割数据集构建全流程解析
2026/10/21 22:40:21 网站建设 项目流程

搞过图像分割数据集的朋友都知道,标注这一步看着简单,真正做起来才明白什么叫“细节决定成败”。特别是农业场景下的成熟度识别,不像通用物体检测那样框个框就行,香蕉的颜色渐变、遮挡关系、果实排列方式,都会直接影响后续模型的训练效果。最近我正好整理完一套用于智慧农业的香蕉成熟度识别分割数据集,卡达巴香蕉,一共702张图像,LabelMe格式,3个类别。今天就把从采集规范、标注设计到数据转换的完整过程记录下来,给准备做农业视觉项目的人做个参考。

这套数据的核心任务不是“判断图片里有没有香蕉”,而是要把每一根香蕉的果实区域从背景里精确分割出来,同时判断它属于哪个成熟阶段。之所以强调分割而不是分类或检测,是因为在采摘、分拣、品质分级的实际场景里,模型不光要知道“这根香蕉熟了没”,还要知道“熟的区域具体在哪里、边界轮廓是什么样的”,这样才能配合机械臂去做抓取或剔除。无论是做算法研究、毕业设计,还是给分拣设备做算法验证,这套数据都能直接用。

1. 项目背景与需求拆解:为什么成熟度检测要上分割模型

很多人在接触这个需求时,第一反应是“用目标检测框一下不就行了”。这个思路在成熟度粗判时确实可行,但如果要落地到品质分拣,检测框远远不够。香蕉是弯曲的,而且一串果实之间常常互相遮挡,一个检测框里可能同时包含两根不同成熟度的香蕉。这时候框级别的标签会让模型学到大量干扰特征,边界也含糊。分割模型则能输出像素级别的类别归属,哪个区域是未熟、哪个区域是成熟、哪个区域已经过熟,一清二楚,机械臂可以精确避开过熟区域去抓取成熟果实。

1.1 三个成熟度类别的“切分”逻辑

三类标签看着简单,实际标的时候才发现“成熟度边界”是最大的主观分歧点。有人觉得带一点黄就算成熟,有人觉得必须全黄才算。所以这套数据集在定标注规范时,我坚持用“果皮外观主导、触感辅助”的硬规则,尽量降低人工判定的因素。

  • 类别一 unripe(未熟):果皮以绿色为主,允许少量黄绿色过渡,但绿色覆盖面积必须超过80%。这个阶段的香蕉果实较硬,表皮光泽度偏低。
  • 类别二 ripe(成熟):果皮以黄色或金黄色为主,绿色面积降到20%以下,允许出现少量褐色斑点,但斑点面积不能超过果面总面积的15%。
  • 类别三 overripe(过熟):果皮出现大范围褐色或黑色斑点、斑块,甚至局部发黑发皱,斑点面积接近或超过30%。严重时会出现果皮开裂。

这套标准的考虑是:在实际分拣线上,果皮颜色是机器最容易捕捉、也最稳定的信号,即使在不同批次、不同光照条件下,只要按面积比例划定,标注员之间的分歧就能大幅降低。如果任务需要更细的成熟度等级,后续可以在这三个大类基础上再做拆分,但作为第一版本,三类是性价比最高的配置。

1.2 这套数据集能落到哪些真实场景

很多人会把这类数据集简单理解成“给算法刷点分用”,其实它能在好几个环节发挥作用。第一个场景是采摘期预测。果园管理者通过定期拍摄香蕉串的图像,统计果实成熟度分布,就能估算未来几天到几周的采收窗口,减少“全采”或“漏采”带来的损失。第二个场景是采后分拣线。在传送带上安装工业相机,实时分割每根香蕉的成熟区域,配合机械臂剔除过熟果、单独分流未熟果,效率比人工目检稳定得多。第三个场景是仓储管理。香蕉是呼吸跃变型水果,熟得很快,仓库里每隔一段时间扫一遍货架图像,分割识别成熟度变化,就能及时调整冷库温度或催熟批次。

也就是说,这套数据不单纯是“图像分类”的玩具,它面向的是从田间到货架一整条产业链的视觉感知需求。后续不管你是要训练语义分割模型,还是转成COCO格式做实例分割,这套LabelMe标注底子都能接得住。

2. 数据采集与LabelMe格式解析

数据集的根基在采集,采集质量决定标注效率和模型上限。702张图不是随便拍出来的,而是按场景、光照、角度三个维度做了刻意安排。采集时我坚持的原则是:宁可多拍难样本,也不要全部拍“顺光、无遮挡、单根果实”的简单图。模型训练不怕难样本多,怕的是真实场景中遇到的复杂情况在训练集里根本没出现过。

2.1 图像采集时最容易踩的坑

第一个坑是背景过于干净。很多人拍数据集喜欢把香蕉放在纯色板或摄影棚里,图倒是好看,但模型学到的全是“香蕉=背景下的一块黄色”,一旦放到真实的果园背景或传送带杂色背景下,精度立刻崩掉。所以这套数据里我特意混入了果园枝叶遮挡、人工光照不均、传送带阴影、人手握持等情况,让模型必须依赖果实本身的纹理和颜色特征去做判断。

第二个坑是拍摄角度单一。如果所有图都从正上方俯拍,模型对侧视角度会很敏感。采集时我至少覆盖了平视、俯视、斜视三类角度,每张图里香蕉的弯曲方向、头部朝位尽量不同。这样可以让模型对形态变化更鲁棒。

第三个坑是分辨率问题。分割模型对边缘要求高,图像清晰度不够,标注时连边界都看不清,更别提后续学出精细的轮廓。采集时我统一用不低于1000万像素的相机或手机后摄,保存时保持原分辨率,只在训练预处理阶段再缩放。如果原始图尺寸太小,标注多边形会显得很粗,模型输出的边缘也毛糙。

第四个坑是时间点的选择。香蕉成熟度是动态变化的,我今天拍一组绿色样本,放到第7天可能已经变黄。如果整个数据集是几天内集中拍的,类别分布就会失衡。所以采集周期我拉长到三到四周,每隔一两天拍一次同一批果实,这样能天然覆盖从绿到黄再到褐斑的完整变化过程,类别之间的过渡样本也足够多。

2.2 LabelMe格式的字段含义与存储细节

LabelMe是图像标注里非常主流的开源工具,它以JSON文件为单位,每张图片对应一个同名JSON文件。很多人第一次打开LabelMe的JSON会蒙圈,这里把核心字段拆开看就容易懂了。

{ "version": "5.2.1", "flags": {}, "shapes": [ { "label": "ripe", "points": [[x1, y1], [x2, y2], "...", [xn, yn]], "group_id": null, "description": "", "shape_type": "polygon", "flags": {} } ], "imagePath": "IMG_0007.jpg", "imageData": null, "imageHeight": 1080, "imageWidth": 1920 }

这个结构里有几个信息值得留意。shapes是一个列表,每张图像里的每个标注对象都对应一个元素,叶子、背景里的杂物如果标了也会出现在这里。label是类别名,必须和最终训练的类别配置完全一致,拼写错误会导致转换脚本直接失去对应标签。points是多边形的顶点坐标列表,顺序很重要,需要按顺时针或逆时针连续描边,不能交叉,否则后续生成掩码时多边形区域会变得混乱。shape_type固定为polygon,这是分割任务最常用的标注形式,比矩形框更能贴合果实弯曲轮廓。

再往下,imagePath是原始图像的文件名,注意它和JSON文件名要一一对应。imageData这一栏如果保存了,会把图像以Base64形式嵌进JSON,文件体积会膨胀很多,通常转换时我们会把它置为null,只保留路径引用。图像尺寸字段在转换时很有用,掩码生成依赖这两个值,如果图像被压缩过但JSON里没同步更新,就会造成掩码和图像尺寸不匹配的惨案。

3. 标注规范与实操记录

标注规范不是挂在墙上的口号,它直接决定模型上限。同一个成熟度类别,不同人标出来的多边形可以差出10%的面积。所以动手之前,我先把标注规则定成了一条条可执行的条款,并让标注人员先标10张试跑,不合格的返工。

3.1 标注前的六项准备

第一项是标签配置。在LabelMe的label列表里,我固定只放unripe、ripe、overripe三个标签,不多不少,避免误点。第二项是快捷键设置,多边形创建、撤销、保存这几个高频操作都绑到顺手的位置,提升效率。第三项是坐标系确认。LabelMe的坐标原点在图像左上角,x向右,y向下,这和很多图像处理库一致,但要注意有些标注工具用的原点在左下角,混用会出问题。第四项是遮罩文件夹规约,一张图对应一个同名JSON文件,图像和JSON放在同一个目录,这样管理最省心。第五项是命名规则,我用“序号+采集批次+角度”的格式命名图片,比如KDB_0031_L2_F(卡达巴、第31张、第2批次、俯视),方便后期回溯异常样本。第六项是判定规则的演示,先把每类各挑两三张图做成“标准参照图”,标注员遇到不确定的情况先对照参照图再下结论。

3.2 画多边形时需要注意的细节

多边形标注看着是体力活,实际是技术活。第一,贴合边界时不是每个像素都描一遍,而是“长边分段、拐弯加密”。果实轮廓的直线区域每隔一段距离取一个点,曲率大的地方加密取点,这样既保证边界精度,又不至于让多边形有几万个顶点,拖慢后续处理速度。第二,香蕉果实之间如果挨得非常近,边界很难分清,我的原则是“宁可略微收一点,也不要跨到邻果上”。稍微收一点最多损失一两毫米边缘,跨过去就会把两个类别的像素混在一起,对分割训练是灾难。第三,被遮挡的果实区域只标可见部分。比如一串香蕉里,后面的果实被前面的挡住了,如果强行靠想象把被挡住的轮廓也画出来,模型就被灌输了“错误形状”。按可见区域标注是最尊重真实观测的方式。

还有个容易被忽略的细节是果实与果柄的连接处。成熟度分割的关注区域是果实主体,果柄一般不算在内。但果柄和果实之间常有渐变色过渡,标注时我要求统一从果柄与果肉的“凹陷交界处”切断,这样每张图的语义边界才一致。如果某些图包含了果柄区域,类别的纯度就被污染了。

3.3 质量校验:两轮检查法

标注完成不等于数据能用。我做了两轮检查,第一轮是机器检查,用脚本扫描所有JSON文件,检查是否存在标签拼写错误、points为空、多边形面积过小、坐标超出图像边界等问题。这些问题肉眼不一定看得过来,脚本一跑就能筛掉一大批。第二轮是人工抽查,按照“简单图、中等难度图、困难图”各抽5%,把标注结果以叠加掩码的形式可视化出来,逐张看边缘贴合度和类别正确性。这里有个实用技巧:把原图和掩码叠加成半透明效果,过熟斑点的边界是不是画准了、绿色果皮和背景里相似绿色叶片是不是区分清楚了,都看得非常直观。

二次检查发现的最典型问题是“过熟类别被低估”。很多标注员对overripe判断偏保守,非得看到大片黑斑才肯标,导致很多已经明显过熟但仅局部发黑的香蕉被标成了ripe。后来我调整了规则说明,增加了几张临界状态参照图,返工后情况就好了很多。

4. 从702张标注图到可用训练集:转换与增强

数据标完,最麻烦的还没到——LabelMe的JSON格式并不能直接被深度学习训练代码消费,必须转换成掩码图或COCO标准格式。这个环节坑很多,尤其是当你要同时训练语义分割和实例分割模型时,转换逻辑差别很大。

4.1 LabelMe JSON如何转换成训练掩码

如果是语义分割,最常用的转换目标是“单通道灰度掩码图”,每个像素的灰度值代表类别编号。比如约定0为背景、1为unripe、2为ripe、3为overripe。转换思路很简单:读取JSON,拿到每张图中的多边形顶点,用OpenCV的fillPoly函数在空白掩码上填充对应灰度值即可。但要注意两点,一是多边形的点序要规范,fillPoly能处理凹多边形,但如果顶点顺序完全乱序,填充结果可能出错;二是图片尺寸必须和原图严格一致,所以我读取的imageHeight和imageWidth就是掩码画布尺寸的最终依据。

import json import cv2 import numpy as np label_map = {"background": 0, "unripe": 1, "ripe": 2, "overripe": 3} with open("KDB_0031_L2_F.json", "r", encoding="utf-8") as f: data = json.load(f) mask = np.zeros((data["imageHeight"], data["imageWidth"]), dtype=np.uint8) for shape in data["shapes"]: cls_id = label_map[shape["label"]] points = np.array(shape["points"], dtype=np.int32) cv2.fillPoly(mask, [points], color=cls_id) cv2.imwrite("KDB_0031_L2_F_mask.png", mask)

这段代码看起来简单,实际跑起来会有好几个隐藏问题。第一个是JSON里可能出现中文字段或特殊字符,读取时一定要指定encoding="utf-8",否则在Windows下很容易报编码错误。第二个是imageData里残留的Base64数据会让JSON文件特别大,读取速度变慢,建议转换前统一清洗掉。第三个是如果同一张图里同一个类别出现了多个多边形区域,不能只取最后一个,要全部画进同一张掩码里。这里用循环遍历每个shape再fillPoly,每次叠加,后画的会覆盖先画的,同一类别的掩码颜色一致,所以不会出问题。但不同类别的多边形如果区域重叠,后画的类别会盖住之前的,这种错误应该在标注阶段就避免。

如果是实例分割,目标格式通常转成COCO JSON,每条标注包含segmentation多边形坐标、bbox、category_id、image_id等字段。实例分割与语义分割的关键差异在于,同一类别的不同香蕉个体需要区分开,所以每个独立果实都要作为单独一个annotation对象。我在标注时要求每个果实单独画一个多边形,不许把同一串香蕉合并成一个多边形,这样转换时每个shape天然就是一个实例。

4.2 数据集划分与样本均衡策略

702张图不算大,但作为分割数据集已经可以跑出不错的效果。单类别的样本量大概在200多张,但如果直接按8:1:1划分,容易出现某个类别的难样本恰好都跑到了测试集,导致测试分数虚低。我的做法是:先把图像按“采集批次”分组,确保同一天拍摄的相近图像不会同时出现在训练集和验证集,否则会因为图像相似度太高而高估模型性能。然后在这个前提下,把图像分成训练约560张、验证约70张、测试约70张。

类别均衡这个事也要做点文章。由于成熟周期原因,早期采集的图大多属于unripe,后期才大量出现overripe,直接随机划分会让overripe在训练集中占比偏低。我统计过每张图中各类别掩码的像素占比,然后按图像级别调整训练集,尽量保证每个类别在训练集中都有足够多的“干净样本”。如果overripe样本确实偏少,后续还可以在增强阶段做额外处理。

4.3 增强手段与模型选型建议

分割模型对数据量的需求比分类模型大,702张图直接训练容易过拟合,所以增强策略必须做好。我用的是组合式增强:随机水平翻转、随机旋转15度以内、随机亮度对比度扰动、随机裁剪缩放,其中随机裁剪这个操作要格外小心,裁剪区域必须和掩码同步进行,不然前景和标签就对不上了。用现成的增强库时,要确认它会同时增强图像和掩码,而不是只增强图像。实操中我发现,对香蕉这类颜色敏感任务,色调类增强要克制。香蕉成熟度判断高度依赖颜色信息,如果过度做色调偏移、饱和度扰动,模型会学到“颜色不可靠”的错误先验,反而降低识别精度。我最后只保留了亮度、对比度的轻微扰动,以及随机遮挡模拟(类似CutOut的一种方式),用来增强模型对局部遮挡的鲁棒性。

模型选型方面,如果你的目标跑实时推理,建议用轻量分割网络配合MobileNet或类似轻量骨干的Encoder;如果追求精度,可以上Transformer结构的分割网络。数据集本身是LabelMe格式,不管哪种模型,都需要先经过上面说的转换步骤。如果做的是语义分割,最终输出是每个像素的类别图;如果是实例分割,则要保留每个果实的独立掩码。这里我在转换时就多留了一个心眼:语义分割掩码和实例分割的COCO标注,我都各生成了一份,一份用来做成熟度区域分析,一份用来做单果计数和定位。

5. 常见问题与排查技巧实录

做数据集和跑算法不太一样,算法报错有日志可查,数据集出问题往往是“暗伤”——模型精度上不去,但不知道哪里坏了。我整理几个实操中真正遇到过的、也最容易复现的坑,大家碰到时可以逐一排查。

5.1 JSON文件损坏与读取失败

这是最常见的问题。标注过程中如果有人用别的编辑器打开过JSON并保存,格式可能被改动;或者文件拷贝时路径带中文,导致程序读不到。我的排查思路是:先写一个脚本批量遍历所有JSON文件,用json.load强行解析,凡是解析失败的单独列出。解析失败里九成是缺少,或括号不匹配,原因通常是编辑器的自动格式化把imageData那段超长Base64字符串截断了。遇到这种文件,直接重新打开LabelMe,另存一份新JSON覆盖即可。如果不想覆盖,可以写一个容错脚本,把imageData字段删除后再解析,靠shapes`里的坐标数据就可以重建掩码,不必依赖那段Base64。

5.2 类别标签错乱与误标修正

错乱分两方面。一是标签拼写不统一,比如有人标了Ripe,有人标了ripe,还有人标了ripe(末尾带空格)。训练时这些都会被当成不同类别,类别数从3变成6。排查方式是统计所有JSON中的label取值集合,一眼就能看出哪些标签需要统一替换。二是标错类别,比如把过熟初期标成了成熟,这种靠脚本检测不出来,只能靠人眼复查。我的经验是挑模型最容易混淆的临界图做可视化叠加检查,重点看两类交界处是否有明显的标签跳跃。如果发现某个文件标错了,修改LabelMe里的label字段再重新导出JSON就行,不需要重新画多边形。

5.3 边缘遮挡与同色背景的标注心得

农业场景里,香蕉和背景的区分度有时候很差。绿色香蕉在绿色叶片背景下,边缘几乎消失;黄色香蕉在木色传送带或纸箱背景下,颜色也非常接近。这种情况的标注策略和通用规则不太一样。

我坚持“边缘无明显分界线时,以纹理和结构变化为准”。比如绿色香蕉和叶片之间虽然颜色接近,但香蕉表面有纵向棱线、有光泽,叶片表面有叶脉纹理,两者交界处通常有一道微弱的明暗变化。标注时把缩放比例拉到100%,沿着这道明暗变化描边,不要凭肌肉记忆随手画。对于被遮挡的果实,我还是那句话:只标可见像素。有些朋友觉得“完整标出来模型才能学到完整形状”,但分割任务里,遮挡区域本来就没有真实观测值,硬标一个想象中的形状只会混淆模型。让模型学会“看不见的区域就是没数据”这件事,比让它编造形状重要得多。

最后再多说一句个人经验。整理这套数据时,最大的体会是“七分标注、三分训练”。702张图的模型效果好不好,不取决于你训练时跑了多少轮,而取决于标注阶段有没有把类别定义、边界规则、遮挡策略想清楚。如果你准备从零开始做一个农业识别分割数据集,我建议先别急着批量标,花一天时间把规则定细、做十几张标准参照图,后面能省下至少一多半的返工时间。这套香蕉成熟度数据集本身是一个相对小而完整的范例,拿它练手、做迁移学习或者验证算法,都是很值得跑的。如果有朋友已经跑出了结果,欢迎交流一下不同分割网络在成熟度边界上的表现差异,这个数据集的下一版,我会继续补充更多批次和弱光场景的样本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询