如果你也在做温室作物的视觉识别,大概率遇到过这种场景:茄子挂果密密麻麻,用目标检测框架框出来的边界框东叠一个西叠一个,NMS过完以后剩不下几个,果实计数根本没法看。后来我把方案从目标检测换成实例分割,给每个茄子单独输出一个像素级的mask,漏检问题才明显缓解。今天要聊的就是面向这个场景的一份公开数据资源:1类农学茄子目标实例分割数据集,类别名是eggplant,总共2766张图像,训练集2261张、验证集505张,图像分辨率集中在1280x1706附近,同时带lab标注文件。我会从这份数据集的定位、数据划分、分辨率与标注管理、训练链路、实测踩坑到后续迭代,完整拆一遍。
1. 这份茄子实例分割数据集,定位在哪
1.1 检测框解决不了的数果难题,为什么要交给mask去切
目标检测和实例分割最大的差别在输出粒度。检测只给一个axis-aligned的矩形框,框里除了茄子还有叶片、背景和遮挡物。当两个果实挨得很近时,边界框会大量重叠,后处理NMS要决定留谁舍谁,很容易把其中一个真值目标直接滤掉。而实例分割在检测的基础上,还要对每个目标的像素做分类,输出一个二值掩码。框可以重叠,mask可以互相压着,但每个实例的边缘是清晰的,计数只需要数连通域。
做农业视觉的人对"数果"这件事应该深有体会。温室里茄子成串生长,枝条间的果实彼此遮挡非常常见,三四个茄子挤在一个矩形框里,你甚至没法从框内区域判断到底有几个目标。分割掩码不一样,即使是部分遮挡,模型也能把露出来的轮廓切开,只要mask面积达到一定阈值,就可以算作一个有效实例。所以说,如果项目的核心需求是株果比统计、机械臂采摘点定位、果实尺寸估计,实例分割比检测高一个层次。
1.2 只有一个eggplant类别,说明任务边界被设计得很窄
标题里明确写了类别名是eggplant,没有第二个类别,这是一份单类别的实例分割数据集。单类别带来的直接好处是,模型不用花容量去区分茄子和其他蔬果之间的类间差异,所有的学习精力都集中在轮廓拟合和前景分离上。对新手来说,模型的训练难度、调参空间都小很多;对工程项目来说,单类别的分割模型更稳,不容易出现跨类混淆导致的误检。
但"单类别"也说明它的使用边界很清楚:这份数据不做成熟度分级、不做病虫害识别、不做品种分类。想从这份数据出发做成熟和未成熟茄子的区分,得自己补颜色层级或者新增类别。我见过有人拿着单类别数据硬套多分类任务,最后验证时发现模型把绿色幼果和叶片混成一团,问题不在模型,而是任务和标签对不上。单类别的分割先做扎实,再往上游加分类头,才是这条数据线比较合理的延展路径。
1.3 什么样的人适合拿这2766张图上手
我理解这个标题的读者大概分三类。第一类是高校研究者和学生,做农业视觉、果实分割方向,需要一份任务边界清晰的数据集作为对比基线。第二类是智慧农业或温室自动化的工程团队,要把果实分割模型落到采摘、计数、测产环节,需要先拿现成数据验证流程。第三类是刚入门实例分割的开发者,手头缺一个干净的单一类别数据集练手,这份数据规模适中、标注结构清晰,拿来跑通PyTorch或YOLO的完整训练推理链路非常合适。
第一类和第二类读者,拿到数据后切忌先盲目训练。先想清楚自己交付的是检测框还是分割掩码,是离线统计还是实时推理,模型部署GPU还是边缘设备。这些前置决策会直接影响后续所有操作。目录结构、图像分辨率、标注文件在标题里已经给了,剩下的工程问题需要按任务重新设计,而不是直接套默认模板。
2. 2766张图的划分数字里,藏着哪些工程判断
2.1 2261与505这个82开,对实例分割意味着什么
标题里的数字很有意思:2261加505正好等于2766,和总数对得上,说明这份数据在发布时已经做好了训练和验证的划分,用户拿上手不需要再自己切一遍。2261除以2766约等于0.817,标准的8比2比例。对单类别的实例分割来说,2261张训练图是个合格的起步规模:模型可以在这些图上学会茄子轮廓的基本形态,505张验证图也足够给出稳定的mAP估计。相比动辄上万张的图像分类数据,实例分割标注昂贵,两千多张已经是不小的投入。
不过要清醒看到,这个规模对实例分割来说并不宽裕。推理阶段最难处理的遮挡目标、小目标、高光反光,都需要大量极端案例喂给模型。假设每张图平均有三到五个实例,那么实际训练样本量在6800到13800个mask左右;如果要做高召回率的采摘系统,这个量级肯定要扩充。我自己的经验是,单类别实例分割低于五千个mask时,模型很容易过拟合到采集现场的光照和背景上,换个温室性能就掉。2261张只是"能开始"的门槛,不是"够用"的保证。
2.2 没有测试集的常见应对:自己留一档
标题里只写了训练集和验证集,没有测试集,这是公开数据集的常态。验证集用于训练中的早停、超参选择和模型保存;严格意义上,测试集要等实验全部结束以后再碰一次,用来评估最终泛化能力。如果一直拿验证集反复调参,最后得到的是一个对验证集过拟合的数字,不是真实泛化水平。
实操方向,我建议把现有验证集再纵向切一次。比如505张验证图里留出100到150张作为test set,剩下的继续当日常验证。另一个做法是K折交叉验证:把2261张训练图分成五折,轮训每轮用四折训练、一折验证,最后取mAP均值。这两种方案都能降低调参时的数据偷看风险。切分时注意按图像ID切而不是按实例切,同一张图里的不同mask不能出现在两个集合中,否则会构成数据泄漏。
2.3 同类数据下,分布不均比数量不足更致命
很多人拿到数据只关心总数,忽略分布。实例分割的分布问题至少有三个维度:第一是生长阶段,幼茄细长、成茄粗短,轮廓差异很大;第二是光照条件,温室顶光、逆光、人工补光下果实表面纹理和阴影完全不同;第三是遮挡程度,单果、双果重叠、多果成串,mask的完整度天差地别。
如果数据随手一划,训练集全是单果清晰图,验证集塞满重叠成串的图,训练曲线会非常难看——训练loss一路下降,验证mAP反复震荡上不去。正确做法是先做实例分布统计,画每张图的实例数和mask面积直方图,再做分层采样,保证训练集和验证集都有相似比例的清晰图、遮挡图、小目标图。这一步虽然烦琐,但能省掉后面至少十次无效调参时间。调参救不回来的分布问题,只有数据工程能解决。
3. 分辨率1280x1706和lab标注,决定了后续所有操作
3.1 为什么这个分辨率对mask边缘精度很重要
1280x1706约等于218万像素,接近3比4的竖向构图,这个画幅在农学数据采集中很常见,通常来自手持设备或固定机位竖拍。高分辨率对实例分割的意义在边缘细节:茄子的萼片锯齿、果面高光、果柄连接处,这些在低分辨率下会被糊成一片的区域,1280宽度下还能保留轮廓形状。分割掩码的质量本质上取决于边缘的像素分类准确度,输入分辨率越低,边缘越容易被压缩成几个像素,最终mask的IoU就上不去。
但高分辨率也不能直接扔进网络。大部分实例分割框架默认输入是方形,比如640x640或1280x1280。如果无脑resize成640,1280x1706的长边被压到640,短边只有约480,信息损失很大。更合理的是用letterbox等比缩放,保持长宽比,剩余区域填灰边,或者直接设置imgsz=1280训练。1280输入显存需求大概是640的四倍,8GB显卡几乎跑不动大模型的训练,所以这个分辨率既是精度资产,也是显存负担,要根据硬件条件取舍。
3.2 三种主流的实例分割标注格式,拿到手先认格式
标题末尾的"lab"我按行业习惯理解为label标注文件。实例分割数据集的标注格式,常见的有三种。第一种是COCO JSON,整套标注集中在一个annotations.json里,每个实例有segmentation字段,存着多边形顶点坐标或RLE编码。第二种是YOLO分割格式,每张图对应一个txt,行格式是class_id x1 y1 x2 y2 ...,所有坐标除以图像宽高做归一化。第三种是VOC风格,每张图用XML描述目标框和分割文件路径,分割掩码通常单独存成PNG。
拿到数据的第一件事不是加载训练脚本,而是打开标注文件确认格式归属。我见过太多人在网上下了一份数据,没仔细看格式,直接把JSON喂给YOLO,报错以后开始怀疑环境坏了。先认格式,再选转换工具,这两步顺序别颠倒,能节约至少半天。另外,如果标注文件里同时存在一个实例的mask和bbox,要确认引擎在转换时是否会自动从mask重新计算bbox;有些框架允许两者不一致,这在后处理阶段会带来奇怪行为。
3.3 训练之前必做的一次可视化审核
拿到标注之后,无论如何都要先做一次可视化。用Python读取标注文件,把mask或者polygon画在原图上,随机抽五十张人工扫一遍。重点看三类问题:掩码是否紧跟果实边缘,有没有把背景叶片圈进去;同一个果实是否被重复标注成两个实例;是否存在面积极大或极小的离群mask,比如只有几十个像素的噪声标注。
这一步重要是因为实例分割训练一旦跑起来,标注错误会被模型原样学走,甚至推理时放大。边界标得马虎的mask会让seg_loss长期降不下去;漏标的果实会直接教模型"看到它也不要输出"。可视化审核用OpenCV加几十行脚本就能完成。如果发现明显系统性问题,比如所有mask都比真实轮廓偏大一圈,回退给数据端修正,比花两周调模型参数划算得多。先数据后模型,这是我在多个数据集项目里验证过的原则。
3.4 从标注规模看这份数据集的成本结构
2766张图听起来不多,但乘以每张图的实例数就是另一回事。假设平均每张图3到5个茄子实例,总标注量在8300到13800个mask之间。熟练的标注人员用多边形工具给每个果实画贴合轮廓的mask,一分钟到几分钟不等,再加上审核、修改、格式转换,整份数据的标注成本不低。这从侧面说明,这份数据的价值不在"量大",而在于每个mask都经过了质量控制,可以直接作为任务基线。
反过来,这也提醒我们复用时别浪费。很多人数据增强时随手把图像上下翻转、随机裁剪,却从不检查裁剪后mask是否出界、翻转是否改变果实形态。与其花大价钱新增标注,不如把现有mask用足。成本结构决定了这类数据集适合迭代式使用:先跑基线,再用模型挑困难样本,最后只针对困难样本做定向标注,每一份标注预算都花在刀刃上。
4. 用这份数据把模型真正跑起来:从格式转换到训练曲线
4.1 框架选型:YOLOv8-seg为什么是我默认的第一选择
实例分割框架选择不少。Detectron2稳定、学术生态好;MMDetection功能全但配置层级深;PaddleDetection对国产硬件友好。如果让我给一个默认第一选择,我推YOLOv8-seg。理由有三个:第一,从标注到训练到部署的链路最短,命令行和Python API封装得很顺手,对刚接触分割的人最友好;第二,模型家族从n到x不同规模,单类别数据量不大,选s或m规模足够;第三,导出ONNX和TensorRT路径成熟,从实验到落地不用重写代码。
不是其他框架不好。复现论文里Mask R-CNN的基线指标,Detectron2的config体系更贴近学术习惯;做复杂的多阶段项目,MMDetection模块化带来灵活度。就这份单类别茄子数据来说,复杂度不高,YOLOv8-seg用最小的认知成本帮我把流程跑通,等流程通了再按需换框架,成本低得多。
4.2 把标注转成训练格式,避开坐标归一化的坑
假设拿到的是COCO JSON,第一件事是转成YOLO分割的txt格式。核心逻辑不复杂:读取每个实例的segmentation多边形坐标,所有坐标除以图像宽高得到归一化坐标,然后拼成class_id加坐标串写进txt。最容易出错的点有两个:一是原始polygon坐标可能是浮点,二是某些标注工具有多环多边形(外环加内环),转换时只取了第一个环,导致部分mask变成空心。写转换脚本时最好加校验:每个txt坐标数量是否为偶数,坐标值是否落在0到1之间。
下面给一个COCO JSON转YOLO分割txt的参考脚本,我处理类似数据集时基本是在这个框架上改的:
import json import os def coco_to_yolo_seg(json_path, out_dir): with open(json_path, "r", encoding="utf-8") as f: coco = json.load(f) img_map = {img["id"]: img for img in coco["images"]} cat_map = {cat["id"]: cat for cat in coco["categories"]} for ann in coco["annotations"]: img = img_map[ann["image_id"]] cat = cat_map[ann["category_id"]] w, h = img["width"], img["height"] seg = ann["segmentation"][0] points = [] for i in range(0, len(seg), 2): x = min(max(seg[i] / w, 0.0), 1.0) y = min(max(seg[i + 1] / h, 0.0), 1.0) points.append(f"{x:.6f} {y:.6f}") txt_path = os.path.join(out_dir, img["file_name"].replace(".jpg", ".txt")) with open(txt_path, "a", encoding="utf-8") as g: g.write(f"{cat['id'] - 1} " + " ".join(points) + "\n")脚本里有个细节:COCO的category_id通常从1开始,YOLO的class_id从0开始,所以转换时要减1。这份数据只有一个eggplant类别,简单处理即可。跑完脚本后再做反向校验:随机选几张原图,把txt坐标换算回像素坐标画在原图上,和原始标注对比,确认坐标没有平移或缩放。
4.3 训练参数这么定,第一轮别乱调
数据准备好以后,训练命令可以很简洁。以YOLOv8-seg为例:
yolo segment train data=eggplant.yaml model=yolov8s-seg.pt epochs=100 imgsz=1280 batch=8eggplant.yaml里写清楚train和val的图片路径,以及nc=1、names=['eggplant']。第一轮训练我建议全部用默认超参,最多改三个参数:epochs设为100;imgsz按显存情况设640或1280;batch按显存上限来。有人一上来把学习率从0.01改成0.001,觉得改一下就能避免发散,其实大多数情况只是浪费第一轮时间。先让默认参数跑一轮,拿到baseline,再做后续实验,才符合科学迭代的顺序。
imgsz的选择依赖硬件。8GB显存跑1280的yolov8s-seg基本不可能,选640更实际;14GB以上显存,1280值得投资,从640提到1280对边缘精度和小目标召回率的提升非常直观。batch尽量往大了开,显存允许就开16,梯度估计更稳定,收敛速度通常更快。预训练权重yolov8s-seg.pt建议保留,单类别任务从COCO权重起步,会明显缩短训练时间。
4.4 训练过程看曲线,比等mAP更早暴露问题
训练开始以后,不要只盯每个epoch结束打印的mAP50和mAP50-95,中间的loss曲线才是诊断工具。YOLOv8-seg训练时,控制台会输出box_loss、seg_loss、cls_loss、dfl_loss四个指标。对分割任务来说,seg_loss是关键,它衡量mask预测和标注mask的差异。如果seg_loss在某个epoch后不再下降甚至反弹,优先怀疑两种可能:一是训练集里出现大量标注不一致的mask,二是当前学习率下模型容量到了瓶颈。
还有一个常见情况:训练曲线一路下滑,但验证mAP长期不动。这种"训练好、验证拉胯"的情况多半是过拟合信号。解决思路不是继续加epoch,而是增强数据增强强度、提高权重衰减,或者换容量更小的模型。如果验证mAP在末尾还在缓慢上升,说明epoch可以再加,但每次加三五十个,不要翻倍。训练结束后,best.pt和last.pt都保留,后者在继续训练或做知识蒸馏时可能比best.pt更好用,因为它没被验证集上的偶然波动打断。
5. 实测阶段最容易翻车的三件事
5.1 绿色萼片把模型带偏的误检
茄子果实顶部有一圈绿色萼片,和叶片同色系,这是最容易引入误检的地方。模型如果只靠颜色纹理区分前景,很容易把带叶片边缘的区域也当成茄子轮廓,推理结果里出现长条形的绿色噪声mask。温室环境下尤其明显,背景叶片密集,绿色像素占比极高,模型稍微放松一点就会把叶片归为果实。
我处理这类问题的经验分三层。第一层在标注上统一规则:mask是否包含萼片,整个数据集里必须保持一致,不能有的标萼片有的不标,模型学边缘会被不一致搞乱。第二层在增强上,加HSV色彩抖动和对比度扰动,让模型不再依赖固定的绿色区间,用mosaic把背景多样性拉高。第三层在推理上,把conf阈值从默认0.25提到0.4左右。宁可漏少量低置信度果实,也别让背景噪声混进计数。调阈值成本极低,收益往往立竿见影。
5.2 果实重叠后,mask只露半边脸的漏检
茄子成串生长意味着前果挡后果是常态。对实例分割来说,被遮挡的果实通常只能标注出可见部分,模型学到的也是露出多少就分割多少。但如果遮挡比例太高,比如露出区域不到整个果实的20%,模型经常会直接判定为背景。问题的本质是训练集里极端遮挡案例太少,模型没见过只露一截也要输出mask的情况。
想提升这部分召回,建议先统计训练集里不同遮挡程度下的实例占比,极端遮挡案例不足就人为构造:用copy-paste增强把切出来的茄子mask叠到其他果实附近,模拟不同遮挡比例。另一个思路是配合视频帧连续信息,用前后帧互相补。单帧模型的上限摆在那里,压不出新潜力。如果做采摘系统,宁可多一个半遮挡mask,也比漏掉果实好,机械臂后续还有末端定位环节可以兜底。
5.3 缩小图像后小目标直接从mask里消失
我用640x640训过一轮,发现最明显的问题是远处枝条上的小果,预测结果里几乎不出现完整mask,要么被忽略,要么只输出一个几个像素的小点。这个现象在高分辨率数据集里非常典型:原图1280x1706,一串小型幼果可能只占整张图的2%,缩放到640后对应区域只剩十几像素宽,轮廓细节被彻底压平,分割头无从下手。
针对小目标,优先级最高的方案是把imgsz从640提到1280,代价是显存和训练时间翻倍,收益是小目标召回率直观提升。硬件实在扛不住,就做patch训练:原图按网格切成若干子图,每个子图独立训练和推理,推理时再把结果拼回原图坐标。这个方案绕过显存瓶颈,代价是多一次或多次切片操作。对小目标占比高的农业数据集,强烈建议先看mask面积分布再定训练分辨率,这个习惯能帮你少走几轮弯路。
5.4 外加一个单图验证流程,排查起来更顺手
训练结束别急着看汇总指标,先把推理脚本搭好,才能针对具体图像定位问题。最小可用的单图验证流程:加载best.pt,指定一张验证图像,设置conf和iou阈值,保存带mask的可视化结果,同时打印该图检测到的实例数量、平均置信度和mask像素数。
from ultralytics import YOLO model = YOLO("runs/segment/train/weights/best.pt") results = model.predict( source="val_sample.jpg", conf=0.4, iou=0.5, save=True, save_txt=True, show=False, ) for r in results: print("instances:", len(r.boxes)) print("conf:", r.boxes.conf.tolist()) print("mask areas:", r.masks.xy.sum(axis=1) if r.masks is not None else None)有了这组输出,配合可视化图像,就能快速判断当前问题是误检、漏检还是阈值太严。如果conf都在0.8以上但count远低于真值,说明模型漏检,和阈值无关,该查小目标和遮挡;如果conf在0.3到0.5的预测很多但都是噪声,该提阈值或修标注。这个调试闭环比反复盯mAP曲线高效得多。
6. 从能跑的模型到田间能用,还差哪几步
6.1 用主动学习做数据迭代,比闷头加数据更划算
如果2766张图只是起点,后续扩充数据最划算的方式是主动学习,而不是随机采集标注。流程很简单:拿当前模型推理一批未标注的新图像,按置信度排序,优先挑两类样本——置信度特别低的,模型觉得不像茄子,很可能是没见过的形态;置信度在0.4到0.7之间徘徊的,模型拿不准,往往是难例富矿。把这些样本交给标注人员修正,效率和随机标注比会高很多。
我实践下来,三轮主动学习迭代后,数据规模从2766涨到四五千张,模型mAP的提升通常比相同数量随机采样高两三倍。原因是农业现场的长尾分布太明显,强光、逆光、密集串果、雨滴附着这些极端情况在随机采样里占比很低,主动学习会主动把它们筛进训练集。数据迭代的终点不是图够了,而是难例覆盖率够了。
6.2 针对温室光照和果实密度的增强清单
农业场景的数据增强不能照搬通用数据集那套。我的建议清单如下:色彩层面,HSV色相、饱和度、亮度扰动必须开,温室光照一天之内变化很大,模型学死某个亮度范围,换个天气就崩。空间层面,mosaic和copy-paste是两大利器,mosaic能把四张图拼成一张,提高密度多样性;copy-paste可以把分割出来的茄子实例贴到其他背景上,低成本模拟不同密度和遮挡组合。尺度层面,随机缩放要小心,原图已经是高分辨率,过度缩放会把小目标弄丢,建议控制在0.6到1.2倍之间。
有一个增强禁忌需要提醒:后续任务如果要做机械臂抓取点估计或果实朝向判断,水平翻转和旋转要谨慎。翻转会改变果实相对茎秆的方向,模型学会翻转后依然能分割,但对方向敏感的下游模块是误导。通用分割任务无所谓,精密农业任务要先确认下游需不需要方向信息,再决定翻转增强的开关。
6.3 部署到边缘设备时的取舍思路
从实验模型到田间部署,第一个要砍的是模型规模。单类别分割任务不需要yolov8x-seg这种大模型,yolov8n-seg或yolov8s-seg经过蒸馏后通常够用。实测里n模型mAP比s模型低两三个点,推理速度能快一半以上,对实时采摘或计数系统来说非常划算。第二个是推理后处理,把置信度阈值、NMS阈值、最大检测数量在导出时固定住,运行时不做动态调整,能省不少CPU算力。
部署链路我一般走:训练好的best.pt导出ONNX,再做TensorRT的FP16量化,输入分辨率固定为640或1280的letterbox规格。在边缘设备上,FP16量化后的n模型,对640x640输入的推理延迟通常能压到实时水平,分割帧率比FP32提高一到两倍。量化带来的精度损失对单类别果实分割基本可接受,mask IoU掉一两个点但推理速度翻倍,实际项目里是划算的。如果做完全部优化还是不够快,再考虑裁剪输入分辨率或改用patch推理。每砍一档都要回头验证下游任务指标,别只看单帧延迟。
这篇文章写到最后,我还是想再把那个老观点强调一遍:实例分割数据集的价值,一半在标注,一半在怎么用。我在实际跑这份茄子数据时踩过最亏的一次坑,是把所有图像统一拉伸成正方形再训练,后来一查,小果实的mask面积整体缩水了将近三分之一。改成按原图比例做letterbox之后,同样的模型,mask IoU直接涨了约四个点,这个细节论文里通常不会写。最后再分享一个小技巧:训练完别急着删last.pt,保留一份开启测试时增强的推理结果做对照。有时候标准推理看着不行的图,TTA能拉回来不少,尤其对遮挡严重的果实串,留着这份对照能帮你判断到底是模型不行,还是推理配置没给到位。