简介:这份PDF文献面向从事茶叶加工自动化、计算机视觉与深度学习应用的研究人员和工程开发者,针对机采鲜茶叶中风选、筛选难以精确细分等级的问题,提出一套基于卷积神经网络的智能分选方案。文中搭建了7层结构的CNN识别模型,通过共享权值与逐渐下降的学习速率提升训练性能,并结合图像分割与尺度变换完成输入归一化,实验显示识别正确率不低于90%,可对单芽、一芽一叶、一芽二叶、一芽三叶、单片叶及叶梗进行有效分类。资源包为单一PDF文件,共1个文件,大小约2.31MB,便于直接阅读与引用。目前已有172人学习下载,适合作为智能系统开发、图像识别课题的参考文献与专业指导材料,读者可从中获取完整的网络结构设计、训练策略与实验验证思路。
1. 鲜茶叶分选这道题,卷积神经网络到底能解哪一段
鲜叶进厂那会儿,一筐里混着单芽、一芽一叶、一芽二叶,还有对夹叶、老梗、虫咬叶和红变叶。传统做法靠振动筛分加风选,再请几位老师傅在传送带两边盯着挑,问题是筛孔只能按尺寸粗分,风选只能按重量分,颜色和瑕疵全靠人眼。一条日产两千斤鲜叶的初制线,光拣剔工就要六到八个人,而且上午和下午的光线一变,老师傅的判断标准也会漂移。鲜茶叶智能分选系统要解决的就是这件事:把「什么样的叶子算特级、什么样算一级」从人的经验变成可复现的图像判别,再用卷积神经网络把判别速度拉到产线节拍。这套方案适合两类人:一类是茶厂里负责设备改造的工程师,想搞清楚视觉分选到底能不能接上现有的摊青和杀青工序;另一类是做农业视觉的算法同学,手里有茶叶图像但不知道从哪一步开始搭分类器。下面按「数据怎么来、模型怎么选、产线怎么接、坑在哪」的顺序讲透。
2. 从鲜叶图像到可训练数据集:采集、标注与增强的完整链路
卷积神经网络再强,喂进去的是糊的、偏色的、标注混乱的图,出来的就是玄学。鲜茶叶分选和通用图像分类最大的差别在于:目标极小、类别间差异细微、光照条件随车间窗户和灯管变化。所以数据链路要单独拿出来讲,不能一句「采集并标注」带过。
2.1 采集端:工业相机、光源与背景的硬约束
常见做法是用卷帘快门 CMOS 工业相机配远心镜头,分辨率至少 500 万像素,因为单芽长度约 1.5 到 2.5 厘米,要在一帧里同时看清芽尖绒毛和叶面红变,像素当量得压到 0.05 毫米每像素以内。光源用高显色指数 LED 条形灯从两侧 45 度打光,显色指数 Ra 大于 90,否则绿色叶和红变叶在 RGB 空间里会挤在一起。背景用哑光黑色或深灰传送带,避免反光把叶缘吃掉。
采集时鲜叶要单层摊开,重叠率控制在 5% 以下。重叠一高,标注框就会互相压,分类器学到的边界是错的。我一般会在传送带上方加一个振动匀料板,让叶子在进入相机视野前先散开。帧率按产线速度反推:如果传送带 0.3 米每秒,视野 0.3 米,那相机至少 2 帧每秒才能不丢叶,实际取 5 到 10 帧每秒留余量。
2.2 标注规范:四类还是六类,先定死再动手
鲜叶分选不是越细越好。类别一多,类间差异就逼近标注员的一致性极限。我建议先按收购计价方式定类别,常见是四类:单芽、一芽一叶、一芽二叶、非茶类杂物(老梗、虫叶、红变叶合并)。如果茶厂按特级、一级、二级计价,那就把单芽和一芽一叶再拆开,但拆之前先做一轮标注一致性测试:让三个人独立标同一批 200 张图,算 Cohen kappa,低于 0.75 就说明类别定义有歧义,得回去改规范。
标注用矩形框还是分割掩码?如果后续只做分类不做定位,整图分类就够,把单叶裁成小图存成类别文件夹。如果要在传送带上做实时剔除,需要检测框,那就用 YOLO 格式的 txt。两种都行,但别混着来。
2.3 数据增强:别用随机旋转把茶叶转成不存在的姿态
鲜叶在传送带上主要是一个朝向,随机旋转 90 度会造出自然界不存在的样本,反而让模型学到伪特征。我一般只用水平翻转、±15 度小角度旋转、亮度 ±10% 和对比度 ±10% 的抖动。颜色抖动要克制,因为红变叶和正常绿叶的区分就靠色相,抖过头就把类别边界抹掉了。
下面是一段用 Albumentations 做增强的代码,直接可跑:
import albumentations as A import cv2 # 鲜叶增强管道:几何变换保守,颜色变换克制 train_transform = A.Compose([ A.HorizontalFlip(p=0.5), # 只做水平翻转,符合传送带实际 A.Rotate(limit=15, border_mode=cv2.BORDER_CONSTANT, value=(0, 0, 0), p=0.5), # 小角度旋转,黑边填充 A.RandomBrightnessContrast( brightness_limit=0.1, contrast_limit=0.1, p=0.5), # 亮度对比度各±10% A.HueSaturationValue( hue_shift_limit=5, sat_shift_limit=10, val_shift_limit=10, p=0.3), # 色相只动5,保护红变特征 A.Resize(224, 224), # 统一到骨干网络输入尺寸 ]) # 使用示例 img = cv2.imread("fresh_leaf_001.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) aug = train_transform(image=img)["image"]逻辑说明:HorizontalFlip模拟叶子在传送带上左右朝向的随机性;Rotate限制在 15 度是因为鲜叶不会在带上竖起来;HueSaturationValue的hue_shift_limit=5是血泪经验,设到 20 以上红变叶和正常叶就分不开了。参数怎么改:如果相机换了、光源色温变了,先把brightness_limit调到 0.15 再重训,别一上来就加旋转角度。
提示:增强后的图要抽 20 张肉眼过一遍,确认没有把芽尖转出画面、没有把红变叶调成绿叶。这一步花五分钟,能省一次通宵重训。
3. 骨干网络选型与轻量化:ResNet、MobileNet 还是自己搭一维 CNN
标题里写的是卷积神经网络,但卷积神经网络结构图一搜一大把,落到鲜叶分选上,选哪个骨干、要不要轻量化、输入尺寸定多少,直接决定能不能在产线工控机上跑起来。
3.1 分类还是检测:先分清任务再选结构
如果只是给每片叶子打等级、不做位置剔除,那就是图像分类任务,输入是单叶裁图,输出是类别概率。这种场景用 ResNet-18 或 MobileNetV3 就够,224×224 输入,在几千张图的规模上微调,验证集准确率能到 95% 以上。如果要在传送带上实时定位并吹除杂物,那就是目标检测,得用 YOLOv5n 或 YOLOv8n 这类轻量检测器,输入 640×640,单帧推理控制在 20 毫秒以内才能跟上 5 帧每秒的采集。
我一般会先问一句:茶厂到底是要「分级统计」还是「在线剔除」。前者分类就够,后者必须检测。别为了显得高级硬上检测,标注成本翻三倍,产线还不一定需要。
3.2 轻量化取舍:MobileNetV3 在工控机上的实测边界
工控机常见配置是 Intel i5 或 i7 带核显,没有独立显卡。这种情况下 ResNet-50 单帧要 80 到 120 毫秒,跟不上节拍。MobileNetV3-Small 在 OpenVINO 加速下能压到 8 到 12 毫秒,精度掉 1 到 2 个百分点,完全可接受。如果厂里配了 Jetson Nano 或 Orin,那可以上 ResNet-18 加 TensorRT,精度和速度都稳。
下面是一段用 PyTorch 加载预训练 MobileNetV3 并替换分类头的代码:
import torch import torch.nn as nn from torchvision import models # 加载 ImageNet 预训练的 MobileNetV3-Small model = models.mobilenet_v3_small(weights=models.MobileNet_V3_Small_Weights.DEFAULT) # 替换分类头:鲜叶分4类 in_features = model.classifier[3].in_features model.classifier[3] = nn.Linear(in_features, 4) # 4类:单芽/一芽一叶/一芽二叶/杂物 # 冻结前几层,只微调后半段(小数据集防过拟合) for name, param in model.named_parameters(): if "features.0" in name or "features.1" in name: param.requires_grad = False # 优化器:分类头用大学习率,骨干用小学习率 optimizer = torch.optim.Adam([ {"params": model.features.parameters(), "lr": 1e-4}, {"params": model.classifier.parameters(), "lr": 1e-3}, ])逻辑说明:weights=DEFAULT拿 ImageNet 预训练权重,鲜叶数据集通常只有几千张,从头训会过拟合。替换classifier[3]是因为 MobileNetV3 的分类头最后一层是全连接,输出维度改成自己的类别数。冻结前两层是常见做法,浅层学的是边缘和纹理,通用性强,没必要动。参数怎么改:如果数据集超过两万张,可以把冻结去掉,学习率统一降到 1e-4;如果验证集准确率震荡,把分类头学习率从 1e-3 降到 5e-4。
3.3 输入尺寸与批大小的联动
输入尺寸不是随便定的。224×224 是 ImageNet 标准,但鲜叶的芽尖细节在缩到 224 后会糊。我试过 320×320,精度涨了 1.5 个百分点,推理时间涨 40%。如果产线节拍允许,优先 320。批大小在工控机上设 8 或 16,再大内存吃紧。训练时批大小 32,配合学习率 1e-3,是比较稳的起点。
注意:别在训练时用 224、推理时用 320,尺寸不一致会让精度掉得莫名其妙。训练和推理的预处理必须完全一致。
4. 产线落地:从模型文件到分选执行机构的对接
模型在笔记本上跑通只是第一步,真正难的是把它接到传送带、气阀和工控机上,让每一片叶子在正确的位置被吹走或放行。
4.1 推理服务化:Flask 还是直接 C++ 嵌入
常见做法有两种。一种是 Python 起一个 Flask 或 FastAPI 服务,工控机上的采集程序通过 HTTP 发图,服务返回类别和坐标。优点是开发快,缺点是 HTTP 开销在 5 帧每秒下还能忍,再高就不行。另一种是用 OpenVINO 或 TensorRT 把模型转成 C++ 推理引擎,直接嵌进采集程序,延迟能压到 5 毫秒以内。我一般先上 Flask 验证流程,跑通了再转 C++,别一上来就啃推理引擎,容易卡在环境配置上。
下面是一个最小 Flask 推理服务的代码:
from flask import Flask, request, jsonify import torch, io from PIL import Image import torchvision.transforms as T app = Flask(__name__) model = torch.load("mobilenetv3_leaf.pt", map_location="cpu") model.eval() # 预处理必须和训练时一致 preprocess = T.Compose([ T.Resize((320, 320)), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) @app.route("/predict", methods=["POST"]) def predict(): file = request.files["image"].read() img = Image.open(io.BytesIO(file)).convert("RGB") tensor = preprocess(img).unsqueeze(0) # 加batch维度 with torch.no_grad(): logits = model(tensor) prob = torch.softmax(logits, dim=1) cls = torch.argmax(prob, dim=1).item() return jsonify({"class": cls, "confidence": prob[0][cls].item()}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)逻辑说明:preprocess里的Resize和Normalize必须和训练脚本逐字一致,均值方差用 ImageNet 的,因为骨干是预训练的。unsqueeze(0)是给单张图加批维度,模型 forward 要求四维输入。参数怎么改:如果换成 224 输入,改Resize即可,但记得重训或至少重新校准。端口 5000 如果被占,改成 5001,同时改采集端的请求地址。
4.2 执行机构:气阀延迟与坐标映射
检测框中心点从图像坐标转到传送带物理坐标,需要一个标定矩阵。常见做法是在传送带上放一张棋盘格,用相机拍一张,算单应性矩阵。然后根据传送带速度和相机到气阀的距离,算出延迟帧数。比如传送带 0.3 米每秒,相机到气阀 0.6 米,那就是 2 秒延迟。如果推理加通信耗时 50 毫秒,那气阀触发要再提前 50 毫秒。这个延迟算错,叶子就被吹到隔壁筐里了。
4.3 现场光照漂移的在线补偿
车间窗户透进来的自然光从早到晚色温变化能到 1500K,模型精度会掉。常见做法是在相机旁加一个白板,每半小时拍一张,算白平衡系数,在线校正输入图。或者干脆把窗户遮死,全用 LED 灯,省事但费电。我一般建议遮窗加 LED,因为在线白平衡校正本身也会引入误差。
提示:上线前用同一批叶子在早中晚各测一轮,记录精度波动。如果波动超过 3 个百分点,先查光源,别急着改模型。
5. 避坑与排查:鲜叶分选项目里最容易翻车的五件事
这一章按「现象 → 原因 → 解决」写,都是我在现场踩过的。
现象一:训练集准确率 99%,验证集只有 70%。原因:同一片叶子被裁成多张高度相似的图,分到了训练集和验证集,造成数据泄漏。解决:按「批次」划分,同一筐叶子要么全在训练集,要么全在验证集。用GroupShuffleSplit按采集批次分组。
现象二:模型在实验室准,到车间就乱判。原因:实验室用白色背景,车间用黑色传送带,背景分布变了。解决:采集时就用产线同款传送带和光源,别在实验室搭一个干净环境自欺欺人。
现象三:红变叶被大量判成正常叶。原因:红变叶样本太少,类别不平衡。解决:过采样红变叶,或在损失函数里给红变叶类别加权,权重设为其他类的 3 到 5 倍。别只靠数据增强,增强造不出真实的红变纹理。
现象四:推理服务跑几小时后变慢甚至卡死。原因:Flask 默认单线程,请求堆积;或者torch.no_grad()忘了加,显存/内存泄漏。解决:用gunicorn起多 worker,确认推理代码在no_grad上下文里,并定期重启服务。
现象五:气阀偶尔不吹或连吹。原因:坐标映射的延迟参数没算通信耗时,或者气阀响应时间没标定。解决:用高速相机拍一次吹气过程,量出从发信号到气阀实际出气的毫秒数,把这个值加进延迟补偿。
6. 把精度再抬两个点:测试时增强与置信度阈值调优
模型训完、产线跑通之后,如果还想把精度往上抬,最划算的两招是测试时增强和置信度阈值调优,不用重训。
测试时增强的做法是:推理时对同一张图做水平翻转和原图两次前向,把两个 softmax 概率平均,再取 argmax。代价是推理时间翻倍,但在 5 帧每秒的节拍下完全扛得住。我实测在鲜叶四分类上能涨 0.8 到 1.2 个百分点,尤其是单芽和一芽一叶这种容易混的类。
置信度阈值调优更直接:模型输出的 softmax 概率不是校准过的,直接取 argmax 会把一些低置信样本也硬分。做法是统计验证集上每个类别的置信度分布,找一个阈值,低于阈值的样本送人工复检或直接归为「不确定」。在产线上,「不确定」的叶子可以吹到另一个筐,回头再处理,比错分到特级里强。
下面是一段测试时增强加阈值过滤的代码:
import torch import torch.nn.functional as F def predict_with_tta(model, img_tensor, threshold=0.85): # img_tensor: 1x3xHxW with torch.no_grad(): logits_1 = model(img_tensor) logits_2 = model(torch.flip(img_tensor, dims=[3])) # 水平翻转 prob = (F.softmax(logits_1, dim=1) + F.softmax(logits_2, dim=1)) / 2 conf, cls = torch.max(prob, dim=1) if conf.item() < threshold: return -1, conf.item() # -1 表示不确定,送复检 return cls.item(), conf.item()逻辑说明:torch.flip(img_tensor, dims=[3])是水平翻转,和训练时的HorizontalFlip对应。两个概率平均后取最大。threshold=0.85是起点,具体值要在验证集上扫一遍,看误判率和复检率的平衡。参数怎么改:如果产线不想频繁复检,把阈值降到 0.75;如果特级叶不允许混入,把阈值提到 0.92。
最后说个习惯:我每次上线新模型前,都会留一筐「钉子户」叶子——就是那些人工都容易看走眼的单芽和一芽一叶混装——单独跑一遍,看模型判成什么。这筐叶子的结果比验证集准确率更能说明问题。希望帮到你。
本文还有配套的精品资源,点击获取