搞农业AI的朋友应该都有同感:在智慧农业的落地场景里,视觉识别是最容易被低估的一环。尤其是辣椒成熟度识别,看着好像只是“拍个照、分个类”,但真正做起来才知道,光照、遮挡、品种差异、成熟度过渡期,每一项都让人头皮发麻。前些天我把这套基于深度学习的辣椒成熟度识别系统整理成了第10655期项目,包含数据集、训练好的模型、可复现的代码和可视化界面,实战下来效果不错,今天把这期项目的思路、踩坑和关键细节完整分享一下。
这个项目解决的是从农田到端侧的真实痛点:采摘机器人或巡检小车需要知道“哪颗辣椒可以摘了”。单纯用图像分类解决不了,因为一棵藤上往往同时挂着青果、转红果和红果,必须用目标检测把每一颗辣独框出来,再判断它的成熟等级。围绕这个需求,整个项目从数据采集、标注、YOLO模型训练,到最后的界面部署,形成了一条完整链路。如果你正准备做果蔬成熟度识别,或者想找一份能直接改的YOLO农业数据集,这篇内容会非常对路。
1. 项目定位与整体设计思路
1.1 辣椒成熟度识别到底难在哪
很多人第一次接触辣椒成熟度识别时,会觉得这比人脸检测简单多了——毕竟辣椒是静止的,颜色分明,青的就是绿,红的就是红。但只要你真的下过地、拍过照,马上会发现三个坑:
第一个是光照不听话。露天种植的辣椒,上午和下午的光照角度完全不同,阴天和晴天颜色差异巨大。中午太阳直射时,红辣椒的高光部分会发白,青色部分会被照成浅黄,模型很容易误判。第二个是成熟度不是离散的,而是渐变的。青椒会先局部变紫,再变成半红半青,最后全红。这个“过渡态”到底算青还是算红,标注的时候很难统一。第三个是遮挡太严重。辣椒叶子多,果实经常只露出一半,或者两根辣椒贴在一起,边界根本看不清。
所以项目在设计上就不能只用“颜色平均值”这种传统图像处理思路。传统的RGB阈值分割在实验室里表现挺好,换到菜地就崩,因为自然光下的颜色分布太宽了。我们最终选用深度学习目标检测,原因有两点:一是卷积网络能学到纹理、形状、局部颜色组合等高阶特征,不只依赖整颗果子的平均颜色;二是目标检测天然适合多果实、多类别的场景,可以直接输出位置框和类别标签,为后续机械臂采摘或计数提供结构化数据。
1.2 为什么选YOLO而不是传统分类网络
我早期尝试过两阶段方案:第一步用Faster R-CNN做检测,第二步用ResNet对每个框做成熟度分类。这套方案准确率确实高,但速度太慢,在边缘设备上每秒只能处理一两帧,根本没法跟移动底盘配合。后来果断切到YOLO单阶段检测。
YOLO最大的优势是快且稳。它在一次前向推理中同时预测边界框和类别,不需要额外的区域提议网络,部署起来也方便。我用的YOLOv8和YOLOv5家族都自带灵活的配置文件,对于辣椒这种不算极小的目标,成熟度和泛化能力都够用。而且YOLO生态成熟,数据格式转换、TensorRT部署都有现成工具链,节省了大量工程时间。
这里要说明一个设计口径:模型输出的类别不是“青辣椒”和“红辣椒”两个粗粒度类别,而是细分成“未成熟”、“半成熟”、“成熟”三类。“半成熟”这个类别是最关键的,它对应商业采摘中“转色期”的果实,很多场景需要优先采摘这类,因为再过几天就全红了,容易过熟腐烂。所以标注时宁可多花时间,也要把过渡态单独分出来。
1.3 第10655期数据集的交付形态
这期项目以“数据集+模型+代码+界面”四件套的形式发布。数据集部分包含原始图像和YOLO格式的txt标注文件,分类清晰,目录结构如下:
dataset_10655/ ├── images/ │ ├── train/ (3200张) │ ├── val/ (800张) │ └── test/ (400张) ├── labels/ │ ├── train/ (对应txt) │ ├── val/ │ └── test/ ├── data.yaml (类别定义和路径配置) └── classes.txt (类别清单)图像来自不同品种的辣椒,包括线椒、牛角椒和甜椒,拍摄场地涵盖露天大田、连栋温室和实验室模拟光源环境。模型部分提供了训练好的YOLOv8n和YOLOv8s权重,代码部分覆盖训练、验证、推理和简单界面,界面是用PyQt做的,可以加载图片或摄像头画面,实时显示每颗辣椒的成熟度框和置信度。这样一套东西拿到手,即便你完全没接触过YOLO,也能通过阅读代码把整个流程跑通。
2. 数据集的构建与预处理细节
2.1 采集环境与图像规范
数据质量决定模型上限,这是老生常谈但永远值得强调。我们采集图像时,专门定了三条规范,避免后期返工。
第一,分辨率统一使用1920x1080或1280x720,保证辣椒单果在图像中至少占据40x40像素以上的区域。太远的全景图里,一颗辣椒只有十几个像素,安全帽和辣椒根本分不清。第二,每个拍摄场景都要有“远近结合”。既有距离植株30-50厘米的特写图,也有包含整株辣椒的大场景图,这样模型才能适应不同工作距离。第三,曝光和色彩标准尽量贴近实际部署设备。如果我们最终要用某款摄像头,就尽量用同款摄像头来采集,而不是用手机拍完再让算法去适应不一样的色彩风格。
这里有个很多人忽略的点:别只拍“好看”的图。如果为了后期省事,只挑晴天顺光、果实完整、背景干净的照片,那训练出来的模型一到真实田里就会露馅。我特意在数据集中加入了大量“脏数据”——比如带有少量泥点的果实、枯黄叶片的背景、逆光剪影中的果实、甚至还有水滴附着在辣椒表面。事实证明,这些看似降低精度的图片,反而把模型的泛化能力拉上去了。
2.2 标注策略与类别定义
标注是整个周期里最枯燥但最重要的部分。我们用的标注工具是LabelImg和X-AnyLabeling,两者都支持导出YOLO格式。类别定义如下:
| 类别ID | 名称 | 定义说明 |
|---|---|---|
| 0 | immature | 未成熟,果皮整体青绿色,没有明显转红区域 |
| 1 | mid-ripe | 半成熟,果皮出现局部转红、紫红或橙黄区域,但未覆盖全果50% |
| 2 | ripe | 成熟,果皮整体红色或深红色,可采摘出售 |
这里有个标注细节值得展开。半成熟的判定标准,我们讨论了很久,最终确定按“果实表面转色面积是否达到50%”来切分。这个标准虽然带一点主观性,但比“看起来有点红”要可操作得多。实际操作中,我要求标注员把边界情况单独截图保存,每周开一次线上会统一标准。比如一张图中,一颗辣椒从左到右逐渐变红,前面是青的后面是红的,这样既不能算半成熟,也不能算未成熟,我们就按转色面积占比来机械判定,宁可牺牲一点个体判断,也不能让两个标注员的口径不一致。
另外,不标任何小于10x10像素的果实。因为在标注界面上强行标一个模糊小点,只会给模型传递噪声。太模糊的小目标可以等后期增加分辨率或使用更高算力模型来处理,不能硬塞进训练集。
2.3 数据清洗与增强
原始图里总会有一些完全不可用的样本:拍糊了、对焦对到了叶子上、镜头被水雾罩住、果实在画面边缘被裁掉一半多于40%。这些图我会用脚本批量剔除。剔除逻辑很简单:先看图像清晰度(拉普拉斯方差小于阈值就删),再看EXIF信息和标注密度,如果某张图完全没有标注框,就直接删掉。
数据增强这块,我用了随机水平翻转、随机裁剪、HSV色域微调、马赛克增强四种方式。特别要强调HSV微调,因为农业视觉里光照变化是最大噪声源,我们在hue、saturation、value三个通道上分别做了±0.02、±0.5、±0.4的扰动,模拟早中晚不同光线。马赛克增强则能让模型更好地适应叶片遮挡和果实拥挤的场景。
训练之前的最终统计是4400张图中包含约1.8万个标注框,平均每张图4个目标。类别分布大致是immature占40%、mid-ripe占25%、ripe占35%,相对均衡,不需要做重采样。如果类别差距超过3倍,建议使用focal loss或复制少样本图片,我这里就没有额外处理。
3. YOLO模型选型与训练实战
3.1 版本对比与选型依据
YOLO现在有v5、v6、v8、v9、v10等多个版本,很多新手一上来就想用最新的。我的建议是别盲目追新,认准自己部署平台和工具链支持度。当前项目我最终选择了YOLOv8n和YOLOv8s两个尺寸,主要原因有三点:
第一,Ultralytics框架对训练和导出做了封装,对我来说,数据集只要放到指定目录,一条命令就能开训,非常方便。第二,YOLOv8的检测头在小型目标上比YOLOv5有所改进,而辣椒在2米外看起来其实就是小目标。第三,网上关于YOLOv8的成熟教程和部署案例特别多,出了问题容易查资料。
同时我也对比了YOLOv5s,发现同条件下mAP要低1到2个百分点,但推理速度更快。如果你的边缘设备实在紧张,可以选择YOLOv5s。可如果你想要更稳的精度,尤其是对半成熟这种易混类别,YOLOv8s更值得。下面是我的实测对比:
| 模型 | 输入尺寸 | mAP@0.5 | mAP@0.5:0.95 | 推理速度(单张GPU) | 权重大小 |
|---|---|---|---|---|---|
| YOLOv5s | 640 | 0.912 | 0.674 | 2.1ms | 14.1MB |
| YOLOv8n | 640 | 0.901 | 0.658 | 1.4ms | 6.3MB |
| YOLOv8s | 640 | 0.934 | 0.702 | 2.7ms | 22.5MB |
从结果看,YOLOv8s的mAP最高,只贵了0.6毫秒,但换来了两个多点的提升,对于农业场景完全值得。最终我提交的模型以YOLOv8s为主。
3.2 训练参数与实验记录
训练框架用的是Ultralytics默认设置,但有几个参数我做了明确的调优,直接贴出来给你参考:
yolo train \ model=yolov8s.pt \ data=data.yaml \ epochs=100 \ batch=16 \ imgsz=640 \ optimizer=AdamW \ lr0=0.001 \ lrf=0.01 \ warmup_epochs=3 \ mosaic=1.0 \ hsv_h=0.02 \ hsv_s=0.5 \ hsv_v=0.4 \ project=runs/10655 \ name=pepper_ripebatch size设成16不是因为显存不够,而是为了让迭代次数更稳。更大的batch容易让BCLoss和BoxLoss在前期抖动,小batch配合低初始学习率会更平滑。Optimizer选了AdamW而不是SGD,虽然训练时间略长,但对小数据集来说收敛更快,不容易被卡在局部最优。
训练时长方面,在单卡RTX 3060上跑了约1.5小时。大家如果只有CPU,也能训练,但建议把epochs下降到30,imgsz降到480,否则等得眼睛发酸。我实验中设置了早停机制,patience为15个epoch,意思是如果验证集mAP连续15轮不涨,就自动停止,避免过拟合。
3.3 效果评估与常见指标解读
训练完不要只看最后一行loss下降了多少,要看验证集上的mAP、精确率和召回率。我的最终结果:mAP@0.5达到0.934,mAP@0.5:0.95达到0.702。从混淆矩阵来看,immature和ripe的召回率都很高,都在0.95以上,最容易错的确实是mid-ripe,有一小部分被错分成immature或ripe。
这说明什么问题?说明成熟度边界判断依然存在天然难度。我们人类的成熟度判断本身就有50%的模糊地带,算法能做成这样已经不错了。如果你的业务允许,建议在界面上把“半成熟”单独标记为优先采摘,并允许人工复核,这样漏检的代价会小很多。
另外,我在导出模型时选择了多尺度测试,把val时的imgsz从640提高到960,mAP进一步上升到0.946,但推理时间也增加了。所以最后部署时,还是要回到640,因为实际采摘机器人的相机画面不会无限大,实时性更重要。
4. 推理代码与可视化界面开发
4.1 模型导出与独立推理脚本
训练好的YOLOv8s.pt可以直接用Ultralytics的predict接口,但为了脱离训练框架做轻量部署,我会把模型导出为ONNX格式:
from ultralytics import YOLO model = YOLO("runs/10655/pepper_ripe/weights/best.pt") model.export(format="onnx", imgsz=640, opset=12)导出后,ONNX模型大约20MB,可以用ONNX Runtime在CPU上推理,也能转换为TensorRT跑GPU。我写了一个独立的推理脚本,核心逻辑就三步:读图、预处理、后处理。
预处理需要留意一个细节:YOLO模型输入的是归一化后的RGB图,且要进行letterbox处理,也就是把原图等比缩放并填充灰色边,而不是直接拉伸。否则,图像比例变化会造成框位置偏移。后处理则是解析输出张量,按照置信度阈值和NMS过滤框。一般我用conf_thres=0.25,iou_thres=0.45。
推理脚本里,我会把检测结果保存成JSON和可视化图,同时统计每种成熟度的果实数量。比如一张图输出:
{ "total": 23, "immature": 9, "mid-ripe": 6, "ripe": 8, "boxes": [...] }这个结构可以直接喂给下游的采摘决策模块,或者作为巡检报表输出。
4.2 用PyQt做一个简单标注/识别界面
如果只靠命令行输出,农户和业务人员用起来比较费劲。所以我用PyQt5写了一个轻量图形界面,功能有三个:打开图片识别、打开摄像头实时识别、保存结果。
界面布局上,左侧是图像显示区,右侧是类别统计和置信度滑块。代码里核心部分是把YOLO的推理封装成一个Worker类,放到QThread里跑,避免识别时界面卡死。下面是一个简化的界面逻辑:
class DetThread(QThread): update_signal = pyqtSignal(dict) def run(self): results = self.model.predict(self.frame, conf=self.conf, imgsz=640) # 解析框、类别、数量 self.update_signal.emit(info_dict)实际用的时候,QThread是最好的避坑方案。因为YOLO推理在PyQt主线程里会阻塞事件循环,窗口会变成“未响应”,感觉像是卡死。放到后台线程后,界面可以顺畅显示进度或退出操作。
界面上还有一个“导出统计表”按钮,能把当前批次的所有图片识别结果汇总成Excel表格。这对之后做产量估算很有用,甚至可以联动大棚环境数据,形成产量预测基础。
4.3 接摄像头实时检测的注意事项
项目部署时,我接到USB摄像头和海康网络摄像头上都跑通了。USB摄像头比较简单,用OpenCV的VideoCapture读帧即可。网络摄像头则要取RTSP流,注意OpenCV在连接不稳定时可能会丢帧或延迟,我建议加一个缓冲队列,每帧只取最新图像,不要处理过期帧。
实时检测的帧率,GPU上能跑到35-45 FPS,CPU上也有8-12 FPS,基本能满足巡检要求。你如果想把模型压到边缘设备上,比如Jetson Nano或树莓派,有两点建议:第一,用TensorRT把ONNX转为FP16精度,推理速度能提升2到3倍;第二,把输入尺寸降到480,虽然精度略降,但速度提升明显。农业场景里,摄像头离辣椒近,480的输入对大果实影响不大。
还有一个容易被忽略的问题:实时画面里的反光。大棚里塑料棚膜或者滴灌水珠会形成镜面反光,辣椒表面会出现高光斑块,很容易被误判成“转色区域”。我的处理办法是在摄像头前加偏振片,或者在下游加一个简单的亮度通道判断,仅凭RGB是不够的。这个坑,我在测试中踩了好几次才摸清楚。
5. 真实环境中的问题与排查清单
5.1 成熟度边界模糊分类错乱
这是所有做成熟度识别的人都会遇到的第一个大问题。模型在mid-ripe类别上,经常把局部转色但面积不足50%的果子判成immature,或者把超过50%但颜色很浅的果子判成ripe。
排查思路是这样的:先看训练数据里mid-ripe的样本质量。我最初标注时由于标准不统一,有些标注员把“有一点黄”也算mid-ripe,导致类别内样本形态差异巨大。后来我把标注规范改成“转色面积必须肉眼可见超过三分之一”,并且把标注图片按类别抽样可视化,发现mid-ripe里的确有不少错误标注,于是花了三天时间清洗这部分数据,重新训练后mAP直接提升了0.8个百分点。
如果你的模型还是混淆严重,可以试试在损失函数上加权,把mid-ripe的loss权重提高20%。或者减少mid-ripe类别占比,让模型更保守,只在非常明显的转色条件下触发mid-ripe。具体要视业务而定,如果采摘系统更害怕把未成熟的果摘下来,那宁可让mid-ripe分到ripe里也不能分到immature里。
5.2 小目标漏检与叶片遮挡
当摄像头距离植物超过2米,辣椒在画面里只有30像素左右,YOLOv8s的漏检率明显上升。这种情况下,我做了三件事。
第一,在数据集中加入更多远距离拍摄图像,并利用测试时增强(TTA)提高稳定性。第二,把imgsz从640提升到960,小目标能多出50%的像素,mAP提升不小。第三,检查NMS设置。小目标重叠多时,iou_thres设得太高会把邻近框合并掉,我改成0.35后多捡回了不少小框。
叶片遮挡导致只有半个果子露在外面,要不要标?这个问题项目组讨论过,最后的结论是:只要露出部分超过果实总面积的30%,就标一个完整的框,让模型学习推测遮挡部分。刚开始模型可能会预测出偏大的框,但卷积网络能从茎和阴影推断出完整轮廓,训练几轮后框反而比人手标得更准确。
5.3 光照变化引起的误报
光照问题特别容易在傍晚和阴天发作。一次我在黄昏测试,发现整片辣椒都被预测成mid-ripe,因为低色温光把绿色果实照得发黄。后来分析,训练集里确实缺少黄昏光照的样本,我在后续数据采集中专门安排了早、中、晚三个时段拍摄,并在图像增强里加大色温扰动。
另外,我建议在界面里加一个“时间场景模式”开关,比如早晨、正午、阴天,离线模型可以针对不同模式加载不同的色彩校正LUT。这样做虽然有点土,但在低成本设备上比复杂的光照自适应算法实用得多。如果你不想做多模型,最简单的方法是训练前对图像做自动白平衡,然后用白平衡后的图重新训练和推理,效果也会稳定不少。
6. 收获与扩展建议
6.1 从数据集到落地的完整链路复盘
在这期项目里,我完整走了一遍“痛点定义→数据采集→标注→模型训练→界面部署→现场测试”这个链路,最大的体会有三点。
第一,数据集的价值远远大于模型本身。很多朋友喜欢到处找新模型架构,但真正的性能瓶颈往往在数据标注一致性上。一个清洗得干净、类别定义清晰的数据集,往往比换一个骨干网络带来的收益大得多。我这套数据集的第10655期之所以公开,就是想让大家跳过前期最痛苦的采集和标注阶段。
第二,模型调参不是越多越好。YOLO默认参数在很多农业数据集上都够用,我最终有效调的只有imgsz、conf_thres和几个增强参数。瞎改学习率和anchor只会浪费时间。先跑通,再优化,这是我一直坚持的思路。
第三,界面化部署决定了项目的实用价值。能跑出检测框不算交付,能让人用起来才算交付。这个项目里的PyQt界面虽然简陋,但它是真正推动这个技术进入农业现场的临门一脚。
6.2 把这个方案迁移到其他果蔬的思路
这套方案本质上是“果实检测+成熟度等级分类”的通用框架。迁移到番茄、草莓、苹果时,只需要做两步:一是重新采集目标果实的数据,保持同样的标注规范和类别定义;二是把data.yaml里的类别名称改成对应成熟度标准。
比如番茄,熟透的和未熟的颜色差异比辣椒更明显,三类划分可以改成两分类“可采摘/不可采摘”。草莓则更适合做成熟度四分类,因为白果期和半红期是重要分界。模型的训练代码完全不需要大改,微调就能用。这就是我选择YOLO这类开源检测框架的原因,生态好,迁移成本低。
如果还想更进一步,可以考虑把识别结果跟产量估算结合。通过统计每帧图像中的成熟果实数量和果实像素面积,再根据行距株距推算单株产量,这比纯人工估产高效太多。我认为智慧农业的落地,正需要这样一个个小而准的检测模型去堆积,最终拼成完整的数字化农场管理系统。
我个人在实际操作中还有一个体会:不管你的模型在测试集上多好看,到了真实的农田里,第一周一定会被各种意外打脸——泥巴、虫子、遮阴网、反光膜。所以做农业AI,一定要留出足够的现场调优时间,把跑通模型当作起点,而不是终点。希望这期第10655数据集和配套代码,能让你少走一点我走过的弯路,把更多精力放在解决真实问题上。