基于Mask R-CNN与HRNet的腰果质量分级实例分割实践

发布时间:2026/10/22 13:38:06
基于Mask R-CNN与HRNet的腰果质量分级实例分割实践 做工业视觉这几年我越来越觉得很多质检问题不是画个框就能解决的。腰果质量分级就是一个典型。腰果表面上的裂纹、霉斑、黑点形状千奇百怪有的细得像一条线有的是一整片不规则暗区。你要判断一个果仁到底该归为A级还是B级光知道“框里有个目标”没有任何意义你得知道缺陷长什么样、有多大面积、边界在哪里。这也是我们最终选择基于mask-rcnn_hrnetv2p-w32-1x_coco这套配置来做实例分割而不是简单跑一个目标检测模型的原因。这篇文章不打算照着文档复述而是想把我从数据采集、标注、训练、评估到上线的完整过程整理一遍把踩过的坑和试错后的经验写出来。适合正在做食品外观质检、农产品分级或者准备用实例分割落地细分缺陷检测场景的朋友参考。哪怕你没碰过腰果里面的选型逻辑、标注方法、训练调参和部署注意事项也可以直接迁移到类似的视觉项目上。1. 项目概览与需求拆解1.1 腰果质量分级究竟难在哪腰果加工线跟很多食品产线一样原料进来之后要先经过外观筛选。外壳脱掉之后果仁本身会出现各种情况完整的、半瓣碎的、表面带黑斑的、有裂纹的、放久了发霉的甚至被虫蛀出小孔的。不同客户对等级要求不一样有的按缺陷数量有的按缺陷面积占比有的按是否允许碎片。传统的方案就是人工目检。传送带旁边坐着工人眼睛盯着往下过的腰果看到坏的挑出来。但腰果这种物料表面是浅黄色带纹理的裂纹和自然皱皮经常混在一起靠人眼长时间盯疲劳之后漏检率会明显上升。而且质检标准很容易带主观性同一个果仁上午的班次可能判成B级下午的班次可能就变成C级了。工业上要解决的是三件事第一能不能稳定找出每一颗腰果的缺陷并说清楚缺陷类型第二能不能根据缺陷面积或数量自动给出等级判断把“人说了算”变成“规则说了算”第三能不能跟上产线节拍在几毫秒到几十毫秒内完成判断带动执行机构把不合格品剔除掉。要在算法层面满足这三点只给目标框是远远不够的。1.2 为什么实例分割比目标检测更适合腰果质检先看一个实际问题。某一次测试里我们先用一个普通的目标检测模型去识别腰果上的霉斑检测框画得也挺准但做分级的时候发现完全没法用。原因很简单霉斑是长条状或不规则片状检测框给的是一个矩形范围框里既有霉斑也有正常果仁表面。如果按这个框的面积去算霉变占比结果会严重偏大一批明明只有一点点霉斑的A级腰果会被误判成不合格。腰果分级最重要的指标之一就是缺陷面积占比。矩形框无法准确表达缺陷的真实边界只有像素级的轮廓才能算准。Mask R-CNN这类实例分割模型能把每个缺陷实例的轮廓分割出来再统计轮廓内的像素数量这个面积才是真实的损伤面积。另外还有一个场景在食品物料上非常常见腰果在传送带上往往是紧挨着、甚至有部分重叠的。目标检测模型可以画出两个框但两个框叠在一起哪个缺陷属于哪个果仁很难判断。语义分割虽然做了像素分类但它不会区分实例重叠在一起的两个果仁会连成一片也没法按单个果仁去计算结果。Mask R-CNN的好处在于它天然是“检测分割”双输出。它先用RPN区域提议网络把可能的目标位置找出来再对每个候选框同时做分类、框回归和掩码分割。每一个腰果就是一个独立的实例不会被旁边的腰果“合并”掉。对我们后续做单个果仁缺陷统计来说这个设计非常关键。1.3 HRNetV2p-W32作为主干网络的取舍刚看到这个标题里的HRNetV2p-W32可能有人会问为什么不干脆用ResNet50在COCO上这两者都能跑但换到腰果这种细纹理场景差异就出来了。ResNet是编码-解码思路图片经过多次降采样高分辨率信息会逐步丢失。到深层特征图里一个小裂缝可能只剩下几个响应点不容易和背景区分开。HRNet的思路不一样它从头到尾一直保留了一条高分辨率分支其他分支负责生成更低分辨率的特征然后不同分辨率之间不断做信息融合。这样一来细节信息没有被牺牲掉对小目标、细长裂缝、浅色霉斑这类特征的感知会好很多。V2p这个版本是在HRNetV2的基础上对高分辨率表示做了可学习的池化处理后续接检测头时表达能力更强。W32指的是主干网络中等宽度分支的通道数W48通道更宽、能力更强但计算量和显存开销也更高。从我实际体感来说W32配合1x训练策略在腰果这种类型相对单一的任务上性价比已经很高训练速度快部署时也更容易满足实时性要求。1x在这里指的是训练计划按12个epoch来做学习率在第8和第11个epoch下调而不是那种动辄几十上百个epoch的长训练。用它做基线一晚到两晚能跑完一轮方便快速验证数据和标注质量。先跑通流程再考虑更好的模型这是我一直推荐的节奏。2. 数据集构建与标注实践2.1 采集图像时会忽略的坑很多人一开始就想上最好的工业相机我觉得没必要。第一版采集直接用了普通的高分辨率摄像头配合固定光源和固定俯拍高度先把数据跑起来证明方案可行之后再升级硬件这个路径省钱也省时间。采集阶段最关键的是变化性。如果所有图片都在同一台设备、同一角度、同一光线下拍的模型很容易把背景环境特征当成腰果特征去学一换场景就崩。常规做法是分多批采集上午下午各采一批适当调整光源角度把腰果随机翻几个面。腰果是三维物料侧面有沟、边缘有棱不同姿态下缺陷的形态差异很大。如果只拍正面模型遇到侧面裂纹就基本无法识别。类别体系也要在采集前定好。常见的腰果外观缺陷包括裂口、黑斑、霉变、破损再加上一个正常类五个类别起步就够用。这里要特别注意类别粒度和分级规则的对应关系。如果你的分级规则里要求区分“轻微黑斑”和“严重黑斑”那就要分两类如果不区分就不要强行拆类别越多标注成本越高模型也越容易混淆。样本数量方面我个人的经验是一个类别至少要有500到1000个独立实例才能看到一个相对收敛的效果。腰果表面缺陷比较密集时单张图可能同时有多个缺陷实例所以实际需要的图片数可以比实例数少一些。如果预算有限、类别特别多还可以先只分“正常”和“异常”两类把模型跑通后再细分。2.2 标注腰果缺陷时的细节规范标注是实例分割项目里最耗时间、也最决定上限的环节。一个模型能学到什么程度很大程度上取决于标注边界是否干净。腰果主体的标注相对简单沿着果仁轮廓描多边形就行。麻烦的是裂缝。裂缝又细又长有的只有2到3个像素宽稍不小心就标成一条锯齿线。我们统一的规则是肉眼能看出的裂缝都标标注时沿裂缝中心线画窄多边形宽度尽量控制在3像素左右。这样做不是为了像素级完美而是为了保证不同标注员手底下尺度一致。如果一个人标3像素宽另一个人标10像素宽模型看到相同裂缝却对应完全不同的目标训练时很容易乱掉。黑斑、霉变这类区域面积大边界相对清晰但经常有灰度过渡。标注的时候不要纠结于过渡区的每一个像素建议按“视觉上明显异常的范围内边界”为准。这块最好在标注规范里写清楚包括最小缺陷尺寸、哪些自然纹理不算缺陷、边缘和阴影要不要标等。多人并行标注时至少留出5%到10%的图做交叉复核由同一个人复查统计不同标注员之间的结果差异。这个机制能提前暴露出规范没说清的地方非常值得做。2.3 切到COCO格式的细节和校验方法实例分割数据集的通用格式是COCO格式。结构上并不复杂就是几个JSON数组images里放每张图片的id、文件名、宽高annotations里放每个实例的category_id、bbox、polygon分割点、area和iscrowdcategories里放类别映射。很多人在生成COCO格式时会栽在两个地方。一是area字段直接用图像宽高相乘之类的占位值没有按多边形真实面积算。这在某些框架训练时不算大问题但会影响部分评估代码的计算最好在建数据时直接把数据集计算出来的真实像素面积写进去。二是多边形坐标要和图像尺寸严格对应一旦图片做过缩放或裁剪坐标也要同步转换否则模型训练时polygon坐标全错位。数据准备好之后一定要做可视化校验。简单的方法是写一个小脚本把标注的多边形和框叠加在原图上人工翻几百张图看看有没有错位或漏标。再写一个统计脚本输出每个类别的实例数量、每张图的实例数分布、腰果面积的尺寸范围。这个统计能帮我们发现类别严重不均衡的问题也能帮后续调anchor的时候判断初始尺度设置。数据划分上还有一个容易被忽略的地方同一个果仁如果拍了多张不同角度的照片不能让它们同时出现在训练集和验证集里。否则模型可能不是真的学会识别腰果缺陷而是记住了这个果仁的特征验证指标会虚高。更好的做法是按采集批次来划分训练集和验证集模拟不同批次的真实分布变化。3. 模型训练与配置调优3.1 环境准备与预训练权重现在做深度学习项目基本都绕不开PyTorch。起步阶段环境版本不需要追新我用的是比较稳定的版本组合。训练框架方面很多开源检测工具箱都内置了mask-rcnn_hrnetv2p-w32-1x_coco这套配置直接在此基础上改就行不用从零手写训练循环。安装依赖时最容易踩坑的是版本不匹配。CUDA、cuDNN和PyTorch版本要对应好装完之后一定要先跑一下官方自带的demo图片确认前向推理正常再开始训练。这一步能省下后面排查环境问题的大把时间。预训练权重我直接用COCO数据集预训练好的模型。虽然腰果和COCO里的日常物体差异很大但底层纹理、边缘、颜色特征仍然可以复用。修改模型输出类别数之后需要注意新加的分类层和mask分支是随机初始化其他层加载预训练权重。如果显存允许我建议先冻结主干网络最前面的两到三个stage用较低的学习率单独训练新增分支等loss稳定后再解冻主干对最终精度提升有帮助。3.2 核心配置文件改动记录在开源检测框架里模型文件、训练计划、数据集路径都是围绕配置文件组织的。mask-rcnn_hrnetv2p-w32-1x_coco这个配置名字已经把关键信息都写出来了mask rcnn是模型结构hrnetv2p-w32是主干网络1x是训练策略coco是预训练数据集。实际使用时最少要改几个地方。第一是类别数把默认的80类改成自己的类别数量比如5类或7类。第二是数据集的路径和标注文件路径。第三是训练超参数尤其是学习率和batch size。配置文件里和训练计划相关的部分通常长这样我用简化示例说明optimizer dict(typeSGD, lr0.02, momentum0.9, weight_decay1e-4) lr_config dict( policystep, warmuplinear, warmup_iters500, warmup_ratio0.001, step[8, 11]) runner dict(typeEpochBasedRunner, max_epochs12)这里默认的lr0.02对应的是使用8张卡、总batch size为16的前提。如果你用单卡训练batch size只有2或4学习率直接沿用0.02大概率会发散。常见的做法是按batch size线性缩放从0.02除以16再乘以当前batch size比如batch size2时lr建议设为0.0025到0.005。这个缩放规则很粗糙但比不缩放要稳得多。锚框参数我也调过。腰果是长条形默认长宽比0.5到2.0基本够用但如果你想更贴合可以加一个4.0的选项。锚框的base scale默认8对腰果这种几十像素到几百像素的目标是合适的不太需要动。输入尺寸方面默认的短边800、长边1333对大多数工业相机拍出来的图片来说没问题。如果图片里腰果非常小比如2000万像素拍几十颗腰果那就需要把输入分辨率提高或者做裁剪否则小裂缝很容易在降采样时直接消失。3.3 训练过程怎么监控训练一启动终端会不断打印当前的iter、loss、学习率等信息。刚开始接触实例分割训练的人会有点懵因为同时有好几个loss并列显示比如rpn_cls_loss、rpn_bbox_loss、cls_loss、bbox_loss和mask_loss。这里简单拆一下。RPN相关的两个loss负责候选框的生成是否准确区分前景背景cls_loss和bbox_loss负责对最终检测框做分类和位置修正mask_loss专门负责像素级分割也就是每个像素的掩码预测是否对。Mask R-CNN对每个roi会预测多个类别的mask训练时只选择该roi真实类别对应的那一路计算损失。理想情况下整个loss曲线在前几百个iter快速下降然后慢慢平稳。如果一个epoch之后loss还在高位横盘优先检查学习率是否设置过高或过低其次检查标注数据是不是有问题比如上下颠倒、类别映射错位。warmup的作用不可小看。开始训练时学习率从很低的初始值逐步升到设定值可以避免一开始梯度更新幅度过大参数在头几步就被推飞。warmup_iters一般设500到1000warmup_ratio通常0.001。单卡训练1x schedule如果输入尺寸是800到1333batch size取224G显存的显卡大概需要运行十几个小时。如果训练中显存溢出可以先降低batch size并同步降低学习率。实在想提速可以考虑自动混合精度训练显存占用能明显下降速度也能提升。4. 模型评估与迭代优化4.1 评估指标不是越高越好训练完成后常规评估会输出一组指标mAP、mAP_50、mAP_75、AR以及mask分支对应的mask AP、mask mAP_50等。mAP是COCO的标准指标计算IoU从0.5到0.95的多个档位再取平均。但工业质检场景里我对mAP_50和mAP_75更敏感。实际产线判断缺陷是否存在时不需要框的位置精确到像素级只要大致能对上就能触发剔除所以mAP_50往往更有参考价值。而mask AP直接反映了分割轮廓的质量这决定面积统计准不准。因此在项目汇报里至少要看三组数字mAP、mAP_75、mask AP才能比较全面地了解模型状态。特别要留意每个类别的单独AP而不是只看平均。腰果实际运行中黑斑和霉变的检测效果通常还行裂缝类的AP往往偏低。原因很简单裂缝在IoU计算规则里很吃亏一个细长的目标预测结果稍微偏移几个像素IoU就会大幅下降。如果你只看平均AP可能看不出裂缝已经被拖后腿了。单看AP还不够还要换算成业务指标。生产线上最关心的是误检率和漏检率。把验证集里所有预测结果跟人工标注比较设置不同的置信度阈值观察误检和漏检怎么变化。最终阈值不一定选0.5要根据不同缺陷的经济代价去调。比如漏过一个发霉的果仁被客户发现损失远大于把几个好果仁误判成坏的那阈值就可以适当降低。4.2 把预测结果可视化后你才能看到问题跑完评估只看指标表远远不够我会把预测结果以图像形式批量输出然后人工翻看。这个过程能直观地发现很多指标反映不了的问题。第一类问题是漏检。很多时候裂缝在图上确实细到几乎看不见模型没能识别出来。翻到这种图我们会回到标注数据里去看这类样本数量是不是太少是不是很多人标注时都漏掉了如果是先从数据补齐开始。第二类问题是误检。腰果表面有天然的沟和皱皮纹理很容易被当成裂纹。翻图一看啊冒出一堆细长mask在正常果仁上就知道是数据标注规范里没有把“皱皮不算缺陷”这条说清楚模型学歪了。解决办法不是骂标注员而是把大量带皱皮且标注为正常的样本加进训练集让模型学会区分。第三类问题是mask边界粗糙。预测的mask在腰果边缘处有锯齿、超出轮廓几个像素。这种问题多数可以通过提升输入分辨率、增加训练epoch或者换更宽的backbone来缓解。如果只在及格线附近徘徊我一般先不急着换模型而是先把最大的一类错误样本补掉。4.3 调优主线和几个实用路子模型调优有个优先级习惯先数据、后模型、再训练策略。每次优化只改一个变量不然你根本说不清是哪个改动带来了提升。数据层面针对低AP的类别单独补数据是最直接的。比如裂缝样本少就专门采一批带裂缝的腰果拍完后做随机旋转、镜像、亮度扰动再进训练。数据增强上我常开随机翻转和多尺度训练尺度抖动对腰果这种大小差异大的目标很有帮助。模型层面如果数据已经比较充分可以把W32换成W48通道变宽后对大位点特征和边缘细节的拟合能力会更强显存开销也会上升。另外也可以给特征金字塔的顶层加一个输出或者把NMS阈值从默认往下调一点让互相靠近的腰果不容易漏检。要注意的是HRNet本身参数量就不小换W48前先确认部署侧的推理耗时能不能接受。训练策略层面从1x升级到2x甚至3x多训练几个epoch往往能带来稳定提升代价只是时间。EMA指数移动平均在一些开源工具箱里也能一键开启对最终精度有正面效果。推理时做多尺度测试翻图会把每个尺度下的预测结果综合起来能涨一点mAP但推理时间成倍增加产线部署时要权衡。还有一个非常容易被人忽略的调优点后处理里的置信度阈值和小图尺寸。同样一个模型如果把测试短边从800放大到1000小裂缝的检出率会有肉眼可见的提升。前提是部署端能接受这个耗时。5. 部署落地与工程化经验5.1 模型导出与推理加速训练用的模型权重并不能直接拿到产线上跑通常要做推理优化。比较常见的链路是先把PyTorch训练模型导出成ONNX再通过推理引擎转成加速版本。导出时要注意固定输入尺寸。训练时代码里经常允许动态输入但部署端固定成短边800或960可以显著提高推理性能。另外NMS这类后处理在不同框架里支持程度不一样通常建议单独实现或使用引擎自带的插件不要把整套模型原样导出。我自己在部署时踩过的一个坑是导出ONNX时没有把原始图像的预处理步骤对齐。训练时的BGR通道顺序、归一化系数、缩放方式在部署代码里必须保持一致。有一版模型导出后精度掉了很多最后发现是推理代码里用了RGB顺序做预处理跟训练时不一致。加速之后的速度差异非常大。一张1080P的腰果图纯PyTorch推理可能要两百多毫秒优化后能压到二十到三十毫秒基本够用。这里具体数字会因显存、TensorRT版本和输入尺寸而不同但趋势是一致的部署优化是必不可少的一步。5.2 产线集成与分拣联动的关键控制点谈部署不能只谈模型还要考虑相机怎么装、光源怎么打、信号怎么联动。相机方面普通工业相机加固定镜头的方案就够用。腰果在传送带上是动态的快门速度要足够快避免运动模糊。光源建议用低角度的环形光或条形光这样可以减少腰果表面反光和阴影对缺陷识别的影响。整个成像区域要加遮光罩避免车间自然光变化造成图片亮度漂移。触发方式一般用光电传感器。腰果通过传感器后相机在固定延时下拍照确保腰果正好在画面中央。这样拍的图片位置稳定模型处理起来更省心。检测完的结果还要传给后端执行机构。如果只是报警可以把结果写到PLC或工控机由产线控制剔除气嘴。如果要做自动分级就要把每个检测框的类别、分数、mask面积比例一起传出去。这里的通信协议和交互时序要提前约定好尤其是多相机并联时数据同步很容易出乱子。产线侧判定规则通常和模型阈值层分开。模型只负责输出“这个区域有多大概率属于某类缺陷”最终是否判不合格、判到什么等级由规则层根据缺陷面积占比、数量、种类去决定。这样做有一个明显的好处客户改了标准只需要改规则配置不用重新训模型。5.3 持续迭代的数据闭环模型上线只是开始不是结束。算法在真实产线运行肯定会遇到新的情况新的原料批次颜色更深、烘焙工艺调整后霉斑颜色变浅、不同季节的腰果纹理不一样。数据回流机制比换一个更强的主干网络更有价值。我建议在产线边上部署一个自动截图服务凡是模型置信度在0.4到0.7之间的模糊判断结果以及现场确认的误检漏检图片自动保存到待标注队列。每周或每两周做一次标注补录和原始训练数据混合后再增量训练这个闭环跑起来模型会越来越适应真实场景。模型迭代时要做好版本管理每个版本的权重文件对应哪份训练数据、哪个标注规范版本、哪套超参数都要记录清楚。否则过两个月想回滚到之前那个效果好的模型可能根本找不回来。6. 常见问题速查与避坑清单6.1 数据与标注阶段的典型问题表格里的问题都是我在项目里真实遇到过的按频次排序。现象可能原因处理办法验证集AP很高但产线效果差数据划分没有按批次分隔模型记住了特征按采集批次重新划分训练/验证集裂缝类几乎检不出来标注宽度差异太大模型学不到一致模式统一裂缝标注宽度补标一批裂缝样本模型把腰果皱皮当成裂纹正常样本缺少自然纹理变化大量增加带皱皮的正常类别样本一张图里多个腰果挤在一起mask粘连堆叠情况标注不完整难以学习实例边界单独采集一批单颗腰果图辅助模型学习边界同样的标注标准两个标注员风格差异大没有明确标注规范写书面规则明确最小缺陷尺寸、裂缝宽度、不算缺陷的情况6.2 训练阶段常见问题现象可能原因处理办法loss开头就直接飞掉学习率太高或warmup设置过短降低学习率确认warmup_iters数值训练loss持续不下降学习率太低或数据标注存在大量错标小幅调高学习率并检查标注可视化显存不足无法训练batch size太大输入分辨率太高减小batch size并按比例降低学习率开启混合精度训练完mask轮廓粗糙、边缘外扩训练epoch不足或输入尺寸偏小增加训练轮数适度提高测试输入分辨率训练阶段还有一个让我印象深刻的问题用默认的lr0.02在单卡训练前面200个iter loss疯狂上升当时以为是代码写错了折腾了一晚最后发现就是学习率没按batch size缩放。后面我把这个规则写在配置注释里再也没犯过。6.3 部署阶段常见问题现象可能原因处理办法导出的ONNX推理结果和训练不一致预处理步骤不一致BGR/RGB顺序不同对比训练代码和部署代码的归一化与通道顺序TensorRT转换报错网络结构里存在不支持的算子检查模型结构处理掉特殊算子或更换兼容层相机画面清晰但模型检测率突然下降光源老化或环境光变化检查光源状态、重新采集对照图尝试自动亮度补偿产线节拍跟不上单张图推理时间太长固定输入尺寸、启用半精度推理、批量处理叠加最后分享一点体会回头再看整个项目mask-rcnn_hrnetv2p-w32-1x_coco这套配置本身并不算新但它证明了在垂直检测场景中一个稳定、可解释、能快速迭代的基线方案往往比追逐最新网络结构更靠谱。我体会最深的一点是项目成败很多时候不取决于模型性能涨了多少个点而是标注是否统一、指标是否贴合真实质检规则、部署后反馈闭环能不能跑起来。这套流程放在腰果上成立换成果仁、坚果、水果甚至五金件表面缺陷检测思路都是一样的。如果你也准备启动类似的项目建议先用这套配置把端到端流程打通再根据收集到的问题逐步迭代你会少走很多弯路。