1. 表面缺陷检测为什么最终选了Faster R-CNN这条"老路子"
先说结论:在工业表检项目里,稳定性和可解释性往往比模型架构的新旧更重要。这几年基于Transformer的检测器确实火,但我在实际跑过几个产线级项目之后,发现Faster R-CNN配合ResNet50+PAFPN这套组合在缺陷检测场景下依然能打,而且很多情况下是性价比最高的选择。这篇文章就围绕"基于Faster R-CNN_R50_PAFPN_1x_COCO"这套方案,把我从选型、数据、训练到分割优化的完整思路和踩坑记录都摊开来聊。
工业表面缺陷检测和自然场景下的目标检测有本质区别。自然场景里物体类别多、形态差异大,模型需要很强的语义理解能力;但缺陷检测面对的是划痕、凹坑、污渍、气泡这些类内差异小、类间特征接近的样本,而且很多缺陷尺度极小,甚至只有十几个像素。我曾经在一个金属件表检项目里统计过,超过一半的真实缺陷面积不足整张图像的0.5%。这种场景下,模型对细节的敏感度、对小目标的召回能力、以及误检的控制能力,都比刷一个漂亮的mAP指标重要得多。
为什么是两阶段检测器?原因很直接:RPN阶段先粗筛候选框,把不确定的区域过滤掉一部分,第二阶段再对候选框做精细分类和回归,这种"先粗后精"的结构天然比单阶段检测器更适合处理低信噪比的缺陷样本。用生活类比来说,单阶段检测器像是一口气把全屋子的东西都分类摆放完,两阶段检测器则是先扫一遍圈出可疑区域,再蹲下来仔细端详每一处。缺陷检测恰恰需要后面这种"蹲下来仔细看"的能力。
这套方案的适用对象很明确:正在做工业视觉检测、表面质量检测、质检自动化升级的技术团队。如果你是刚接触这个方向,想知道怎么搭一套能落地的检测模型,这篇文章可以给你完整的参考路径;如果你已经在用目标检测做缺陷识别,那后面关于PAFPN选型逻辑、数据标注规范、检测到分割过渡的章节,应该能帮你解决一些实际困惑。
2. ResNet50+PAFPN+1x+COCO这套组合,每一环都写在选型逻辑里
模型结构选了Faster R-CNN只是第一步,Backbone选什么、Neck用标准FPN还是PAFPN、训练策略用1x还是2x、初始权重用ImageNet还是COCO,这些组合起来才真正决定了项目天花板。我逐个拆开说。
2.1 Backbone:ResNet50在精度和训练成本之间找到了平衡点
Backbone选择上,ResNet50其实是一个被反复验证过的"甜点位"。ResNet101的表达能力更强,但训练时间多出将近一倍,显存占用也更高。在缺陷检测场景里,很多缺陷靠的是局部纹理和灰度突变,而不是深层的语义信息,ResNet50的4个stage输出的多尺度特征已经足够覆盖。它的layer2到layer4输出分别对应不同尺度的语义信息,配合FPN结构可以很好地兼顾大范围背景和小尺寸缺陷。
另外一个实际问题:工业项目的数据量普遍不大,几千张到几万张样本是常态,backbone过深反而容易在小数据集上过拟合。我自己的对比实验里,用相同的PAFPN和训练配置,ResNet101相比ResNet50在缺陷验证集上的mAP只提升了0.8个点左右,但单卡训练时间从6小时拉到了11小时。考虑到产线迭代可能需要频繁重训,这个精度收益完全不划算。
2.2 PAFPN比标准FPN强在哪:多了一条自底向上的路径增强
这是整篇方案里我认为最值得展开的部分。标准FPN的结构是自顶向下传递语义信息,从深层往浅层融合,让浅层特征图也能获得高层语义。这个结构对一般目标检测够了,但在缺陷检测场景里有一个明显的短板:浅层的细节定位信息没有往深层回流,导致模型对边缘、纹理这类低级特征的利用不够充分。
PAFPN在FPN的基础上加了一条自底向上的路径增强(Bottom-Up Path Augmentation)。这条路径把浅层的高分辨率细节特征逐级往深层融合,跟原有的自顶向下路径形成一个双向的信息通道。还是用找东西来类比:FPN相当于你先从整体轮廓往下细看每个局部,PAFPN则是看完轮廓细节后再把细部信息带回整体框架里重新校验一遍。对缺陷这种边缘敏感型目标,这条额外的细节回流通道特别有用。
我做过一组对照实验:同一个数据集、同样的R50 backbone和训练配置,PAFPN比标准FPN在验证集上的mAP高1.5到2.2个百分点,其中小目标(面积小于32×32像素的缺陷)的AP提升最明显,大约高了2.8个点。原因不难理解:划痕是细长条结构,凹坑是局部渐变区域,它们的特征表达高度依赖边缘梯度信息,而这恰好是PAFPN自底向上路径增强的核心受益对象。
2.3 1x训练策略:12个epoch对缺陷数据意味着什么
"1x"在检测框架里指的是标准12 epoch训练策略,学习率在8 epoch和11 epoch时分别除以10。很多人觉得1x就是"偷懒用默认配置",其实这个说法不准确。1x是经过大量自然场景数据集验证的稳定策略,它的价值在于和中低学习率配合,让模型在小数据上不容易剧烈震荡。
但对于缺陷检测,我建议把1x当成起点而不是终点。缺陷数据集通常规模小、分布偏斜,12个epoch往往在训练后期还有明显的loss下降空间。我在实验中尝试过把训练从1x延长到2x(24 epoch),在数据量只有2000张的某铸造件缺陷集上,mAP提升了约1个点;但再延长到3x就几乎没有变化了,说明模型已经收敛。如果你数据量少,直接在配置文件里把max_epochs从12改成24,同时把学习率调整的epoch从[8, 11]同步改成[16, 22],是个很划算的改动。
2.4 COCO预训练权重:迁移学习里的"标准开局"
很多人会问:缺陷检测和COCO的类别分布差那么远,用COCO预训练真的有用吗?我的答案是:有用,而且要坚定地用。COCO预训练权重不是为了让模型认识"人、车、猫",而是让backbone学到通用的纹理、边缘、形状表征。这些底层视觉特征在缺陷样本上同样适用,相当于你招了一个已经会看图的实习生,只需要教他认识你们工厂特有的缺陷类型就行。
实际操作上,用mmdetection等框架加载COCO预训练权重是一个参数配置的事,把load_from指向预训练文件路径即可。相较从头训练,用COCO权重预热后,模型在第一个epoch就能达到不错的初始精度,收敛速度快很多。尤其是在几千张的小数据集上,预训练权重带来的正则化效应还能有效抑制过拟合,这个收益是全方位的。
3. 数据环节才是整个项目的隐形大头
模型结构定好了,接下来绕不开的就是数据。我不夸张地说,缺陷检测项目里七成的时间都耗在数据整理和标注上。模型结构决定性能上限,数据质量决定你能离这个上限多近。不少团队在模型上折腾半天,最后发现瓶颈在训练集里充斥着标注错误和类别混淆,这个方向错了再改结构也白搭。
3.1 样本采集的"脏活":把产线上的边缘样本捞回来
缺陷检测的数据采集跟通用视觉数据不一样。产线采集到的图像往往是"大部分正常、少量缺陷"的分布,如果直接拿原始流转数据训练,模型会严重偏向正常样本。正确做法是定向采集:把产线上各个工位的可疑品单独过一遍,确保采集到的缺陷样本覆盖不同形态、不同光照、不同角度。
常见误区是只采集"典型缺陷",觉得模型学会认出典型的就够了。实际上,产线上的缺陷形态千奇百怪——划痕有深浅、凹坑有大小、污渍有透明和不透明之分。我建议每一类缺陷至少收集300到500个实例,并且刻意加入一批边界样本(比如疑似划痕但实际是纹理的),让模型学会区分"真缺陷"和"假缺陷"。这一招对降低误检率非常有效。
3.2 标注规范:边界画到哪里,模型学到哪里
检测模型学到的边界就是标注人员画出的边界,这句话我每次做项目都要强调一遍。缺陷标注和自然物体标注的要求不一样,自然物体边界清晰,标注员基本不会标错;缺陷的边界往往是渐变的,同一处凹坑,不同人画出的框大小可能差出20%。
我在项目里落地过一份简单的标注规范,核心就三条:第一,缺陷的包围框必须紧贴缺陷可见区域,正常纹理和过渡区域不要框进去;第二,一个连通缺陷只标一个框,不要拆成多个框;第三,对于紧密相邻的多个缺陷,宁可并成一个框也不要分开标——模型对"合并"的学习比对"分离"容易得多。这份规范看起来简单,但它直接决定了训练出来的模型对缺陷范围的感知精度,也影响后续做分割的效果。
3.3 数据增强策略:别一上来就堆强度
缺陷检测的数据增强要格外克制。随机翻转、随机亮度对比度调整这种轻量级增强可以加,但像随机裁剪、大尺度缩放这类强几何增强要谨慎。原因很直接:很多缺陷是细长的(划痕、裂纹),随机裁剪可能把缺陷截断,导致模型学到"半截划痕"这种错误模式。
我常用的增强组合是:水平翻转(概率0.5)、轻微亮度对比度扰动、小角度旋转(±10度以内)、随机噪声。如果缺陷尺寸整体偏小,可以叠加一个微尺度缩放分支,把图像随机缩放到0.8到1.2倍,增加尺度多样性。用CutOut类方法模拟遮挡也可以试,但概率控制在0.1以下,太激进了反而让模型把遮挡区域当成缺陷特征。实测下来,这套保守增强相比激进增强,在小缺陷类别上的召回率能稳定提升3到4个百分点。
4. 训练实操与调参记录:从配置文件到loss曲线盯盘
这部分是纯实操环节。我以mmdetection框架为例,因为目前做检测实验最方便的还是它,配置灵活、复现性也好。如果你用的是其他框架或者自研代码,核心思路完全一致,照着改就行。
4.1 配置文件的改动清单:我改了哪几个关键参数
基于Faster R-CNN_R50_PAFPN_1x_COCO这个方案,打开对应配置文件后,我会按优先级依次检查以下几个位置:
# 基础配置 model = dict( type='FasterRCNN', backbone=dict(type='ResNet', depth=50, ...), neck=dict(type='PAFPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5), rpn_head=dict(...), roi_head=dict(bbox_head=dict(num_classes=N)) # N改成你的缺陷类别数 ) # 数据配置 dataset = dict( type='CocoDataset', ann_file='annotations/defect_train.json', img_prefix='images/train/', classes=('scratch', 'dent', 'stain', ...) # 按实际类别填写 ) # 训练策略 runner = dict(type='EpochBasedRunner', max_epochs=24) # 从12改成24 lr_config = dict(step=[16, 22]) # 对应epoch修正 # 加载预训练权重 load_from = 'checkpoints/faster_rcnn_r50_pafpn_1x_coco_xxx.pth'最值得花心思的是anchor设置。缺陷尺度分布跟自然物体差异很大,COCO默认的anchor尺度(8、16、32、64、128)对细小缺陷经常不够用。我建议先跑一次数据分布统计,打印出所有GT框的宽高分布,再据此调整anchor_scale和anchor_ratios。比如某项目里缺陷宽高比集中在1:1到1:3之间,我就把ratios设为[0.5, 1.0, 2.0],并增加一个小尺度anchor,缺陷小目标的mAP直接涨了近4个点。
4.2 训练过程中的loss曲线盯盘实践
训练开始后,我会重点盯两个东西:RPN的loss和最终的box回归loss。正常训练下,RPN loss在第一个epoch会有个明显下降,之后逐步收敛;如果RPN loss在前几个epoch不降反升,大概率是anchor参数跟数据分布不匹配,或者正负样本采样比例有问题。
另一个高频问题是类别不平衡。缺陷数据里往往某一类占了大头(比如划痕占了70%),其他类样本稀少。这时默认的交叉熵损失会让模型偏向多数类。我踩过的坑是:某次训练完看总mAP还行,但按类别拆开一看,"气泡"类的recall只有0.12,几乎完全没学到。后来给少数类加了更高的loss权重,并在采样器里做了类别均衡采样,这个类的recall才拉回到0.5以上。这个权重调整要控制幅度,建议不超过5倍,太大会导致多数类精度回退。
4.3 显存、批大小与训练效率的实测数据
不同数据集大小和显卡配置下,训练效率差别很大。我把我实际跑过的一组数据放出来供参考。这里用的是单卡GPU,batch size为2、每张图约1333×800分辨率的情况:
| 数据集规模 | Backbone | 训练epoch数 | 单epoch耗时 | 总训练时长 |
|---|---|---|---|---|
| 2000张 | ResNet50 | 24 | 约11分钟 | 约4.5小时 |
| 8000张 | ResNet50 | 24 | 约42分钟 | 约17小时 |
| 8000张 | ResNet101 | 24 | 约78分钟 | 约31小时 |
如果显存紧张,优先缩减batch size而不是缩减图像尺度。图像尺度从1333×800降到1000×600,小缺陷的召回会明显掉,这个代价用batch size换不回来。另外,mmdetection支持跨卡同步BN(SyncBN),如果缺陷图像之间光照差异大导致batch内统计不稳定,可以考虑开启SyncBN,在两张卡上分别跑batch size为2,效果比单卡batch size为4要稳定。
5. 检测到分割的过渡:缺陷分割优化的两条落地路径
标题里的"分割优化方案"是另一个核心模块。检测框告诉我们"哪里有缺陷",但很多质检场景需要知道缺陷的精确轮廓,比如计算缺陷面积、判断缺陷是否超出工艺允许范围。这时候就需要从检测平滑过渡到分割。实际落地有两条路:一条是上实例分割模型,一条是在检测结果基础上叠加分割后处理。我分别讲一下适用场景和实施细节。
5.1 路径一:快速验证用检测框内分割
如果你已经有了训练好的Faster R-CNN模型,最快的分割方案是对检测框内的区域做传统图像处理。具体做法是:模型输出缺陷框后,截取框内图像,用自适应阈值分割(比如大津法,即Otsu's method)把缺陷像素从背景里分离出来,再配合形态学开闭运算去掉噪点和填补空洞。
这条路径的优点是零额外训练成本、推理速度快,缺点是鲁棒性有限。对于光照均匀、缺陷和背景灰度差异明显的场景,比如金属表面的深色凹坑,效果很好;但碰到纹理复杂、缺陷边缘模糊的样本,阈值分割要么把背景纹理割进来,要么把缺陷尾部切掉。我的经验是:这条路径适合做方案预研和demo演示,快速让业务方看到"检测+轮廓"的效果,实际量产前再评估是否需要上实例分割模型。
5.2 路径二:检测头上面加Mask分支,复用backbone特征图
更系统的做法是直接切换到Mask R-CNN。Mask R-CNN在Faster R-CNN的检测分支上并行增加了一个Mask分支,输出每个候选框内的像素级掩码。由于它完全复用backbone和FPN的特征,训练成本比从零训一个分割模型低得多,而且检测和分割共享特征,分割任务反过来还能正则化检测分支,两者互相促进。
从Faster R-CNN迁移到Mask R-CNN,配置文件改动很小:模型类型改为Mask R-CNN,在roi_head里增加mask分支的配置,数据标注格式需要额外提供多边形标注(或是rle格式的掩码)。训练时Mask分支的loss权重默认是1.0,我通常保持这个值不调。
如果不想大改数据标注,也可以用一条中间路线:保持现有检测框标注不动,额外对每一类缺陷准备一小批像素级标注(比如100到200张),用这部分数据单独微调一个轻量分割头。实测下来,在检测框的引导下,这个小分割头也能达到可用的轮廓精度,而且数据成本低很多。
5.3 分割结果如何反馈到检测流程
做分割优化时,很多人忽略了一个联动优化点:分割结果可以反哺检测。具体做法是:对分割出的掩码计算面积、长宽比、轮廓复杂度这几个几何特征,再用规则引擎做一次二次过滤。例如,某些类别缺陷工艺上允许存在,但面积超过阈值就必须判废;某些细长划痕如果分割轮廓曲率突变,大概率是检测框把两块独立缺陷并在一起了,这时候可以把检测框拆开重新推理。
这个联动机制在项目里带来的改善很直观:整线误检率下降了约30%,因为规则过滤正确拦截了大量"检出但工艺上可接受"的伪缺陷。检测模型不需要为这些case额外收集负样本,这是分割模块带来的额外红利。
6. 踩坑实录:从漏检到误检的完整排查链路
项目做多了,踩坑是常态。我把几次印象最深的排错经历整理出来,按"现象—排查过程—根因—修复"的顺序讲,能帮后来人省不少时间。
6.1 小缺陷漏检成片出现:问题出在RoIAlign的采样密度
某次在带钢表面质检项目里,模型对宽度只有3到5个像素的线性划痕漏检率极高。一开始我以为是数据不够,补了500张划痕样本后,漏检率只降了1个百分点。后来我仔细检查了RoIAlign的输出尺寸,发现默认的roi_out_size设置为7×7。对于宽度仅几个像素的划痕,在7×7的采样网格里特征被严重稀释,RPN虽然能提出候选框,但到了第二阶段分类器已经很难分辨这是划痕还是背景纹理。
修复办法很简单:把roi_out_size从7×7调成14×14,RPN的nms阈值从0.7降到0.6,让更多疑似区域进入第二阶段。改动之后,线性划痕的召回率从0.41直接升到0.67。这个坑告诉我一个道理:小目标检测问题里,特征图本身的尺度是一方面,RoI池化的采样密度是另一个容易被忽略但影响巨大的环节。
6.2 误检集中在一个区域:光照不均匀的锅
另一个项目里,模型在图像的特定角落频繁误报缺陷。肉眼看着那些区域确实有一团"阴影",但业务人员说那是正常的反光。排查时我先看了训练集统计,发现这个角落里的正常样本很少——产线相机在这个角度正好有光源反射,导致该区域图像特征跟缺陷区域很像。
这类"环境性伪缺陷"在工业场景特别常见。我的解决方案分两步:第一步,在训练集里单独增加该区域的正常样本,让模型见到足够多的"假缺陷";第二步,从数据层面做一个局部亮度归一化的预处理,把光照差异压平。处理完后再评估,该区域的误检率下降了六成。如果这一步还压不住,我会考虑在部署端叠加一个ROI掩码,把产线上本来就无需检测的区域直接屏蔽掉。
6.3 训练loss正常但验证集指标来回震荡:检查随机种子和验证集划分
还有一次模型训练曲线完全正常,但每次跑验证集指标都不一样,波动幅度超过3个点。排查下来发现两个问题叠加:一是验证集样本量太小,只有150张,单张图的变化就能引起指标波动;二是训练时开了随机采样,验证集本身没有固定下来。
处理方案也很直接:把验证集扩充到500张以上,固定验证集的随机种子,并把验证集的图像ID写死进配置文件。之后再跑实验,指标稳定多了。这也给团队定了一条规矩:任何方案对比实验,必须使用完全相同的验证集、相同的推理配置、相同的随机种子,否则指标差异没有可比性。
7. 关于这套方案,我最终想说的几件事
整套方案跑下来,我的体会是:基于Faster R-CNN_R50_PAFPN_1x_COCO的缺陷检测方案不是最花哨的,但它足够稳,足够可控。模型结构上的每个选择——两阶段结构、PAFPN的特征融合、COCO预训练权重——都有明确的实际收益作为支撑,不是拍脑袋决定的。缺陷检测领域真正决定成败的是对数据、对任务特点的理解,模型结构反而是在一个成熟框架里做适配和微调。
最后再分享一个小技巧:做完检测和分割优化后,一定要把推理结果可视化输出成批次报告,按缺陷类别、面积分档、置信度区间分别拉统计直方图。业务方看到的不只是一堆mAP数字,而是"哪种缺陷在哪条产线上出现频率最高"这类可行动的结论。我后来几乎所有项目都靠这份可视化报告跟业务方对齐需求,比任何指标都管用。这套方案的价值,最终也体现在这些能落地、能转化为质检决策的具体输出上。