简介:面向农业视觉检测与YOLO算法实战学习者的番茄成熟度识别项目,以实时目标检测为核心技术,解决番茄未成熟、半成熟、成熟阶段的快速定位与状态分类问题,适用于农产品分拣、生长监测等实际场景。压缩包共6个文件,整体大小仅5.41MB,体量轻巧。包内包含两个交互式编程笔记,分别作为演示版与改进版项目,支持逐段运行并查看训练、测试及效果展示;另有训练好的模型权重文件,加载后即可直接用于预测,避免重复训练;还提供一个应用程序脚本,可封装检测流程,方便接入其他系统或转成接口服务;依赖清单与说明文档则分别确保环境配置无误、降低上手门槛。目前已有44人学习,适合目标检测初学者跟随完整流程实践,也适合农业智能化开发者参考数据组织与部署思路,快速搭建自有番茄成熟度检测方案。
1. 番茄成熟度YOLO检测:一个能用 200 张图片训练落地的视觉项目
番茄成熟度判定是设施农业里最典型的视觉任务:采摘、分拣、定价都要靠它。人眼判断尚且会因为光照、品种、视角产生分歧,换成机器视觉,问题就从"怎么看清"变成了"怎么定义成熟"。YOLO 模型在农业场景里被大量使用,但真正把它用在番茄成熟度检测上,难点其实不在模型本身,而在数据标注的粒度、成熟度阶段的划界、以及模型在自然光照下的稳定性。
这篇笔记围绕"番茄成熟度 YOLO 检测"这个方向,讲清楚从数据准备、模型选型、训练调参到部署上板的全流程,重点放在几个容易被忽视的坑上——比如"薄雾就是成熟"的标注陷阱、YOLO 训练中 BN 崩溃的判断方法、以及成熟度边界样本对混淆矩阵的干扰。适合刚入门目标检测的从业者照着复现,也适合已经在跑 YOLO 的开发者做成熟度细分时的参考资料。
2. 定义番茄成熟度阶段:先把问题拆成模型能学的东西
2.1 为什么成熟度判定不能直接套用通用检测
通用目标检测解决的是"有没有"的问题,番茄成熟度检测解决的是"到什么程度"的问题。两者的本质差异在于:通用检测的类别边界由物体本身决定,而成熟度边界是一个连续变化过程的离散切分。青果转白、白果泛红、红果发深,这个过程没有绝对的分界线,同一个番茄在不同光照下拍出来,色值差异可能比两个相邻成熟度阶段的差异还大。
因此,训练一个成熟度检测模型,第一步不是收集图片,而是确定"你要分几个阶段"。常见做法是分四类:未熟(青果,以绿色为主)、转色(开始出现黄绿色或浅黄色)、半熟(果面 30%~60% 转红)、全熟(果面 90% 以上红色)。有些分拣线会再加一个"过熟"类别,用于剔除裂果和软果。分类粒度直接决定了标注成本、模型复杂度和分拣线的容错空间——类别越多,边界样本越多,模型在边界上的误判越频繁。
2.2 数据采集的三个硬性要求
番茄成熟度数据集的采集,我建议按"同一株、连续拍、多角度"的原则来做。同一株番茄在不同成熟阶段的照片,能避免模型学到品种特征而不是成熟特征。连续拍的意思是:每周对同一片果实拍照 2 到 3 次,这样转色过程的不同比例会被完整记录下来,边界样本自然会出现,而不是靠后期硬找。
拍摄时要注意三个点:一是光线,温室里建议用自然光加白色补光板的组合,避免红蓝光补光灯导致果实颜色偏移;二是视角,至少要包含侧视、俯视和略微仰视三个方向,模拟采摘机器人机械臂的常见视野;三是遮挡,尽量采集有叶片遮挡、果实重叠的图片,模型如果只在"干干净净"的图片上训练,部署到大棚里误检率会明显上升。
2.3 标注粒度:一个番茄一个框,还是一个果实一个框
这是成熟度任务最容易踩的坑。一张图片里有多个番茄相互重叠时,标注工具里"一个框框住一串番茄"看起来省事,但模型学到的会是"整串的成熟度",完全无法用于单果采摘。正确做法是:一个果实一个框,即使被遮挡 30% 也要标注这个果实,让模型学会从局部特征推断完整状态。
标注软件方面,LabelImg 和 X-AnyLabeling 都可以用。X-AnyLabeling 支持 SAM 辅助分割,对密集果实的框选效率高很多。标注完成后导出为 VOC 格式的 XML 文件,再转换成 YOLO 格式的 TXT 文件。
# 将 VOC XML 标注转换为 YOLO txt 格式 # 依赖:python3, lxml python3 -c " import os, glob from lxml import etree classes = ['unripe', 'turning', 'half_ripe', 'ripe'] xml_dir = './annotations/' out_dir = './labels/' os.makedirs(out_dir, exist_ok=True) for xml_file in glob.glob(xml_dir + '*.xml'): tree = etree.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in classes: continue cls_id = classes.index(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) # YOLO 使用归一化的中心点坐标和宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f'{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') base = os.path.basename(xml_file).replace('.xml', '.txt') with open(os.path.join(out_dir, base), 'w') as f: f.write('\n'.join(lines)) print('converted:', len(glob.glob(out_dir + '*.txt'))) "这段脚本做的事情很简单:读取 VOC XML 里的目标类别和边界框坐标,把像素坐标换算成 YOLO 需要的归一化中心点坐标和宽高。关键在classes列表的顺序,它决定了 TXT 文件里第一个数字的含义——0是未熟,1是转色,2是半熟,3是全熟。这个顺序在后续训练配置里必须保持一致,一旦改错,模型输出的类别就会张冠李戴。
3. 选型与训练:YOLOv8 还是 YOLOv9,成熟度任务看重什么
3.1 不同 YOLO 版本在农业检测场景的取舍
YOLO 系列迭代到现在,v5、v6、v8、v9、v11 都有各自的社区基础。做番茄成熟度检测,我一般推荐从 YOLOv8 开始,原因有三个:官方仓库更新稳定,Ultralytics 的集成度高;针对小目标的检测头在密集果实场景下表现比 v5 好;训练和导出 ONNX 的流程统一,不用自己拼凑脚本。
如果追求更高的精度,YOLOv9 在复杂背景下的特征提取能力更强,但对显存的消耗也更大,边缘设备上推理速度会打折扣。YOLOv8 系列本身也有 n / s / m / l / x 五档,番茄分拣线实时性要求高,yolov8n或者yolov8s就够用了,l和x适合离线离线分拣场景。下面这份对比可以快速做个选型参考。
| 模型 | 输入分辨率 | 参数量约 | 推理速度(RTX 3060) | 适用场景 |
|---|---|---|---|---|
| YOLOv8n | 640 | 3.2M | 约 1.5ms | 采摘机器人、实时视频流 |
| YOLOv8s | 640 | 11.2M | 约 2.8ms | 边缘盒子、分拣线 |
| YOLOv8m | 640 | 25.9M | 约 5.5ms | 离线批量检测 |
| YOLOv9c | 640 | 25.3M | 约 6.0ms | 高精度离线分析 |
3.2 最小可复现的训练命令与参数说明
数据准备好之后,训练的命令不复杂,但参数值得逐条解释。以下命令基于 Ultralytics 提供的 CLI 接口,适用于 YOLOv8 全系列。
# 训练番茄成熟度检测模型 # data.yaml 指向数据集路径,包含 train/val 目录和类别定义 yolo train model=yolov8s.pt data=./tomato_dataset.yaml \ epochs=150 imgsz=640 batch=16 \ lr0=0.005 lrf=0.01 \ mosaic=1.0 mixup=0.2 \ cos_lr=True \ patience=30 \ project=./runs/tomato_seg exp_name=yolov8s_ripe参数选择上有几个点需要说明。epochs=150对于成熟度任务来说是底线,因为边界样本需要足够的轮次才能稳定拟合;batch=16取决于显存,如果你的显卡只有 8GB,建议降到 8;lr0=0.005是 Warmup 后的初始学习率,迁移学习场景下比默认的 0.01 更稳妥,能减少前期 BN 统计量的剧烈波动;mosaic=1.0保留默认即可,它能增强模型对遮挡和密集场景的鲁棒性;mixup=0.2是弱数据增强,太强的 mixup 会让成熟度颜色特征变得模糊。
训练完成后,模型会自动保存 best.pt 和 last.pt。判断训练是否正常的标准不是 loss 曲线是否光滑,而是验证集上的 P / R / mAP 三组指标是否同时收敛。如果 loss 下降但 mAP 一直不动,大概率是类别不平衡问题——成熟度数据里"未熟"和"全熟"样本远多于"转色"和"半熟",需要做类别重加权。
3.3 混淆矩阵不是看对角线,是看相邻类别
YOLO 训练日志里生成的 confusion_matrix.png 是成熟度任务最值得看的图。对角线高只能说明整体分类正确,你要关注的是对角线两侧的数值——尤其是"转色"被误判为"半熟"、"半熟"被误判为"全熟"这两组。如果这两项误判比例超过 15%,说明标注阶段的边界定义不够清晰。
遇到这种情况,不要急着加数据,先回去看标注:把所有"转色"类别的图片翻出来,看是否有 30% 以上果面已经明显泛红。如果有,说明标注标准不一,需要统一为"转色类只包含 10% 以下果面泛红"的严格定义。边界样本的标注一致性,比新增 100 张图片更能提升模型精度。
4. 番茄成熟度检测的四个典型陷阱:光照、过拟合、薄雾与密集遮挡
4.1 光照漂移导致"玄学误检"
现象:模型在训练集上 mAP 有 0.92,换到温室实时视频流里,未成熟青果大量被识别成半熟,误检率飙升。
原因:训练图片大多在上午拍摄,光源色温稳定;实际部署时下午阳光斜射,果面反光带黄色,色值分布偏移到了"转色"区间。
解决:这是成熟度检测里最典型的光照泛化问题。在数据准备阶段加入多时间段采集,至少要包含上午、中午、下午三个时段。如果已经训练完了,先用 Albumentations 做在线增强,重点加 HueSaturationValue 和 RandomBrightnessContrast,然后再微调 20 到 30 个 epoch。这招能救回来一部分,但根治还是要在数据上补齐阳光方位的多样性。
4.2 BN 崩溃:训练 loss 突然变 NaN
现象:训练到第 30 到 50 个 epoch 时,loss 曲线突然跳到 NaN,重启后没过多久又复现。
原因:BatchNorm 的 running mean 和 running variance 在某个 batch 内统计量异常,通常是因为 batch size 太小同时学习率偏大,或者数据里混入了一张全黑/全白的异常图片。番茄棚里常见的补光灯直射、相机过曝都能产生这种极端输入。
解决:第一步降低学习率,把lr0从 0.005 降到 0.001;第二步检查数据集中是否有异常图片,把单通道方差几乎为 0 的图剔除;第三步关闭 MixUp 增强(mixup=0.0),MixUp 在 batch 较小时会影响 BN 统计量的稳定性。按这个顺序排查,绝大多数 BN 崩溃都能在 10 分钟内定位。
4.3 "薄雾就是成熟"的数据偏见
现象:模型对套袋番茄或表面有白霜的番茄,误判为"转色"。
原因:番茄表面的白霜(果粉)和未熟番茄表皮的浅绿色在灰度图像上表现为相似的浅色区域,模型学到了"浅色 = 转色"的错误关联。这本质上是标注阶段对成熟度定义只关注了颜色,忽略了果面质地纹理。
解决:标注时对带白霜的番茄要单独标注,并确保转色类别的样本里包含带果粉的果实,让模型学会区分"白霜覆盖下的绿色"和"真正开始转黄的底色"。具体做法是对数据集中所有白霜番茄过一遍标注,单独加一个细分标签去重检查,防止同一个果实在不同图片里被标成不同类别。
4.4 密集遮挡下的小目标漏检
现象:一串番茄有 8 到 10 个果实,肉眼可见但互相重叠超过 50%,模型只能检出外围的三四个,重叠最深的中心果实全部漏检。
原因:YOLO 默认的 NMS 阈值(IoU=0.7)会让高度重叠的检测框互相抑制。同时,训练数据中如果缺乏密集遮挡的标注,模型就没有学习到"从可见局部推断整体位置"的能力。
解决:推理时把 NMS 的 IoU 阈值从 0.7 降到 0.4 到 0.5,允许局部重叠的框同时保留;训练时对密集果实图做 1.5 倍到 2 倍的 Mosaic 拼接,模拟更拥挤的场景。如果漏检仍然严重,把输入分辨率从 640 提升到 768 会直接改善小目标召回率,代价是推理延迟增加约 30%。
5. 部署到真实分拣线:权重转换、量化踩坑与实时性预算
5.1 从 PyTorch 到 ONNX:TorchScript 不只是导出
训练好的 best.pt 要部署到实际环境,第一步是导出为中间格式。常见做法是用 ONNX,因为它既能跑 NVIDIA 的 TensorRT,也能跑瑞芯微 RK3588 这类边缘 NPU,中途不需要再动训练代码。导出命令很直接。
# 导出 ONNX,开启动态输入支持批量推理 yolo export model=./runs/tomato_seg/yolov8s_ripe/weights/best.pt \ format=onnx imgsz=640 dynamic=True simplify=Truedynamic=True允许推理时动态调整 batch size,simplify=True会调用 ONNX Simplifier 对计算图做常量折叠和冗余节点清理。导出完成后,用 onnxruntime 做一个最小验证:加载模型、跑一张真实棚拍图、检查输出维度和类别顺序。这一步非常值得做,很多部署翻车都是在这里发现的——最常见的是输出张量的形状从[1, 84, 8400]变成了按不同排列方式排布的[1, 8400, 84],检测后处理代码里 transpose 写错就直接白屏。
5.2 量化:FP16 与 INT8 的精度损失没有想象中可怕
边缘设备上跑 INT8 量化是常态。番茄成熟度检测对颜色的敏感度高于通用物体检测,所以量化前需要确认模型的敏感层在哪里。理论上,RGB 颜色分布经过卷积之后已经映射到了高维空间,量化掉的是权重精度而非颜色信息,所以不会出现"量化后颜色识别失效"的问题。
实际做 INT8 量化时,要用真实棚拍图做校准集,大约 300 到 500 张,覆盖不同光照和不同成熟度阶段。校准集太单一会导测量化后的 mAP 从 0.91 掉到 0.83,用多样本校准能控制在 0.89 到 0.90 之间。如果你用的是 RK3588 或者 Jeston Orin,官方 SDK 里一般都有现成的量化工具,按默认参数跑一遍就能用。遇到掉点严重的情况,优先检查 BN 层是不是被折叠掉了——有些量化工具为了省算力会把 BN 合并进卷积,但 YOLO 的 BN 层在浅层对色偏容忍度很低,合并后精度波动特别明显。
5.3 实时性预算:一算二看三测
成熟度检测的实时性要求取决于场景:分拣线传送带上番茄逐个通过,单帧推理要控制在 30ms 以内;采摘机器人需要连续跟踪果实,推理延迟超过 50ms 就会导致机械臂抓取滞后。这里的瓶颈往往不在模型,而在图像采集环节。
相机的曝光时间、传输接口、预处理管线三个环节加起来,常常比模型推理本身更耗时。用 USB 工业相机时,帧率 30fps 的相机实际可用帧率会因为自动曝光调整降到 15fps 以下。经验是固定曝光、固定白平衡,关闭自动增益,让图像输入保持稳定,模型输出的稳定性反而会提高。推理端选型可以先在 PC 上验证:用 onnxruntime 测一次单帧延迟,然后乘上 1.5 到 2 倍的系数估算边缘设备上的实际表现,能提前发现延迟超预算的风险。
6. 让模型更鲁棒的三个进阶打法:小目标检测头、时序投票与标签清洗
成熟度检测做到能跑通容易,做出稳定性就需要额外下功夫。这里有三个方向值得投入。
第一个是小目标检测头。番茄大棚里相机安装在支架上,距离果实 1.5 到 2 米,单个成熟番茄在 640 分辨率下的像素尺寸往往只有 20 × 20 到 40 × 40,属于妥妥的小目标。YOLOv8 默认有三个检测头,分别负责大中小目标,但 P3 层的感受野对 20 像素以下的果实还是不够敏感。改进方式是在 P2 层加一个额外的检测头,代码里只需要在 yaml 配置文件的head部分增加一个上采样路径。代价是推理速度大约下降 15%,但小目标的召回率通常能提升 4 到 6 个百分点。
第二个是时序投票。视频流里单帧误检不可避免——一阵风吹过叶片遮挡果实,模型瞬间把未熟番茄框成半熟。如果直接按单帧结果触发机械臂动作,就会出现抓空。在推理后处理阶段加一个长度为 5 到 10 帧的类别投票:只有连续 N 帧判定为同一成熟度,才输出最终状态。这本质上是把检测的时域稳定性做进去,效果立竿见影。代码实现上用 deque 存最近 N 帧的检测类别,取众数即可。
第三个是标签清洗。模型训练完第一版后,用模型对训练集做预测,把置信度低于 0.3 或预测类别与标注不符的样本全部打印出来人工复核。这一步能发现标注串类、边界框偏移、类别漏标三类问题。我自己的标注经验是,人工标注的准确率大约在 92% 到 95%,剩下的 5% 到 8% 的错误标签会成为模型精度的隐形天花板。洗一次标签的成本大约是一个小时,收益往往比加 100 张新图还大。
成熟的边界是连续光谱,机器视觉只是把它强行切成几个离散块。做这个项目最深的体感是:调模型的力气只占三成,七成力气花在定义清楚"什么是什么"。标注标准统一了,光照覆盖足够广,YOLO 自然会给出一份漂亮的结果。希望帮到你。
本文还有配套的精品资源,点击获取