
简介面向农业视觉检测与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_okTrue) 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适用场景YOLOv8n6403.2M约 1.5ms采摘机器人、实时视频流YOLOv8s64011.2M约 2.8ms边缘盒子、分拣线YOLOv8m64025.9M约 5.5ms离线批量检测YOLOv9c64025.3M约 6.0ms高精度离线分析3.2 最小可复现的训练命令与参数说明数据准备好之后训练的命令不复杂但参数值得逐条解释。以下命令基于 Ultralytics 提供的 CLI 接口适用于 YOLOv8 全系列。# 训练番茄成熟度检测模型 # data.yaml 指向数据集路径包含 train/val 目录和类别定义 yolo train modelyolov8s.pt data./tomato_dataset.yaml \ epochs150 imgsz640 batch16 \ lr00.005 lrf0.01 \ mosaic1.0 mixup0.2 \ cos_lrTrue \ patience30 \ project./runs/tomato_seg exp_nameyolov8s_ripe参数选择上有几个点需要说明。epochs150对于成熟度任务来说是底线因为边界样本需要足够的轮次才能稳定拟合batch16取决于显存如果你的显卡只有 8GB建议降到 8lr00.005是 Warmup 后的初始学习率迁移学习场景下比默认的 0.01 更稳妥能减少前期 BN 统计量的剧烈波动mosaic1.0保留默认即可它能增强模型对遮挡和密集场景的鲁棒性mixup0.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 增强mixup0.0MixUp 在 batch 较小时会影响 BN 统计量的稳定性。按这个顺序排查绝大多数 BN 崩溃都能在 10 分钟内定位。4.3 薄雾就是成熟的数据偏见现象模型对套袋番茄或表面有白霜的番茄误判为转色。原因番茄表面的白霜果粉和未熟番茄表皮的浅绿色在灰度图像上表现为相似的浅色区域模型学到了浅色 转色的错误关联。这本质上是标注阶段对成熟度定义只关注了颜色忽略了果面质地纹理。解决标注时对带白霜的番茄要单独标注并确保转色类别的样本里包含带果粉的果实让模型学会区分白霜覆盖下的绿色和真正开始转黄的底色。具体做法是对数据集中所有白霜番茄过一遍标注单独加一个细分标签去重检查防止同一个果实在不同图片里被标成不同类别。4.4 密集遮挡下的小目标漏检现象一串番茄有 8 到 10 个果实肉眼可见但互相重叠超过 50%模型只能检出外围的三四个重叠最深的中心果实全部漏检。原因YOLO 默认的 NMS 阈值IoU0.7会让高度重叠的检测框互相抑制。同时训练数据中如果缺乏密集遮挡的标注模型就没有学习到从可见局部推断整体位置的能力。解决推理时把 NMS 的 IoU 阈值从 0.7 降到 0.4 到 0.5允许局部重叠的框同时保留训练时对密集果实图做 1.5 倍到 2 倍的 Mosaic 拼接模拟更拥挤的场景。如果漏检仍然严重把输入分辨率从 640 提升到 768 会直接改善小目标召回率代价是推理延迟增加约 30%。5. 部署到真实分拣线权重转换、量化踩坑与实时性预算5.1 从 PyTorch 到 ONNXTorchScript 不只是导出训练好的 best.pt 要部署到实际环境第一步是导出为中间格式。常见做法是用 ONNX因为它既能跑 NVIDIA 的 TensorRT也能跑瑞芯微 RK3588 这类边缘 NPU中途不需要再动训练代码。导出命令很直接。# 导出 ONNX开启动态输入支持批量推理 yolo export model./runs/tomato_seg/yolov8s_ripe/weights/best.pt \ formatonnx imgsz640 dynamicTrue simplifyTruedynamicTrue允许推理时动态调整 batch sizesimplifyTrue会调用 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 自然会给出一份漂亮的结果。希望帮到你。本文还有配套的精品资源点击获取