做青香蕉尺寸分类和检测这件事,一开始并没有想象中那么“高大上”。说白了就是在分选产线上,把一堆青香蕉按大小分级,顺便把那些小尺寸的“香蕉手”(一簇连在一起的香蕉,行业里习惯叫“手”)单独挑出来。听起来像是个挺简单的视觉任务,实际做起来才发现:青香蕉颜色单一、表面反光、果手之间互相遮挡、尺寸跨度又大,用普通的目标检测模型很容易在“小目标”上翻车。所以最后技术方案落在了 Cascade R-CNN 配 HRNetV2p-W40 骨架上,跑 COCO 预训练权重再做迁移微调,训练 20 epochs 左右拿到了一版能上产线的模型。
这篇文章就把这个项目从思路拆解、原理补全、数据标注、训练调参到踩坑实录完整写一遍。不管你是刚接触检测模型的小白,还是已经在做农业视觉项目的开发者,里面涉及的参数取舍、数据标注细节和排查方法,应该都能直接参考。
1. 项目概述与核心需求拆解
1.1 为什么需要给青香蕉做尺寸分类
先说背景。青香蕉和熟香蕉不一样,熟香蕉在消费端按颜色、果型、成熟度分级,场景相对标准;但青香蕉是加工原料,主要流向催熟库、淀粉厂、鲜切加工线,下游对尺寸有硬指标。比如做香蕉片的要求单根长度在 15~20cm 之间,做催熟出口的要按“手”的大小分箱,小果手和大果手价格能差出一倍。所以产线上必须先按尺寸把香蕉分成大、中、小几档,再进入不同下游环节。
传统做法是人工分选,几个人站在传送带两边,靠眼睛估长度、看果手大小,一小时能处理几百公斤,但疲劳之后误判率明显上升。尤其“小尺寸香蕉手”这种类别——它既可能是整串顶端发育不足的小果,也可能是断手后留下的残果簇,工人很容易漏看或分错。这就需要用视觉检测模型自动完成两件事:
- 把每一个香蕉手(或者单根香蕉)从复杂背景里检测出来;
- 根据检测框的尺寸信息,进一步划分成不同大小等级。
标题里“从小尺寸香蕉手识别到模型优化”说的就是这条主线:先保证小目标不漏检,再谈整体分级的精度。
1.2 技术路线选择:Cascade R-CNN + HRNetV2p-W40 的组合逻辑
选模型的时候其实列过好几个候选:YOLOv8、RTMDet、Cascade R-CNN、Mask R-CNN。单阶段检测模型速度快,但在小目标密集场景下精度吃亏;Mask R-CNN 多出来的分割分支对分选任务来说有点浪费算力。最后选 Cascade R-CNN 主要看中它对 IoU 阈值渐进式提高的训练策略——检测框质量更稳,小目标召回率不容易崩。
骨干网络选 HRNetV2p-W40 而不是 ResNet50,原因也很直接:青香蕉的表面纹理、果柄位置、果手轮廓需要较高分辨率的特征来支撑,HRNet 全程保持高分辨率特征图,相比 ResNet 这类先降分辨率再上采样的结构,对“小香蕉手”的定位信息保留要好得多。后面实测数据也证明了这一点:换掉骨干后,小目标类别的 AR@0.5:0.95 提升了 4~5 个点。
预训练用的是 COCO 权重,因为 COCO 里有大量通用物体检测知识,尤其是边缘、遮挡、纹理等底层特征,对农业场景的迁移帮助很大。具体配置文件对应的是 mmdetection 里cascade_rcnn_hrnetv2p_w40_20e_coco这条标准 pipeline,训练 20 epochs。这个配置不是拍脑袋定的,它是精度/速度性价比比较合适的一档,20e 的训练量配合 COCO 预训练,在自建数据上足够收敛。
2. 模型原理与关键设计解析
2.1 Cascade R-CNN 为什么比单阶段模型更稳
Cascade R-CNN 的核心思想是“级联优化”。普通 Faster R-CNN 只在 0.5 的 IoU 阈值下训练检测头,得到的框质量参差不齐。Cascade R-CNN 把检测头串成三级,每一级用更高的 IoU 阈值(0.5 → 0.6 → 0.7)训练,前一阶段输出的框送入下一阶段继续精调。
这个过程可以理解成“层层体检”:第一轮粗筛,把像是香蕉的区域框出来;第二轮按更高标准过滤,去掉边界不清晰的候选框;第三轮再精修,让最终框和真实果手轮廓贴合得更准。对应到青香蕉场景,效果是什么?果手边缘经常有一两根香蕉“伸出”主体轮廓,普通检测器容易只框住主体,边缘漏掉;级联结构会把边缘部分一步步修正回来,检测框更完整,后续按框尺寸分类就更可靠。
另外,Cascade R-CNN 的 RPN 阶段对“小尺寸香蕉手”还算友好。RPN 本身就是全卷积的滑窗机制,对小目标有天然的召回能力,只是后续分类/回归头容易在小目标上置信度不足。级联结构的高阈值训练恰好把这个短板补上了。
2.2 HRNetV2p-W40 骨干网络的高分辨率价值
HRNet 的全称是 High-Resolution Network,它和 ResNet 最大的区别是不走“编码器-解码器”路线,而是始终维护一个高分辨率特征分支,同时逐步增加低分辨率分支做多尺度融合。W40 代表宽度系数,通道数约为 40 的倍数,网络容量比 W18 更大,特征表达能力更强。
可能有人会问:只是检测香蕉,用 ResNet 不香吗?我一开始也这么想,但对比实验做下来,差距主要在小果手上。青香蕉表面有棱纹,未成熟的果皮和背景(叶片、藤蔓、纸箱)在颜色上很容易混在一起,ResNet 的高层特征图分辨率低,小果手可能只剩十几个像素,特征已经糊成一块;HRNet 保持 1/4 分辨率的大特征图,边缘信息保留更多,分类器能“看清”果柄和果身的交界,定位自然更准。
代价是计算量变大。HRNetV2p-W40 比 ResNet50 慢,显存占用也高一截。不过分选产线的工控机一般带得起一张中高端显卡,推理帧率不需要跑到实时视频流的程度,稳定性和精度优先级更高,这个取舍合理。
2.3 COCO 预训练权重的迁移策略
COCO 数据集里有 80 类日常物体,香蕉本身就在其中,但 COCO 的“banana”是单根熟香蕉,和我们的青香蕉果手完全是两种形态。那 COCO 预训练还有什么用?
关键在底层特征迁移。网络的前几层学到的是通用边缘、颜色块、纹理基元,这些知识在任何视觉任务里都通用;只有高层语义特征需要针对新数据重新适配。所以加载cascade_rcnn_hrnetv2p_w40_20e_coco预训练权重后,我们只需要:
- 修改数据集的类别数为 1(banana_hand)或 N 个尺寸等级类别;
- 保持骨干网络层参数冻结或低学习率微调;
- 检测 head 部分用更高的学习率从头训练。
如果你是从零训练同一个模型,要达到同样精度可能需要 5~10 倍的数据量,迁移学习把数据需求降到了一个农业项目团队容易满足的量级。这一步是必须做的,别省。
3. 数据准备与标注细节
3.1 青香蕉图像采集与数据集构建
数据采集我们是在模拟产线环境完成的,不是网上随便爬图。原因很简单:产线场景有固定的光照、传送带颜色、镜头高度,模型只需要适应这个场景就行,收集一堆差异过大的网络图片反而增加学习负担。
采集要点:
- 相机固定在传送带正上方,高度保证单根香蕉的最短边不少于 30 像素;
- 分别在早上、中午、傍晚采集,覆盖自然光和灯光混合的光照变化;
- 除了正常摆放,故意模拟产线上的“乱序”:果手叠放、部分遮挡、断手、带果柄的残果;
- 单一背景为主,但也要采集一些杂物背景(树叶、包装袋、落叶),防止过拟合到背景色。
总共采集约 3500 张有效图像。按 7:2:1 划分训练集、验证集、测试集,划分时保证同一串香蕉的不同图像尽量进同一个子集,避免数据泄漏导致指标虚高。
3.2 尺寸分类标签的确定与标注规范
尺寸分类标签这件事,比想象中麻烦。如果直接按“大、中、小”三个等级作为检测类别,模型既要学“这是什么”,又要学“这有多大”,类别间的差异又体现在框尺寸上,会导致部分样本在类别边界附近震荡。
我的做法是检测类别只设一种banana_hand,尺寸分级在检测框后处理阶段完成:
- 计算检测框的长边像素长度;
- 利用标定板建立像素到厘米的映射;
- 按长度区间分成三个等级:小(<12cm)、中(12~18cm)、大(>18cm)。
这个设计基于一个重要原则:检测是空间定位任务,分类是尺度阈值判断任务,两者解耦后各自的评估指标更清晰,调试时能直接定位是“没检测到”还是“分级分错”。
标注工具用的是 LabelMe,导出 COCO 格式 JSON。标注规范里特别约定:
- 必须框住整个果手,包括伸出来的单根香蕉末端;
- 严重遮挡超过 60% 的果手不标,当作忽略区域;
- 果柄是否计入检测框,全项目统一“计入”。
统一这些规则后,标注一致性明显提升,训练出来模型的边界框也更规整。
3.3 数据增强策略:在“不过拟合”和“够丰富”之间找平衡
农业检测项目容易忽视数据增强,结果训练集精度挺高,一到新场景就崩。我们用的增强策略围绕“产线变化”设计:
- 随机亮度抖动 ±20%,模拟光照波动;
- 随机饱和度抖动 ±10%,模拟青香蕉成熟度微小差异;
- 随机水平翻转,果手在传送带上方向本来就随机;
- 随机缩放(0.8~1.2 倍),模拟不同高度和镜头距;
- 不采用随机裁剪,因为产线场景中香蕉通常完整出现在视野内,裁掉一部分会引入不真实的样本分布。
增强后的训练量大约扩大 4 倍,配合 Cascade R-CNN 的相对大模型容量,没有出现过拟合迹象。验证集 loss 和训练集 loss 的差距一直稳定在合理区间。
4. 训练配置与参数调优实录
4.1 环境配置与依赖安装
训练环境用的是一台单卡机器,GPU 显存 24GB(实测跑 HRNetV2p-W40 的 Cascade R-CNN 完全够用),CPU 是 16 核,内存 64GB,系统是 Ubuntu。
软件栈:
Python 3.8 PyTorch 1.10 CUDA 11.3 mmcv-full 1.5.0 mmdetection 2.25.0这里要特别提醒一下依赖版本匹配问题。mmdetection 对 mmcv 的版本要求很敏感,直接pip install mmcv-full很容易装成不匹配版本,训练过程中爆出一堆看不懂的算子错误。稳妥做法是按官方文档指定版本:
pip install mmcv-full==1.5.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html如果你用的不是这个组合,建议先去对应版本矩阵里查清楚再装。
4.2 核心训练参数说明与选择理由
用的配置基于是 mmdetection 仓库自带的cascade_rcnn_hrnetv2p_w40_20e_coco.py,我只改了几个关键项。下面这张表是我最终训练时的核心参数:
| 参数 | 设置值 | 说明 |
|---|---|---|
| base_lr | 0.002 | HRNet 骨干建议用较小的初始学习率 |
| lr_schedule | 16e/20e 阶梯下降 | 按配置默认,epoch 16 降到 0.1 倍 |
| batch_size | 4(单卡) | 显存限制下的稳定值 |
| num_classes | 1 | 只有 banana_hand 一个类 |
| load_from | COCO 预训练权重 | 迁移学习基线 |
| max_epochs | 20 | 标题里的 20e |
| num_workers | 8 | 数据加载线程 |
| img_scale | (1333, 800) | 保持 COCO 默认训练尺度 |
这里重点说img_scale。COCO 默认尺度是 1333×800,对于产线图像我们需要保证小果手的长边超过一定像素,所以一开始想直接把尺度加到 (1600, 1000)。但实测发现显存直接爆掉,而且增强后小目标的数量并没有显著减少——原因是产线相机分辨率已经固定,放大图像并不会增加真实信息,只是把同一信息放大。最终保留默认尺度,把精力放在相机安装高度上,让原始图像里的小果手本身就足够大。
dct说一个实操技巧:如果训练时 loss 曲线正常下降但验证集 mAP 波动大,优先检查是不是学习率太高,把 base_lr 降到 0.001,很多时候问题就解决了。
4.3 训练过程监控与损失曲线判读
训练过程中我用 wandb 记录了 loss、lr、mAP 变化。Cascade R-CNN 有三个检测头,每个头都有自己的 cls_loss 和 reg_loss,记录时会看到纵坐标偏高,这正常。
几个判断经验:
- 总 loss 在前 3 epochs 快速下降,说明预训练权重迁移正常;
- 如果第一个 epoch 的 loss 比随机初始化还大,检查数据标注是不是选错了数据集格式;
- 训练到 16 个 epoch 学习率下降后,验证集 AR 会有一次明显跳升,这是阶梯式学习率预期的表现;
- 如果训练 loss 持续下降但验证 loss 不降,说明模型开始记住训练集细节,需要增强数据或提前停止。
最终训练到 20 epochs 结束,验证集 mAP 停留在 0.78 左右,相比 ResNet50 骨干的基线提升了约 0.06,这个提升主要来自小目标类别。
5. 常见问题与排查技巧实录
5.1 小尺寸香蕉手漏检怎么解决
这是整个项目里最折磨人的问题。最初的模型在正常尺寸果手上表现不错,但小果手召回率惨不忍睹。排查路径:
- 检查小果手在图像里的像素尺寸。把标注框面积分布画出来,发现大量小果手的长边只有 40~60 像素,确实属于小目标范畴。
- 检查 RPN 的 anchor 配置。COCO 默认 anchor 面积从 32² 到 512² 不等,按 2 的倍数缩放。40~60 像素的果手对应面积大约在 40²~60²,正好落在最小 anchor 附近,覆盖还行,但数量不够。
- 解决手段是调高 anchor 密度,把
ratios从默认的 [0.5, 1.0, 2.0] 加一个 [0.8, 1.0, 1.2],贴近香蕉手的长条形状。另外把一个 feature map 层级的 anchor 步长从 4 改成 2,增加小目标的采样密度。
调完这组参数重新训练,小目标 AR 从 0.46 提到了 0.58。注意这组参数不能动得太激进,否则 RPN 的候选框数量暴增,训练时间拉长且容易误检。
5.2 背景复杂导致误检怎么压下去
产线场景中,传送带上的深色杂质、包装箱的纹理、甚至地面反光都可能被当成香蕉手。初期测试集误检率接近 15%,集中在背景复杂区域。
排查后确定两个因素:
- 标注数据里背景多样性不足,模型“偷懒”学习了背景色的 shortcut;
- 负样本缺失,整个训练集里没有“空场景”图像。
解决方法很直接:
- 加入 200 张完全没有香蕉的产线背景图,标注为空,模型可以学到“这里什么都没有”;
- 数据增强中加入随机背景替换,把前景香蕉贴到不同背景纹理上;
- 提高置信度阈值到 0.5,推理阶段对低置信度框直接丢弃。
处理后误检率降到 5% 以内。注意加负样本时背景图一定要来自同一产线环境,不能拿网上随便找的图顶替,否则场景分布就对不上了。
5.3 显存不足与训练速度优化
HRNetV2p-W40 的 Cascade R-CNN 训练起来比 ResNet 系列吃显存更多,24GB 显存跑 batch_size 4 已经是临界值。遇到显存不足时,我的排查顺序:
- 先降
num_workers,把数据加载线程从 8 降为 4,显存占用能降一点; - 再关梯度累积(
accumulate_grad),如果不小心开了梯度累积且积累步数大,显存会急剧上升; - 最后降 batch_size 到 2,同时按比例调学习率。参考线性缩放法则,batch_size 减半,base_lr 从 0.002 降到 0.001,收敛效果基本不损失。
另外有个小技巧:把训练图像的长边从 1333 降到 1000,显存占用明显下降,但精度会损失 1~2 个点,适合临时救急,不建议最终方案依赖它。
6. 效果评估与落地部署经验
6.1 指标评估体系设计与工程取舍
检测项目的指标不能只看 mAP。mAP 是综合指标,但产线更关心“小果手到底漏没漏”。我拆成三个指标来评估:
| 指标 | 含义 | 关注点 |
|---|---|---|
| mAP@0.5:0.95 | COCO 标准综合精度 | 整体模型质量 |
| AR@0.05:0.95 小目标 | 小面积目标召回率 | 小果手是否漏检 |
| 误检率(FPPI) | 每张图的平均误检数 | 产线上剔除是否干净 |
最终模型测试集结果:mAP 0.78,小目标 AR 0.58,误检率 0.04。作为对比,ResNet50 骨干的 Cascade R-CNN 只有 mAP 0.72,小目标 AR 0.49,误检率 0.07。差距主要在小目标上,HRNet 高分辨率特征的优势体现得很明显。
实际工程部署不能只盯精度,推理速度同样关键。20e 的模型用 TensorRT FP16 转换后,单张推理大约 45ms,跑在产线工控机上满足每分钟 600 帧的处理需求,留出了不少余量。
6.2 模型导出与推理部署要点
训练好的 PyTorch 权重不能直接上产线。导出 ONNX 和 TensorRT 的过程中,我遇到两个值得记下的坑:
- 动态尺寸输入:产线图像尺寸如果和训练尺寸不一致,ONNX 导出时不要固定输入尺寸,保持动态 H/W,否则部署后边缘区域的对象会全部漏检。
- 后处理 NMS 的移植:mmdetection 的 NMS 是自定义算子,导出时容易丢失。我直接把手写的 NMS 逻辑放在了 TensorRT 插件外面,用 Python 在推理后处理阶段重新实现,虽然损失一点速度,但避免了插件兼容性问题。
部署时的规避经验:如果产线需要高帧率,可以考虑把 HRNetV2p-W40 替换成 W18 版本,速度大约提升 40%,精度损失 1~2 个点;如果精度优先,W40 版本加 FP16 量化是最均衡的配置。
6.3 实操心得与可复用的后续优化方向
这个项目做到后来,我最深的体会是:农业视觉项目的瓶颈往往不在模型结构,而在数据质量和标注一致性。如果标注时“果柄算不算检测框一部分”都没有统一,后面所有调参都白费。
建议后续想复现这个方案的同学,先花一周时间把标注规范写清楚,再开始训练。很多“奇怪”的训练不收敛、验证集波动问题,根源都是标注标准漂移。
项目后续的扩展方向主要有三个:
- 增加多视角检测,配合双目相机估算香蕉手的三维尺寸,分级精度会更高;
- 针对小果手专门训练一个分支模型,和一阶段主模型做级联检测,进一步拉高小目标召回;
- 把尺寸分类和品质分类(表面疤痕、腐烂点)合并成多任务网络,一张图同时输出多种信息,节省产线整体算力。
如果你正在做类似的果蔬分选检测项目,这套“Cascade R-CNN + HRNet 高分辨率骨干 + COCO 迁移 + 后处理分级”的路线基本可以直接套用,需要注意的无非是数据采集环境和标注规范的把控。模型结构本身并不神秘,真正决定项目上限的,永远是你在数据上下的功夫。