干农业视觉这几年,有个特别深的感触:真正能落地的智慧农业项目,往往不是把最新最重的模型堆上去,而是把一组看似不起眼的工程问题抠到极致。番茄成熟度识别就是最典型的例子——它听起来简单,好像就是让计算机判断果子红没红,但实际做起来,光线不均、果实遮挡、成熟度边界模糊、采摘点定位、边缘设备推理速度,每一个环节都能让人磨掉一层皮。这篇文章围绕我在某智慧农业模拟项目里的完整实践展开,把番茄成熟度识别和采摘点识别的数据方案、模型选型、训练细节、部署流程,以及过程中踩过的坑,原原本本整理出来。适合正在做农业视觉项目、或者准备把自动驾驶采摘落到温室大棚里的朋友参考。
1. 项目背景与需求拆解
1.1 番茄成熟度识别在智慧农业里到底解决什么问题
先聊一个很现实的问题:番茄采摘目前依然高度依赖人工,而人工成本在大棚种植里能占到三成以上。采摘机器人要想替代人,第一步不是机械臂多灵活,而是“眼睛”得先看清楚:哪些果子能摘、哪些还没熟、果梗在哪个位置、剪切点怎么选。番茄成熟度识别就是整个采摘自动化系统最前端的决策模块,它直接决定机械臂要不要动作,以及动作落在哪。
成熟度识别不是一个单点问题。同一串番茄上常常有绿果、半红果、红果混在一起,人工采摘时分拣是顺手的事,但机器不行。模型如果只把“红果”检出来,就无法区分转色期和过熟期,摘到过熟果容易捏烂,摘到半红果又会降低商品率。所以实际项目里,我通常把成熟度分成至少四类:青果、转色果、红熟果、过熟果,再配合采摘点定位,让机器人只对满足上市标准的红熟果动手。
1.2 拆解任务:检测、分类、定位三位一体
很多人第一次接触这个项目,以为只要训练一个“成熟度分类器”就够了,但放到采摘场景里完全不是这样。机械臂需要的是“哪里有什么”,而不只是“这张图是不是红果”。我把任务拆成三个互相嵌套的子目标:
- 果实检测:在一张复杂背景的番茄图像中找到每一个果实的包围框,并给出置信度。
- 成熟度分类:对每个检测到的果实判断等级,通常直接作为检测框的类别输出。
- 采摘点定位:在成熟果实的果梗位置找到适合剪切的点,也就是离层附近的坐标。
这三个子任务在算法上可以合并,也可以拆分。我的做法是检测和成熟度分类合并到一个模型里,采摘点识别单独用分割模型来做。这样做的好处是:检测模型专注于“果实有没有、在哪儿、什么颜色”,采摘点模型专注于“果梗结构在哪”,互不干扰,后期也容易分别迭代。
1.3 为什么不用传统CV方案
有人会问,番茄成熟度不就是颜色问题吗?用HSV空间阈值切一下,红色区域面积算一算,不是更轻量吗?我在项目初期也试过这个方案,实验室里效果还不错,但一进大棚就崩了。原因很现实:白炽灯、自然光、遮阳网会造成不同的色偏,叶片阴影打在果实上会让局部颜色失真,两个果实重叠时阈值分割会把一个区域连成一片,根本数不清有几个果。
传统视觉方案适合高度受控的工业环境,而农业场景是典型的高动态环境。深度学习能够利用纹理、形状、局部上下文来判断成熟度,而不是死板地看单一颜色通道。更重要的是,检测网络可以同时输出框、类别和置信度,直接衔接机械臂的决策逻辑,这是传统阈值分割没法比拟的。所以这个项目从一开始就定了深度学习的路线。
2. 数据准备与标注策略
2.1 采集数据:光线、角度、遮挡全都要覆盖
模型泛化能力的上限,在数据采集阶段就决定了。千万不能只在天气晴朗的上午去大棚拍几十张照片,那样模型一遇到阴天或傍晚就会“翻车”。我在采集阶段会刻意覆盖几个维度:时间上覆盖早中晚;天气上尽量覆盖晴、阴、雨后;角度上包括俯拍、平拍、侧面拍,模拟机械臂不同的观察视角。
实操中我建议用摄像头录视频再抽帧,而不是捧着相机一张张按快门。针对同一片番茄区域,缓慢移动镜头录上几分钟,然后每间隔5到10帧抽一张。这样可以在短时间内拿到大量背景和遮挡有差异的样本,而且相邻帧之间的相似度可控。一组视频抽下来能出几百张图,效率比人工拍摄高很多。
数量上,我自己的经验是:第一版模型至少要1500到3000张标注图,覆盖不同成熟度组合。如果想要mAP稳定在0.9以上,最好把数据量扩充到5000张以上。千万别指望用几百张图训练一个能下地干活的模型,农业场景的复杂程度远超想象。
2.2 成熟度等级怎么定义才不会被模型搞糊涂
很多人认为成熟度分级很简单:红的就是熟,绿的就是生。但真到标注时你会发现,有一大堆苹果不红不绿,甚至半面红半面青,标成哪一类全凭感觉。如果标注标准不一致,模型训练出来的边界就会飘,这是后期算法精度提不上去的常见原因。
我在项目里和标注伙伴统一了以下规则,如果你也在做同类项目,可以直接参考:
| 等级 | 外观特征 | 采摘动作 |
|---|---|---|
| 青果 | 果面基本为绿色,无红色区域 | 不采摘 |
| 转色果 | 果面出现红色但占比不足70% | 不采摘或延迟采摘 |
| 红熟果 | 红色占比约70%到90%,颜色鲜亮,果实饱满 | 优先采摘 |
| 过熟果 | 果面深红或局部发暗、皱缩、有软化迹象 | 通常不采摘 |
这里的关键是“红色占比”这个量级标准。标注前给每人发一张对比图,把四个等级的典型样本打印出来,遇到边界情况统一归到更保守的那一类。宁可把临界果标成转色果,也不要模棱两可,否则模型会在红熟和转色之间反复横跳。
2.3 采摘点怎么标:小目标标注的注意点
采摘点识别是很多教程容易跳过、但实际项目里绕不开的环节。番茄的采摘点通常位于果梗与果实连接的离层附近,是一个很小的区域。如果用检测框来标,框稍微大一点就会把整个果实包进去,导致模型分不清“果梗框”和“果实框”。我用的是实例分割标注,把果梗和果实分别用多边形画出来,尤其要把果梗的完整轮廓标注清楚。
标注采摘点时还有一个小技巧:不要只标一个像素点,而是把果梗mask画完整,后处理时再通过几何运算提取剪切点。如果只看二维图像,离层点经常被叶片挡住,模型很难直接从单帧看到完整结构。所以标注时尽量把可见的果梗部分都画出来,哪怕被叶子遮挡一部分,只要轮廓连贯,模型就能学到结构性规律。
2.4 数据增强:别把番茄颜色都增强没了
数据增强能显著提升泛化能力,但农业场景里有一个特别容易踩的坑:过度做色彩增强,把番茄本身的颜色语义破坏了。比如把色调随机偏移拉满,红色变成紫色,模型就可能学到“颜色怪异的就是番茄”,而不是真正学会番茄的纹理和形态特征。
我建议两类增强组合使用。第一类是几何增强:水平翻转、随机旋转、轻微缩放,用来提升对不同拍摄角度的鲁棒性。第二类是光照增强:亮度抖动、对比度微调、加少量高斯噪声,用来模拟不同时间点的自然光差异。色彩增强可以做,但幅度要小,尤其是色调偏移度的上限要控制住,否则成熟度分类的类间差异会直接被增强搞乱。
另外,增强尽量用在线增强,也就是训练过程中逐batch实时生成,不要提前离线生成几万张增强图。在线增强不占磁盘空间,而且每个epoch都会生成略微不同的样本,等于变相扩大了训练集。
3. 模型选型与训练细节
3.1 网络选型:检测、分割、关键点怎么组合
对于番茄成熟度识别,主流做法有三种,先看对比:
| 方案 | 精度表现 | 推理速度 | 工程复杂度 | 适用情况 |
|---|---|---|---|---|
| 两级网络:检测成熟度 + 分割采摘点 | 高 | 中等 | 中等 | 实际采摘项目首选 |
| 单检测网络多分类 | 中高 | 快 | 低 | 只做成熟度统计,不采摘 |
| 端到端关键点检测 | 中 | 较快 | 高 | 果实稀疏、遮挡少 |
我在这个项目里选择的是“两级网络”方案:第一级用YOLOv8s做果实检测和成熟度分类,第二级用YOLOv8-seg做果梗和果实的实例分割,再从分割结果里计算采摘点坐标。第一级输出的是“红熟果在图像哪个位置”,第二级输出的是“这个果实的采摘点在哪里”。分开训练的好处是,成熟度分类的问题不会因为果梗标注噪声而带偏,采摘点模型也不需要兼顾颜色判断,职责单一、训练稳定。
模型规模上,YOLOv8s属于轻量级,在边缘设备上跑得动,精度又比nano版本高不少。如果你的目标是高分辨率大图、小目标密集,也可以换成YOLOv8m,但推理时耗会明显上升。我建议先从s版本开始,跑通整个链路后再根据精度短板决定要不要扩大模型。
3.2 训练参数与评估指标
训练之前要先把数据集格式整理成YOLO风格。果实检测部分,每个标注的类别就是成熟度等级,比如0对应青果、1对应转色果、2对应红熟果、3对应过熟果。训练集、验证集、测试集建议按7:2:1划分,划分时要按场景而不是按单张图随机划分,不然同一串番茄的连续帧会同时出现在训练和验证集里,评估结果虚高。
训练命令很简单,核心是几个参数:
yolo task=detect mode=train model=yolov8s.pt \ data=tomato_fruit.yaml \ epochs=200 batch=32 imgsz=640 \ lr0=0.01 optimizer=AdamW \ patience=20 amp=True关键参数里,imgsz=640是性价比比较高的输入分辨率。如果小目标漏检严重,可以提到800或960,但训练和推理耗时都会上涨。lr0我习惯从0.01开始,配合余弦退火往下衰减。patience=20的意思是连续20个epoch验证集指标不提升就早停,既防过拟合又省时间。
评估不能只看平均精度。我最关注的是两个数:mAP@0.5和mAP@0.5:0.95。前者反映的是果实级检出能力,后者对定位框质量更严格。此外一定要看成熟度混淆矩阵,重点检查转色果和红熟果之间是不是经常互相认错。如果混淆集中在相邻等级,说明标注边界不一致或样本分布不均,单纯调参很难解决。
3.3 采摘点识别模型的实现细节
采摘点模型我选择实例分割,而不是目标检测。原因很简单:果梗是小目标,检测框很难精确贴合,而且机械臂需要知道果梗的走向,一个框给不了这么多信息。分割模型输入图像,输出“果实”和“果梗”两类mask,后处理再提取采摘点。
具体做法是:把分割模型预测出的果梗mask和果实mask做交集分析,找到果梗与果实相接的区域。这个相接区域的几何中心,就是最理想的剪切点。如果果梗被叶片分割成几段,先用形态学闭运算把断裂的小段连接起来,再取连通域范围内最靠近果实边缘的位置。
我这里放一个简化版后处理逻辑,思路可以直接抄:
import numpy as np # pred_mask: 模型输出的多类别mask, 1=果梗, 2=果实 stem_mask = (pred_mask == 1).astype(np.uint8) fruit_mask = (pred_mask == 2).astype(np.uint8) # 腐蚀果实边缘,取果梗与果实交界区域 fruit_edge = fruit_mask - cv2.erode(fruit_mask, np.ones((5, 5), np.uint8)) joint_area = stem_mask * fruit_edge # 交界区域质心 if np.sum(joint_area) > 0: ys, xs = np.nonzero(joint_area) pick_point = (int(xs.mean()), int(ys.mean())) else: # 兜底:取果梗mask质心 ys, xs = np.nonzero(stem_mask) pick_point = (int(xs.mean()), int(ys.mean()))这个后处理在部署阶段用C++重新实现时也很直接,无非就是形态学运算加质心求解。关键要记住:采摘点不要只依赖单帧图像,最好在机械臂靠近后做一次二次确认。第一次大范围搜索用检测模型,第二次近距离、高分辨率确认用分割模型,这样能显著降低误剪风险。
3.4 多任务训练与联合调优
如果你想把成熟度和采摘点塞进同一个模型,也不是不行,但工程上要非常小心。多任务模型的最大问题是两个任务的梯度可能互相干扰:果梗分割需要精细的局部结构信息,成熟度分类需要全局颜色和纹理信息,强行共享一个特征提取器,早期训练很容易出现一个任务好、另一个任务崩的现象。
我的建议是:项目初期保持双模型独立,先把两条链路分别验证好,再考虑用知识蒸馏或多任务微调合并。什么时候该合并?当你发现推理耗时成为瓶颈,而且两个单模型精度都已经接近上线标准时,才值得在主干的最后几层做参数共享。否则不要为了“炫技”增加排查难度。
4. 部署与实操落地
4.1 边缘设备选型与模型转换
采摘机器人不可能在温室里挂一台高功耗服务器,模型最终要跑在边缘设备上。我用的是一块带GPU的低功耗开发板,算力大约在几十TFLOPS级别。选型时重点关注三点:GPU算力、内存带宽、功耗限制。算力不够会导致推理实时性不达标,内存不够则模型转换时会频繁OOM。
模型训练用的是PyTorch框架,部署时需要先把权重导出成ONNX,再转成目标设备能加速推理的格式。导出的关键参数是固定输入尺寸,不要保留动态batch或动态尺寸,否则在边缘设备上会触发很多兼容性问题。常用导出命令如下:
yolo export model=best.pt format=onnx imgsz=640 fp16=True导出ONNX后,再用边缘设备厂商提供的推理引擎做一次转换,通常可以开启FP16推理。FP16的精度损失在农业场景里几乎可以忽略,还能把显存占用砍半,强烈建议默认开启。
4.2 推理性能优化:从300ms到80ms
刚跑通整个流程时,我在边缘设备上实测到单帧推理要300ms左右,这对采摘来说太慢了。后来做了三步优化:第一,把输入尺寸从960降到640,小目标漏检率稍微增加,但速度提升非常明显;第二,开启FP16推理,耗时降低一半左右;第三,把预处理、推理、后处理三段放到三个线程流水线执行,而不是串行等待。
优化后,检测加分割的完整pipeline能控制在80ms量级。这里有一个过来人的经验:不要一上来就追求极致帧率,先保证帧间稳定和抖动小。采摘机械臂不要求每秒处理几十帧,它通常需要的是一个稳定、低延迟的检测结果,80ms级别完全够用。
# 检查导出后ONNX的推理时延 onnxruntime_benchmark \ --model tomator_v1.onnx \ --input_size 640 \ --fp164.3 机械臂联动与坐标变换
算法输出的采摘点是一组二维像素坐标,而机械臂需要的是三维空间坐标,中间的桥梁就是相机标定。在采摘机器人里,相机既可以固定在机械臂外部(eye-to-hand),也可以安装在机械臂末端(eye-in-hand)。前者视野稳定、标定相对简单;后者可以靠近观察细节,但标定和补偿逻辑更复杂。
我用的是eye-to-hand方案,深度相机输出每个像素的深度值,再结合相机内参和手眼标定矩阵,把像素坐标投影到机械臂基座坐标系。要注意的是,如果采摘点附近有叶片遮挡,深度图会出现空洞,这时不能直接把无效深度值当成0,最好用前一帧的深度值或周围可信像素插值。保险起见,我会让机械臂先运动到一个预抓取点,再开启近距离相机做二次确认。
4.4 先离线,再在线的部署路线
如果项目是第一次从实验室走到温室,强烈建议采用“先离线、再在线”的渐进路线。离线阶段,固定相机位置,每隔一段时间采集一组图像,批量跑一遍检测和分割,输出一张带有成熟度标签和采摘点标记的采摘地图。人为检查这些标注是否合理,及时发现问题。
在线阶段再把模型接到机械臂控制流程里。先只输出警告和推荐动作,人不参与采摘;确认稳定后再让机械臂按模型结果执行。这样即使模型在某个光照条件下失手,也只是在地图上多个误检点,不会直接把机械臂带偏。
5. 常见问题与排查技巧实录
5.1 成熟度错判:转色果和红熟果总打架
这是最多人遇到的一个问题。现象是验证集整体精度还行,但转色果和红熟果的混淆矩阵里错误特别集中。原因通常有两个:一是标注标准没有统一,同一个果面红色占比边界,有人标转色有人标红熟;二是现场白平衡偏色,导致模型学到的“红色”和实际红熟果的颜色不一致。
解决办法分两步。第一步,清洗标注,把红色占比在65%到75%之间的样本全部拿回来重新审查,统一成同一等级。第二步,训练前对图像做白平衡校正,可以在采集区域放一张标准色卡,推理时先用色卡矫正后再进模型。经过这两步,成熟度错判的比例能降一半以上。
5.2 小目标漏检和密集果实互相遮挡
温室里的番茄是一串一串长的,很多果实在画面里只有几十个像素大小。YOLOv8s在640分辨率下对这种小目标漏检率偏高,尤其当叶片遮挡把果实露出面积压到很小的时候。
我试过有效的方案是SAHI(切片辅助推理),把原图切成若干块重叠patch,每个patch单独推理,再合并结果。这样做能把小目标的有效分辨率放大,漏检率明显下降,代价是推理时间变长。另一个更简单的办法是改变相机安装角度,让目标在画面中占更大面积,近距离采摘场景往往比远距离俯拍更可靠。
5.3 采摘点在推理时漂移
采摘点漂移的典型表现是:模型明明认出了果梗,但后处理算出来的点落在果实中心附近,机械臂如果照着剪,很容易戳伤果实。原因多半是果梗mask断裂,或者果梗与果实mask重叠区域过大,导致质心被果实边缘拉偏。
我在后处理里加了两道保险:第一,对果梗mask做闭运算,断开的小段重新连接;第二,计算候选点后做一次约束检查,如果候选点落在果实mask内部太深的位置,就用果梗mask靠近果实一侧的边缘点替代。实测下来,采摘点的稳定性提高很多。
5.4 换一个温室就失效
模型在A棚训练得很好,拿到B棚一测试,漏检和错判立刻上升。原因就是数据分布迁移:不同大棚的品种、光照、栽培方式、果实颜色深浅都不一样。
我处理这个问题的方法是“少量样本快速微调”。到了新温室后,不要求模型立刻全量重训,而是先拍几百张图像,人工快速标注一遍,然后在原有权重基础上用很小的学习率做微调。通常几十个epoch就能恢复精度。这种做法比从零训练省时,也比直接裸用原模型靠谱得多。
5.5 常见问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 红熟果大量漏检 | 小目标占比高 | 提高输入尺寸,或使用切图推理 |
| 转色果和红熟果混淆 | 标注标准不统一,白平衡偏差 | 重新清洗标注,增加色卡校正 |
| 采摘点漂移 | 果梗mask断裂,后处理质心偏移 | 形态学闭运算,增加约束规则 |
| 白天精度高,傍晚下降 | 光线变化大,数据覆盖不足 | 增加傍晚和阴影场景的数据 |
| 模型在新棚精度骤降 | 数据分布迁移 | 用少量新数据快速微调 |
| 推理时延过高 | 没有开启FP16,后处理串行 | 开启FP16,优化流水线 |
6. 项目经验总结与后续扩展
6.1 这套方案能迁移到哪些同类场景
番茄成熟度识别只是智慧农业视觉里的一个样板。整个“目标检测 + 等级分类 + 采摘点分割”的pipeline,可以迁移到很多同类作物上。比如辣椒的成熟度变化主要是由绿转红,草莓的成熟度看表面颜色和种子形态,葡萄的采摘点则是果梗位置,这些项目的数据组织和模型选型思路都非常相似。
差别在于采摘点的物理结构不同。番茄的离层点相对清晰,辣椒的果梗较硬,草莓的果梗细软,机械臂剪切策略也完全不同。但算法上的主干和训练流程基本可以直接复用,这也是我觉得这个项目教学价值高的原因。
6.2 我踩过最深的坑和建议
如果只能给出一条经验,那就是:数据标注标准一定要在采集前写好文档,并且让所有参与标注的人对齐。我在第一次做这个项目时,就是因为标注时没有定义清楚“红色占比70%”到底怎么量化,导致训练集里同一个等级的样本视觉差异极大,模型反复调都不见好转,最后只能返工重新清洗数据。
第二点经验是,不要指望一个模型解决所有问题。检测成熟度和识别采摘点在任务属性上差异很大,强行合并到一个模型里,调优成本远高于维护两个独立模型。
第三点经验是:农业视觉项目的验收标准不是paper指标,而是能不能在连续一周的现场运行里保持稳定。天气一变、光照一变,模型就会原形毕露。所以做这个领域,心态要放平,把“现场稳定性”放在比“测试集mAP”更重要的位置。