资讯动态

茶叶品种识别实战:RetinaNet+GHM细粒度目标检测方案

发布时间:2026/10/20 3:11:08 来源:尧图企业网站定制
做茶叶品种识别这件事起因是某茶厂想减少人工分拣的成本干脆用摄像头加算法代替老师傅的肉眼判断。真正上手之后才发现茶叶识别跟普通物体检测完全是两回事——叶片和叶片之间的差异细微到很多有经验的人都说不清光照一变、背景一换模型表现就整个垮掉。我最终确定的方案是RetinaNet主框架、ResNet50骨干、FPN特征金字塔把分类损失改成GHM梯度均衡机制并且先用COCO数据集把骨干和FPN“喂熟”再迁到自建的茶叶数据上——也就是标题里R50-FPN-GHM模型的完整落地。这篇文章按“选型→数据→实现→训练→部署”的顺序把所有关键过程和踩过的坑都写出来给同样在做农业细粒度目标检测的人一个可复现的参考。1. 为什么是RetinaNet GHM需求拆解与方案选型1.1 茶叶品种识别的真实场景难点先别急着选模型先看数据长什么样。我当时在茶厂拍回来的素材里随便挑一张都够头疼采摘期叶片密密麻麻挤在一起前后遮挡严重同一片叶子正反面、老嫩状态在颜色和纹理上差异极大大棚内外的光照条件动不动差两个数量级还要面对雨雾、灰尘、运输中的轻微磨损。这些干扰叠加起来模型要提取的不是“有没有一片叶子”而是“这片叶子属于哪个品种、处于什么形态”——这就是典型的细粒度识别加密集小目标检测。更麻烦的是类别不平衡。某个主要品种在画面里占了六成稀有品种可能只出现个零头。用常规单阶段检测器去训很快会发现稀有品种几乎不被召回因为训练时简单负样本和正样本的比例悬殊损失被大量“容易判对的背景”完全淹没。这时候再看YOLO、Faster R-CNN、RetinaNet这些方案各自的短板就很明显了。1.2 YOLO、Faster R-CNN 和 RetinaNet 怎么选方案速度精度正负样本处理在茶叶场景的坑YOLO系列快中等简单分配靠后处理筛密集遮挡场景召回偏低细粒度不够Faster R-CNN慢高RPN先粗筛两阶段配置复杂推理难上低成本边缘设备RetinaNet较快高Focal Loss自动压简单样本需要认真调anchor和数据质量当时我也纠结过要不要直接上Faster R-CNN。它的精度确实稳RPN阶段相当于先做一轮粗筛分类头面对的目标质量更高理论上很适合细粒度任务。但它推理慢两阶段的工程复杂度也高最后做成落地产品会很难受。YOLO则是反过来的问题速度漂亮、部署生态成熟可一遇到互相遮挡的密集叶片召回率就开始掉后续再怎么调也追不回来。RetinaNet刚好卡在中间——单阶段结构保证推理速度同时用Focal Loss解决正负样本极度不平衡的问题。尤其对茶叶这个场景模型思路很对路用密集的anchor覆盖各个位置和尺寸让分类头自己去判断“这块区域是不是某个品种的叶子”而不是依赖一个单独的候选框生成阶段。实测下来相同算力下RetinaNet的AP往往比同期YOLO高而且对稀有品种更友好。1.3 GHM梯度均衡机制为什么更适合茶叶数据RetinaNet的原始损失函数是Focal Loss形式上是给交叉熵损失加一个调制因子FL -α(1-p_t)^γ log(p_t)。γ越大对容易样本的压制越强。听起来干脆但真用起来有个烦人的问题γ是拍脑袋定的。同一批数据γ2可能不错γ2.5就明显收敛不稳换一组光照更复杂的数据原来的γ又不行了。GHM的思路完全不同。它不假设“简单样本”和“困难样本”的固定比例而是直接统计当前batch里每个样本梯度模长的分布密度。梯度模长g越小说明样本越容易越容易的样本往往数量越多所以密度大。GHM会计算每个样本所在梯度区间的拥挤程度用总样本数除以密度得到权重β。密度大的简单样本权重被压得很低密度小的困难样本权重自动升高。这样做的好处是几乎不用额外调超参数就能自适应地解决不平衡问题。对茶叶数据尤其关键两个品种叶片轮廓很像模型一开始根本分不清哪些难哪些易梯度密度能自动把“难分的那批叶子”权重拉高而不是靠设置一个固定γ来碰运气。2. 茶叶数据集构建与预处理占项目80%工作量的部分2.1 采集策略决定模型上限的第一道关卡这个环节我犯过最大的错误就是一开始找茶厂工人用手机随便拍。背景干净、顺光、叶片摆得整整齐齐模型在验证集上看似还行一上现场摄像头就全线崩溃。后来总结出的采集原则是不要刻意拍好看要拍模型将来会“觉得难受”的画面。具体操作上可以从这几个方向铺数据。第一是机位和角度俯拍、斜拍、近距离特写全都要有叶片检测框不能只在固定画面中心。第二是光照晴天直射、阴天散射、室内灯光、早晚色温差异都得覆盖最好在标注文件里记录光照条件后面可以做条件增强。第三是环境背景干净的白色托盘、输送带、纸箱、桌面的木纹背景变化越大模型泛化能力越好。第四是目标形态完整叶片、半片叶子、芽叶共生、被遮挡的叶片都要出现。数量上我的体感是一个品种至少要保证实际标注框数不低于800个稀有品种不要低于400个。如果某个品种连400个都凑不齐宁可先做数据增强把它撑起来也不要硬上模型。把框数当第一指标而不是图片数因为图片多不代表正样本多一张密集叶片的图比十张只有单片叶子的图标注价值高得多。2.2 标注规范和格式转换错框比少框更致命标注这件事最容易出问题。茶叶数据的框一般分成三类单芽、单叶、芽叶组合。开标前必须定死规则比如“框必须贴住叶子外轮廓芽尖要包含在内”“遮挡超过一半的不标”“两片叶子分不开时宁可不标也别画一个大框把两片都框进去”。提示宁可漏标也不要画“包含多片叶子的大框”。漏标最多影响召回率错框会让回归分支学到错误偏移后面怎么调都救不回来。标注格式我统一转成COCO的JSON结构原因是RetinaNet的数据接口基本都兼容这个格式。转换脚本大约是这样# 将txt/yolo格式标注转为COCO JSON的常见处理逻辑 # 1) 读取图片尺寸并写入images字段 # 2) 读取类别标签构建categories字段 # 3) 读取标注框转成[x, y, w, h]写入annotations字段 # 4) 检查框是否越界、宽高是否为0有问题的直接剔除转完格式后建议统计一下每类框的数量分布画出直方图。这个十分钟就能做好的动作能帮你很快发现“标注比例是不是本身就不平衡”还是“训练抽样导致的不平衡”。后面选损失函数时这个分布图就是最重要的依据。2.3 数据增强多尺度、颜色扰动与语义安全边界增强策略要跟数据特征对齐。茶叶的真实场景里尺度变化极其剧烈同一个摄像头画面近处的叶子能占满半个屏幕远处的一片叶子可能只有30×30像素。所以多尺度训练是刚需我当时把图像的短边在640960之间随机缩放配合随机裁剪相当于让模型反复适应各种尺度的目标。颜色和亮度扰动也很有效。茶叶颜色偏绿偏褐不同光照下整个颜色分布会整体偏移用随机亮度和饱和度扰动效果非常明显。但有一个安全红线不要在叶片区域做太强的颜色通道互换。比如把绿色通道和蓝色通道交换人眼都觉得是“假图”模型学到的是大量无效特征。翻转策略上横向翻转、纵向翻转对于绝大多数茶类品种是安全的因为品种识别不依赖叶片朝向。但如果你后面要做“芽叶相对位置”这种方向性判断纵向翻转就尽量慎用。随机遮挡类增强比如CutOut在茶叶场景里我一般关掉因为把叶片中间抠掉一块模型很容易把“带孔的叶子”当成一个新目标增加混乱。3. 模型实现R50-FPN-GHM 的核心代码与参数3.1 ResNet50骨干加FPN特征金字塔的结构要点RetinaNet的骨干部分是ResNet50作用是从输入图里逐层提取语义特征。ResNet50分成多个阶段每个阶段输出的特征图分辨率逐层减半语义信息越来越强、空间信息越来越弱。FPN的活儿是把这些不同分辨率的特征图横向连接起来让高层语义和低层位置信息互相补充。标准的RetinaNet用FPN在P3到P7这五层输出每层对应的stride分别是8、16、32、64、128。小目标基本靠P3、P4这两层去检测因为它们的特征图分辨率高还能保留下小目标的细节位置。我当时在R50骨干上做了轻微的通道统一C3到C5输出经过1×1卷积降到256通道再两两上采样相加得到FPN的P3到P5P6和P7则由P5通过步长为2的卷积继续下采样得到。这样五个level的输出通道都统一到256后续分类头和回归头可以共享一套结构。金字塔层stride对应骨干阶段主要检测目标P38C3小叶片、远处目标P416C4中等叶片P532C5大片叶片、近景P664卷积下采样更大目标P7128卷积下采样极端大目标3.2 Anchor分配、正负样本划分与Head结构anchor是RetinaNet的另一个关键设计。每个金字塔层预设一组固定尺寸和长宽比的候选框模型预测的不是“框在哪”而是“这个候选框离真实目标差多少”。我在每层用了3种尺寸、3种长宽比也就是9个anchor覆盖32×32到512×512的范围保证密集叶片在各种尺度都能被至少一个anchor稳定覆盖。正负样本分配我按常见规则处理与真实框IoU大于0.5的anchor视为正样本IoU小于0.4的视为负样本介于0.4到0.5之间的不计入损失。这里的0.4/0.5阈值对密集目标很重要。茶叶叶片贴得太近如果把阈值放松到0.4以下出来的正样本框全是糊的回归分支会学到混乱位移阈值太严格重叠叶片上的正样本又太少模型学不动。分类子网络和回归子网络是RetinaNet里最有辨识度的部分。两个子网络各自由4层3×3卷积加上最后一层1×1卷积组成分类头输出维度是num_classes×9回归头输出4×9。每一层金字塔共享同一套子网络权重。这样做参数少、训练效率高缺点是这个单一尺度的卷积建模能力有限所以对数据多样性要求更高。3.3 GHM Loss实现细节与工程化替代GHM的工程实现看似不难实际细节不少。核心是计算每个样本的梯度模长g然后统计梯度密度再给每个样本分配权重。分类分支上对于预测概率p梯度模长是g |p - y|。真实标签y为1时p越接近1g越接近0样本越容易y为0时p越接近0g也越接近0这些大概率是大量容易的负样本。我写一个简化版的梯度密度计算来做说明import numpy as np def gradient_density_weights(g_list, n_total, bin_size0.02): g_np np.abs(np.array(g_list)) # 把[0, 1]区间切成50个bin统计落入每个bin的样本数 hist, edges np.histogram(g_np, binsnp.arange(0, 1.0 bin_size, bin_size)) densities [] for g in g_np: idx int(g // bin_size) if idx len(hist): idx len(hist) - 1 # 单位区间内平均样本密度 gd hist[idx] / (n_total * bin_size) # GHM公式权重 总样本数 / 当前样本所在位置的密度 beta n_total / gd densities.append(min(beta, 100.0)) # 上限防止梯度过大爆炸 return np.array(densities)这个直方图版适合用来理解和调试正式训练我建议换成EMA平滑的方式因为训练过程中梯度分布是动态变化的直接用当前batch算出来的hist变化太大loss会抖得厉害。这也是GHM论文里强调过的问题梯度密度的估计本身需要足够稳定的统计量。GHM接入RetinaNet的方式也很直接分类头输出后先做sigmoid得到p再用GHM算出每个样本的权重代替Focal Loss里的固定调制因子然后和交叉熵逐样本相乘。回归分支我保留了原始的smooth L1实测里GHM-R的收益不明显而且实现复杂度高为了项目进度没上。如果你对回归离群点特别敏感后面可以再试GHM-R。4. COCO预训练、迁移学习与训练调试4.1 用COCO喂熟骨干为什么不建议从头训说到COCO数据集训练很多人以为是拿COCO来当茶叶数据用其实不是。COCO在这里的作用是给骨干和FPN提供一个通用的视觉先验。R50骨干在ImageNet上预训练能识别基本纹理和边缘而COCO是目标检测数据集微调过骨干之后FPN和anchor预测头已经学会“如何找到框、如何判别前景背景”。这层能力迁移到茶叶场景时即使是完全没见过的叶片目标初始框的命中率也会高不少。实际操作上我先用COCO的train2017跑了一轮完整的RetinaNet基线。默认类别数是80完整训练到收敛需要很长时间所以我当时的做法是直接下载社区常见的COCO预训练权重把分类头的输出通道从80改成茶叶类别数再用加载到的权重初始化整个模型。加载时有一个高频坑分类头的权重维度对不上要求所有参数都匹配的严格模式会直接报错。正确做法是用非严格模式只加载名字对应的backbone和FPN部分分类头保持随机初始化。写代码时用映射把预训练权重里带前缀的键名对齐到当前模型别偷懒用裸的state_dict直接load。4.2 迁移学习的实际配置与超参数选择从COCO迁移到茶叶数据不能沿用原来那套激进的学习率。COCO上我习惯用初始学习率0.01往下的SGD但茶叶数据量只有COCO的零头保持0.01起步很容易在第一批数据上被一个困难样本带飞。我最后定下来的配置是batch size 8初始学习率0.002warmup 500步之后按步数余弦衰减到0.0002优化器是SGD加0.9的动量。冻结策略上第一轮我把R50的前两个stage冻结只训FPN和两个Head。这么做的好处是避免预训练特征在数据量不足时被快速破坏。等第一轮的loss稳定之后再解冻全部层把学习率降到0.0005继续精调。整个过程我记录了四个关键日志总loss、分类loss、回归loss和每类AP。训练中如果发现总loss下降但分类loss不降通常是类别不平衡问题没被GHM完全消化这时要回头检查增强策略里是不是某种类别被过量放大。4.3 训练过程复盘loss曲线和问题排查实录我把训练中遇到过的三个典型问题整理出来算是这段项目最有价值的部分。现象排查方向当时的解决办法总loss很快收敛但val AP很低多半是过拟合或数据标注分布畸形暂停训练画每类框数直方图发现问题品种框太少对该类别做针对性过采样分类loss降到很低回归loss反复震荡anchor分配阈值可能太宽或回归目标里有错框把正样本IoU阈值从0.5提到0.55回归loss稳定性明显改善稀有品种完全不召回类别不平衡没解决或anchor覆盖率不够先确认GHM权重正常再把该品种的图片做多尺度复制增强最后把它的采样权重调高还有一个我强烈建议所有项目先做的小实验拿30张图、每个类别至少10个框先把模型训练5轮观察loss能不能明显下降、检测框能不能画到目标附近。这种“冒烟测试”几分钟就能跑完能过滤掉绝大多数代码和标注上的低级错误。不要一上来就拿全量数据训两三个小时然后才发现数据格式错了太亏。5. 评估指标、部署要点与局限性复盘5.1 别只盯着COCO风格mAP细粒度场景要看什么COCO的标准评价指标是mAP0.5:0.95在IoU从0.5到0.95的十个档位取平均对框的定位精度极其苛刻。茶叶场景里两个品种的叶片边界本来就有一定主观性标注人员画框时差两三个像素是常态硬套这个指标会把项目带偏。我的做法是分两层评估。第一层看AP0.5和召回率这一档IoU更符合“框大概框住目标”的工业落地需求第二层单独统计小目标、中目标的AP因为叶片在真实画面里的尺度分布太广总体AP高不代表小目标表现好。另外务必画混淆矩阵。当时我以为某个稀有品种识别效果不错一看混淆矩阵才发现它一半被认成了主要品种结合GHM的梯度密度分析才发现它对“颜色偏绿、轮廓偏宽”的困难样本权重其实偏高但是这些样本本身信息量不足导致过拟合到错误特征上去了。5.2 边缘设备部署模型变小但别削掉GHM部署环节我的目标是跑在低成本的边缘GPU设备上所以基本绕不开两件事模型量化和推理引擎选型。RetinaNet本身是个单阶段模型但FPN和几个Head加起来前向计算量也不小。量化的收益很明显FP32换成FP16几乎无损精度换成INT8精度会掉一些但速度能再翻倍。部署前一定要把训练时的预处理和推理时的预处理对齐。数据增强里那些随机翻转、随机裁剪在训练时是帮助模型泛化的推理时必须关掉。NMS的阈值也要单独调我沿用训练时的0.5置信度阈值结果端上漏检一堆重叠的小叶片把阈值降到0.3后才正常。GHM这个损失函数是训练阶段的组件部署时完全不参与前向计算所以不用纠结推理模型里是不是“包含”GHM。真正要关心的是分类头和回归头导出的数据格式是否清晰、NMS后处理逻辑是否正确。把模型导出成通用推理格式后我建议用几段真实摄像头视频做对比不要只用验证集图片测试因为现场视频的帧间抖动和运动模糊是静态图片覆盖不到的。5.3 局限性与后续扩展这个方案的边界任何项目都有适用边界。RetinaNet在茶叶这类密集小目标上表现不错但它对训练数据的要求很高——每类至少几百个框只是起步如果品类增加到20种以上数据成本会线性上升。另外一个明显的短板是推理速度。虽然单阶段已经比两阶段快但相比主流的实时检测框架还是有差距如果之后要做实时视频流级联分析可能要把骨干换成更轻量的结构或者用蒸馏把FPN和Head压缩成学生模型。这些都可以作为后续扩展方向给品种识别叠加质量分级同一片叶子既识别品种又评估完整度或者把识别结果跟产线追溯系统打通做成茶叶管理平台的数据入口。我个人比较看好的是把GHM的梯度密度思想移植到其他任务上比如茶汤颜色分级或包装瑕疵检测这套“先统计样本分布再自动加权”的思路在很多工业视觉场景里都值得一试。最后说一点个人体会。这个项目真正花时间最多的不是模型结构而是数据体检和训练现场调试。R50-FPN-GHM的组合确实省心GHM让我几乎不用纠结γ怎么调COCO预训练把收敛速度拉高了很多但如果没有那几十轮盯着loss曲线、挨个看检测框的笨功夫再好的结构也会被脏数据坑掉。如果你也在做类似的农业细粒度识别项目我的建议是开工前先花两天时间把样本分布、标注质量、光照分布这三件事打扎实模型可以慢慢选数据不行什么都白搭。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑