首页 / 资讯中心 / 文章详情

茶叶品种识别实战:RetinaNet+GHM细粒度目标检测全流程解析

茶叶品种识别实战:RetinaNet+GHM细粒度目标检测全流程解析 ★ FEATURED ARTICLE
几个月前在做茶叶品种智能识别项目时模型选型最终落在RetinaNet上骨干是R50加FPN训练阶段用GHM梯度调和机制替换了更常见的Focal Loss同时借助COCO预训练权重完成迁移学习。很多人听到茶叶品种识别的第一反应是这不就是个图像分类任务吗拿个ResNet训一下不就行了真正上手才发现完全不是这么回事。现场拍回来的照片里一筐茶叶可能同时混着好几类品种需要先把每个可辨识的叶片区域定位出来再给出品种标签而很多品种在外观上的差异小到要对着光仔细看才能分辨。这本质上是目标检测 细粒度识别的复合问题。这篇把从选型、数据准备、模型配置、训练排坑到部署的完整过程梳理一遍给同样在做细粒度检测落地的小团队和个人开发者一个可以直接参考的技术路径。1. 茶叶品种识别难在哪任务边界与细粒度痛点1.1 先从需求里拆出三个硬约束我拿到这个项目时第一件事不是选模型而是把需求拆成能落地的技术约束。原始需求只有一句话做一个能识别茶叶品种的系统拍一张照片告诉我是哪个品种。这句话听上去简单真拆开之后至少有三个硬约束。第一个约束是必须检测加分类同时完成。拍摄场景通常不是单叶片干净背景而是散装茶叶铺在盘子里、竹匾上甚至直接装在袋子里的样子。不同品种的叶片堆叠在一起有些是完整叶片有些是碎片如果只给整张图一个标签模型根本无法应对一张图里有多个品种的真实情况。所以输出必须是若干个检测框每个框带品种类别和置信度。第二个约束是品种差异极其细微。茶叶是标准的细粒度对象例如某些早生品种和群体种在干茶状态下只能通过叶形宽窄、叶缘锯齿深浅、芽毫密度和色泽差异来区分。这类特征在深度学习模型眼里和噪声差不多常规的分类网络很容易把注意力放在背景、容器边缘这些无关信息上。第三个约束来自部署环境。项目要在现场设备上实时跑不可能靠一台八卡服务器慢慢推理。设备算力有限推理速度有硬性要求这直接决定了模型结构不能选得太重。把这三个约束摆出来后面的选型逻辑就清楚了单张图里多个目标、目标尺寸偏小且有遮挡、品种特征极度精细、推理速度受限。这套约束组合下来基本上排除了纯分类网络也排除了重型两阶段检测方案方向指向了单阶段检测器。1.2 细粒度问题为什么会让常规分类器失效细粒度识别的难点本质上不是能不能区分而是区分信号太弱。拿两个容易混淆品种来说它们之间的差异可能集中在叶片的某个局部区域比如锯齿密度、主脉走向、叶背绒毛。全局特征提取之后这些局部差异在特征图里往往被淹没在整体纹理里。更麻烦的是类内差异有时候大于类间差异。同一品种在不同采摘期、不同杀青工艺下做的干茶颜色能从翠绿变黄褐叶形从卷曲到舒展而不同品种如果在同一批工艺下加工外观反而趋同。模型必须学会忽略采摘期、工艺、光影这些干扰因素只聚焦在品种本身的形状和结构特征上。检测和分类耦合在一起还会引入一个新问题如果先用一个分割或裁剪模块把叶片抠出来再送给分类器抠图的误差会直接传导到分类环节。叶片边缘切得不整齐、背景大面积残留分类器就会学到带背景的种类特征在真实场景中泛化失败。所以让检测头和分类头共享特征、端到端训练是更稳妥的做法。密集和遮挡也是常事。干茶在盘中是层层堆叠的大量小叶片彼此重叠目标小、数量多。这要求模型在特征提取阶段就有足够的多尺度能力不能只依赖最后一层高维特征。这也是后面选FPN而不是单层特征图的直接原因。2. 模型选型实录为什么最终是RetinaNet不是YOLO也不是两阶段2.1 一阶段和二阶段之争在这个场景里变成了取舍问题目标检测领域长期以来有一条普遍经验两阶段模型精度高一阶段模型速度快。两阶段靠第一个阶段生成候选区域第二个阶段对每个候选区域精细分类精度天花板确实高但候选区域生成本身需要额外计算而且在叶片密集重叠的场景里成百上千个候选框的计算量是重复浪费的。一阶段模型直接在特征图上回归类别和位置速度上有天然优势早年的代价是精度明显落后。RetinaNet在这个问题上的贡献是把一阶段的精度拉到接近两阶段水平而且没有破坏速度优势。它的做法不是改进网络结构而是改进损失函数——用Focal Loss把一阶段检测器最大的病根给治了。我的判断是两阶段模型在茶叶这类密集小目标场景下精度优势会被密集候选框的噪声抵消一部分而速度劣势会被现场部署环境放大一阶段模型只要样本不平衡问题解决到位精度完全够用。RetinaNet恰好是这个平衡点。2.2 Focal Loss解决的核心问题正负样本极度不平衡单阶段检测器在训练时会生成海量锚框一个600×800的输入图经过特征金字塔每个位置配9个锚框算下来十几万个候选框是常有的事。这里面真正和茶叶叶片有交叠的正样本往往只有几百个剩下全是背景负样本。普通交叉熵损失下负样本数量太多梯度被它们统治网络根本学不到什么是茶叶。Focal Loss的思路很直接给置信度高的样本降权把训练重心移向置信度低的难样本。它的公式核心是因子(1 - p_t)^γp_t是模型对正确类别的预测概率。用一个具体数字感受一下设γ2一个样本预测概率p_t0.9它本来损失就小权重再乘(0.1)^20.01一个样本p_t0.5权重是(0.5)^20.25。相当于难样本的相对贡献被放大了25倍。这个机制对茶叶识别非常关键。因为叶片和背景边界模糊大量背景框和叶片框的特征差异非常小某种意义上它们不是易分样本而是边界难样本。Focal Loss会把这类样本的梯度留住而不是像普通交叉熵那样被海量背景框淹没。2.3 对比YOLO和SSD后为什么没选它们做选型时我对比过几条技术路线不是纸上对比而是拿初版数据集做了小规模训练测试。YOLO系列的优势是部署生态成熟转ONNX、转移动端都很方便速度也快。但我在测试中发现YOLO对密集小目标的细粒度识别有点吃力。茶叶叶片尺寸小、数量多YOLO的检测头设计更偏向中大型目标堆叠状态下漏检率偏高。而且YOLO在细粒度类别特征上的表征能力偏弱训练起来对数据量要求更高。SSD是经典单阶段方案但它的默认框设计比较简单多尺度能力靠的是不同特征层各管一摊层与层之间的信息没有融合。茶叶品种识别里很多特征其实是跨尺度的——大叶片上也有细纹理小叶片也有整体轮廓。SSD在这个问题上先天吃亏。RetinaNet的FPN把深层语义和浅层纹理做了融合每一层都能同时看到轮廓和细节加上Focal Loss解决样本失衡整体测试效果最稳。需要说明的是这不是说RetinaNet全面优于YOLO而是这个场景的约束条件密集、细粒度、小目标、部署速度恰好是RetinaNet的优势区间。3. R50-FPN骨干配置从特征能力到锚框尺度的关键参数3.1 为什么ResNet-50是起步不亏的选择骨干网络选型时我在ResNet-50和ResNet-101之间犹豫了一阵。理论上越深的网络特征表达越强但对于茶叶品种识别这个任务数据集规模也就是一万张上下深度太大反而容易在微调阶段过拟合把训练集里的光影、摆放方式背下来。ResNet-50的容量对细粒度任务来说足够计算量也更适合部署。还有一个现实考虑是预训练权重。深度学习项目里骨干网络用COCO预训练权重几乎属于默认操作。ResNet-50的预训练权重在各类开源实现里都是标配加载方便踩坑少。如果换成更新颖的骨干预训练权重不齐就得自己去适配这属于项目风险。训练时我把ResNet前两个stage适当冻结了。原因是前几层学到的是边缘、颜色、局部纹理这类底层通用特征COCO上已经学得足够好微调阶段不需要大幅改动后面几个阶段才需要针对茶叶品种的细粒度差异做适配。3.2 FPN做多尺度融合具体配置怎么设FPN是RetinaNet能同时处理不同尺寸叶片的关键。ResNet-50后几个stage分别输出C3、C4、C5这些特征图分辨率逐层降低语义逐层增强。FPN做的事情是从最高层开始把语义信息逐层向下融合让每一层既保留纹理细节又具备语义信息。RetinaNet的FPN会输出P3到P7五层特征图分别对应stride 8、16、32、64、128。这意味着从很小的叶片碎片到占据画面较大面积的完整叶片都能有一个尺度合适的特征层去负责检测。每层特征图上每个位置配9个锚框3种宽高比乘以3种尺度覆盖不同形状的叶片。这里有个细节容易忽略茶叶叶片的长宽比差异其实不小有些品种是典型的细长形有些接近卵圆形。3种宽高比里我保留了0.5、1.0、2.0三种细长叶片基本靠ratio0.5的锚框来拟合。如果发现某些品种检测框拉成长条形优先检查锚框宽高比而不是急着调IOU阈值。3.3 输入尺寸不要盲目拉大分辨率输入分辨率直接影响小目标的检出率。茶叶识别要求模型看到叶片纹理的细节分辨率太低品种差异就被丢失了分辨率太高显存和推理时间会成倍增加。我最终采用了多数目标检测实验里常见的设置短边800长边不超过1333。在这个分辨率下单片干茶叶的宽度在图上大约占30到60个像素足够模型提取关键的叶缘和纹理信息。训练阶段我额外做了多尺度训练在一轮训练里随机把短边从640到1024之间取值。这样做的目的不是单纯增加数据而是让模型对不同尺度下的叶片特征都保持响应。推理阶段为了稳定性固定短边800。显存不够的时候优先降batch size不要动分辨率动分辨率对细粒度识别的影响远大于batch size。模型配置核心逻辑可以整理成下面这段示意去掉具体的代码框架封装细节# 模型配置核心逻辑示意 backbone dict( typeResNet, depth50, out_stages[1, 2, 3, 4], # 输出C2到C5 frozen_stages1 # 前两个stage冻结保留通用底层特征 ) neck dict( typeFPN, in_channels[256, 512, 1024, 2048], out_channels256, start_level1, # 从C2开始 extra_levels2 # 增加P6、P7覆盖stride 128 ) head dict( typeRetinaHead, num_classes12, # 12个茶叶品种 in_channels256, stacked_convs4, anchor_generatordict( ratios[0.5, 1.0, 2.0], strides[8, 16, 32, 64, 128] ), loss_clsdict(typeGHMC, bins10, momentum0.75), loss_bboxdict(typeGHMR, bins10, momentum0.75) )4. COCO预训练到茶叶微调迁移策略与数据集构建4.1 COCO预训练权重到底带来了什么标题里出现COCO数据集训练不是偶然这是整个训练策略里最影响收敛质量的一环。COCO数据集的80类物体覆盖了人、车、动物、家具、日常用品虽然和茶叶完全不搭边但COCO预训练让骨干网络学到了通用的形状、边缘、纹理和局部结构表达。茶叶叶片在COCO模型看来至少已经能被提取出一片有锯齿边缘的薄片这样的基础结构。从零训练RetinaNet在小数据集上几乎必过拟合尤其骨干网络层数越深越严重。用COCO预训练权重初始化之后模型已经具备通用特征提取能力微调只需要学习茶叶特定差异任务复杂度大幅下降。我的实际体会是同样60轮训练从零训练到后期验证集AP基本不再上涨而预训练微调版本在20轮左右就已经明显收敛。改类别数时有个关键操作。RetinaNet的分类子网最后输出的通道数是类别数×锚框数原来是80×9现在茶叶是12×9所以分类头最后一层权重必须重新初始化。加载预训练权重时如果发现形状不匹配的键不要直接跳过整个分类头只跳过形状不一致的最后几层其余层继续加载。回归分支完全不受类别数影响可以直接沿用。4.2 茶叶数据集的构建采集、清洗与标注数据集质量决定模型上限这句话在细粒度任务上尤其成立。我采集了12个品种每个品种覆盖多个采摘期、多种摊放状态、不同拍摄角度和光源最终整理出大约1.1万张有效图片。每类800到1200张按80%、10%、10%划分训练、验证、测试。测试集特意保证和训练集拍摄时间错开防止模型把拍摄环境特征当成品种特征。标注是细粒度检测项目里最容易被低估的工作。标注规范我在开工前就定死框必须贴合叶片最外缘不能随意留边叶片重叠严重但露出面积超过六成的也要单独标注多个叶片粘成一团看不清轮廓的直接删掉类别判断有争议的单独存一个文件夹最后抽样让懂茶的人复判。标注工具输出的格式我统一转成了COCO格式这样和预训练、评估代码的衔接最顺。这里有一个经验宁可每类少100张也要把标注一致性做扎实。细粒度品种的类间差异极其微弱如果两个标注员对同一个品种的框大小习惯不同模型会被迫学习框的尺寸差异而不是品种的叶片差异验证集精度会莫名其妙上不去。4.3 迁移微调的训练策略学习率和增强规则迁移微调最忌照搬COCO从头训练的超参。COCO数据集规模大初始学习率0.01是常规配置但茶叶数据集只有一万张同样的学习率很容易震荡。我把初始学习率降到0.0025大概是原来的四分之一优化器用SGDmomentum0.9weight_decay1e-4。训练60轮在第45和第55轮把学习率乘以0.1前500步做线性预热。数据增强方面我反而没有做得太狠。随机翻转、多尺度缩放、亮度对比度扰动这些常规操作就够了马赛克、随机擦除这类激进增强在细粒度场景下要慎重。茶叶品种特征本身是细微纹理差异如果增强太强把叶缘锯齿的像素块直接抹掉或替换成背景色模型学到的东西就失真了。我做过一次对比加入随机擦除后易混品种对的错误率反而上升。参考配置大概是这样的# 迁移微调阶段的核心超参 lr 0.0025 momentum 0.9 weight_decay 0.0001 batch_size 8 max_epochs 60 warmup_iters 500 lr_decay [45, 55] # 多尺度训练随机取短边长度长边限制在1333以内 short_side_range (640, 1024)5. GHM梯度调和机制把Focal Loss换掉之后的真实收益5.1 Focal Loss的遗留问题超参难调、离群样本被放大Focal Loss解决了样本不平衡但它本身有两个副作用。第一个副作用是超参多。α和γ的组合需要针对数据集反复试COCO上表现好的(0.25, 2.0)在茶叶数据集上不一定最优每换一个数据集都要重新调。第二个副作用更隐蔽Focal Loss对所有难样本一视同仁地放大权重但难样本里混着一些异常标注框、被严重遮挡的不可学样本这些样本也会被放大。训练到后期模型很容易被几个标注错误的框带偏表现为loss已经很低但验证集上某些类别的AP突然跳水。我最初用的是Focal Loss训练曲线在常规范围内波动但换了两次训练集版本之后发现每次都要重新微调α和γ。这个成本在实验阶段还忍得住到了持续迭代阶段就成了负担。5.2 GHM的底层逻辑用梯度密度做自适应加权GHM在做的事情是把Focal Loss的人工降权曲线换成数据驱动的自动加权。它的思路是把每个样本的梯度模长g算出来统计整个batch甚至整个训练集在g上的分布情况。g代表的是模型对这个样本的学习难易程度g很小时样本已经能被正确分类且信心很足g很大时样本几乎完全学不动中间区间才是真正值得继续学习的难样本。梯度密度GD(g)统计的是在某个梯度模长附近单位区间内聚聚了多少样本。密度高说明这类样本数量庞大权重就压低密度低说明这类样本稀少权重就提高。权重公式大致是N除以梯度密度N是样本总数。这样操作下来海量易分背景样本被自动压制而那些数量少但关键的难分品种样本被自动放大全程不需要人工调α和γ。GHM还分为GHM-C和GHM-R两个版本。GHM-C管分类损失GHM-R管回归损失。GHM-R额外处理了一个问题回归损失里梯度模长极大的样本往往是标注框偏差很大的离群框这些样本对定位学习是有害的GHM-R会对这部分样本做削峰处理让它们的梯度贡献降下来。5.3 实际配置和效果对比开源实现里GHM通常有两个超参bins和momentum。bins是梯度密度统计的直方图分箱数默认取10作用类似于把连续梯度范围切成10个区间来统计密度momentum是梯度密度估计的滑动平均系数默认0.75让密度统计更稳定不至于被单个batch的噪声带偏。这两个参数在茶叶数据集上直接沿用默认值就有效不用额外折腾。我做了Focal Loss和GHM的对比实验都用同一份茶叶数据集和同一套预训练权重。GHM版本在前20轮收敛速度略慢因为它的梯度密度统计需要一个估计周期但30轮之后验证集mAP反超整体大约提升了1个多点主要提升体现在易混品种对上的误分类减少。更让我看重的是稳定性GHM版本在后期训练中loss曲线更平稳偶尔加一批新数据再训练也不会出现mAP跳水。小规模数据下如果存在少量标注噪声GHM的耐噪优势会更明显。因为它自动压缩了梯度分布极端的样本等于在训练过程中做了一层隐式的标注噪声过滤。这一点在真实项目里很有价值因为数据集大概率不可能做到完美标注。6. 训练、评估与排坑mAP之外更要看混淆矩阵6.1 训练阶段需要盯住的三个观察点训练过程中我一般不只盯总loss而是拆开看分类loss和回归loss是否同步下降。如果分类loss已经稳定但回归loss还在抖动说明锚框的分类基本学明白了但定位还在摇摆这时候优先检查标注框的边缘一致性而不是调学习率。GHM开启后loss曲线会比Focal Loss略毛糙这是因为梯度密度的估计本身有随机性。如果发现loss完全拒绝下降先确认梯度密度统计的momentum参数是否设得太低太大容易让密度估计滞后。还有显存不足时优先降batch size、保持输入分辨率这个前面说过细粒度特征经不起分辨率缩水。另一个观察点是训练早期的检测框输出。前几个epoch我会把模型输出可视化保存下来直接看锚框预测是不是乱飘。这一步能提前发现锚框尺寸和品种叶片尺寸是否匹配比看数字指标直观得多。6.2 评估不能只看mAP混淆矩阵才是细粒度项目的关键在细粒度识别里mAP对整体水平的度量是有效的但对哪些类别容易互相混完全没有诊断能力。我会额外画一张品种级别的混淆矩阵把验证集和测试集上每对品种之间的误判情况全部列出来。这张表才是项目里最有用的工具。看混淆矩阵的时候会自然暴露几个短板。某两个品种如果互相误判率显著偏高说明它们在模型特征空间里距离太近。这时候要回到原始图片里对比这对品种的干茶形态到底差在哪里看是叶色深浅、叶形宽窄还是芽毫密度。如果人眼都分不清单纯调模型参数没有意义正确做法是补充这两种特定状态的样本或者把这些品种归为待定类。我还习惯对测试集做bad case回看。把置信度最高的错误检测框全部导出按品种分组然后逐张看原因标注框标错了、目标被严重遮挡、还是拍摄光线导致品种特征丢失。这个环节能直接告诉你数据集下一步该补什么而不是闭着眼睛涨mAP。6.3 IOU阈值、置信度阈值与部署策略模型训练好了实际运行时还要选一组后处理阈值。置信度阈值我通常设在0.5附近低于0.4会带出大量背景误检框高于0.6又容易漏掉遮挡严重的小叶片。NMS阈值设在0.5到0.6之间。茶叶叶片密集重叠时普通NMS容易把相邻的不同叶片框压制掉如果发现重叠叶片漏检严重可以改成Soft-NMS让高重叠框不要被一刀切。我需要提醒一点验证集上的最优置信度阈值只是一个起点。现场部署环境的拍摄距离、光线、相机参数和测试集不同阈值要留出调节接口。我在推理框架里把置信度阈值和NMS阈值都做成了运行时参数方便现场直接改不用重新导出模型。6.4 三个实际踩过的坑第一个坑是类别不均衡。某个品种收集时间晚只有300多张图且背景大多是同一个拍摄台面训练后这个品种AP明显低于其他类。解决方式不是简单复制图片而是给这个品种补拍不同环境的照片同时采样时把它的概率提高。第二个坑是标注框习惯不统一。不同人画框时有人喜欢紧贴叶片有人喜欢留一圈空隙模型学出来的定位标准就会漂移。后来我把标注规范细化到叶片最外侧像素即为框边界并且每批标注结果抽检情况明显好转。第三个坑是数据集环境偏差。早期数据集中在固定光源下拍摄换到自然光环境后模型瞬间被光影变换打蒙验证集AP掉了七八个点。补了一批现场自然光照片再训练才把问题压下去。这个坑在细粒度项目里特别容易踩因为模型会优先用背景和光照这些简单特征来区分品种一旦环境变化就露馅。7. 部署落地思考推理优化与后续迭代方向7.1 从PyTorch到ONNX再到TensorRT的加速链路训练完成后的模型是PyTorch格式直接部署在工业设备上跑不够快。我的做法是先把模型导出成ONNX再转到TensorRT生成推理引擎。RetinaNet结构规整导出过程基本顺畅需要注意的就是动态输入尺寸和锚框生成这些操作尽量在导出前固化为静态图避免转换过程中算子支持出问题。在RTX 3080上跑FP16推理单张图的耗时从最初的约100毫秒降到20毫秒左右已经能满足现场实时检测的需求。如果后续换到更低功耗的边缘设备瓶颈主要落在ResNet-50骨干的卷积计算上FPN和检测头的开销占比反而小。所以优先优化的方向是骨干而不是检测头。混合精度推理时要验证输出是否和FP32有可感知的精度差距。细粒度识别对特征图的微小变化敏感FP16偶尔会让Web角落的低置信度检测框权重发生偏移所以我在导出后专门跑了一遍测试集确认mAP几乎无变化才放心上线。7.2 后续迭代方向主动学习、蒸馏和易混类专项这个项目做到稳定运行之后我留下了三条持续迭代的路线。第一条是主动学习把模型预测时梯度密度高、置信度又摇摆的样本定期导出让人工挑拣后补充标注。这比随机补拍数据的效率高很多能精准覆盖模型的薄弱区域。第二条是模型蒸馏RetinaNet这套R50-FPN结构在边缘设备上还是偏重后续考虑把检测能力蒸馏到一个轻量骨干上满足移动端识别需求。第三条是易混类专项训练混淆矩阵里的高危品种对单独挑出来用双分支或辅助属性标注的方式增强区分力。如果现在让我重新做一遍这个项目我会在采集数据阶段就多留一个心眼现场环境照片的比例至少要占到一半。模型本身的结构选型其实没有太多秘密RetinaNet、R50-FPN、GHM、COCO预训练这些都是一套成熟组合真正决定项目成败的往往是数据分布是否贴近真实场景、标注是否一致、评估是否看懂混淆矩阵。GHM带来的提升不是颠覆性的但它让我换数据集、调超参的时候省了很多事。做这类细粒度识别别急着上最复杂的模型先把分布、标注、评估这三件事做扎实。
阅读完成 · 觉得有帮助?
咨询建站