玻璃盖板缺陷检测为什么选择RetinaNet?特征提取与样本重加权实战
1. 为什么玻璃盖板缺陷检测最终落到RetinaNet这条线上手机玻璃盖板的质检在不少人眼里是个“传统机器视觉就能搞定”的活但真正上过产线的人都知道玻璃盖板是工业视觉里最难啃的材质之一透明、高反光、背景又乱划痕只在特定光路下才显形脏污和镀膜纹路在图像里长得比缺陷更像缺陷。我最早用Halcon那套阈值分割加特征算子做白片玻璃检测崩边、缺角这类轮廓型缺陷效果还行但一碰到细划伤、暗伤、丝印边缘残留误检率直接没法看。后来转到深度学习目标检测方向用RetinaNet ResNeXt-101-32x4d FPN PISA这套组合才把漏检和过杀压到了可接受的范围。这篇文章就围绕这个标题展开为什么选一阶段检测器而不是两阶段X101-32x4d和FPN到底在特征层面做了什么PISA这种样本重加权机制在缺陷场景下的真实收益有多大以及从训练到部署我踩过的那些坑。适合正在做工业视觉质检、尤其是不透明材质检测项目的工程师参考也适合刚入门缺陷检测、想了解这个方向技术选型逻辑的读者。1.1 玻璃缺陷的特殊性决定了传统方案先输一半先说玻璃盖板在图像上的几个“反人类”特性。第一是透明意味着你看到的不仅仅是表层信息还有底下承托物、相机自身倒影和周围光源的反射第二是高反光导致图像亮度分布极不均匀弧边区域更是亮一块暗一块第三是弱对比度很多缺陷在正常光照下确实看不出来要靠打光角度把它“逼”出来。传统机器视觉的做法是固定工位、固定光源、固定产品型号通过阈值分割提取可疑区域再用几何特征、灰度特征做二次过滤。这套方案的问题在于它对图像稳定性要求极高光源稍微老化、玻璃批次折射率略有不同、甚至车间湿度过大导致起雾图像灰度分布一漂移阈值就失效。而且每条产线要维护几十个检测算子换一个玻璃型号就要重新调参工程师基本上被绑定在现场。深度学习检测模型本质上是学出一个“缺陷外观的分布”而不是固定规则。它容忍光照波动、允许背景变化只要缺陷在训练集中有足够多样本就能泛化到产线实际场景。这也是我后来在这个项目里彻底转向目标检测路线的原因。1.2 RetinaNet vs Faster R-CNN vs YOLO我为什么选一阶段选型时我对比过三类大方向方案速度小缺陷精度工程复杂度说明Faster R-CNN较慢高中两阶段精度稳但ROI精修流程在产线上较笨重YOLOv5/v8快中低低速度快但对极小、极长宽比目标不友好RetinaNet中高中密集anchor Focal Loss兼顾速度与精度Faster R-CNN确实精度高RPN粗筛之后ROI Pooling再做一遍精修对中小目标非常友好。但问题是它慢而且两阶段的训练和调优链路长RPN的anchor匹配、ROI采样比例、两阶段loss权重都是坑。缺陷检测项目里产品型号迭代频繁每换一个型号就要重新训练评估训练周期太长团队迭代效率上不去。YOLO系很快但它的anchor匹配策略和grid-based输出方式对细长目标比如一条横跨几百像素的划痕不友好容易丢失边缘细节。而玻璃盖板检测最缺的就是“细小且长”的缺陷召回能力。RetinaNet是典型的密集预测一阶段模型它在FPN的每一层特征图上铺密集anchor再用Focal Loss解决一阶段检测器一直存在的正负样本严重失衡问题。它在速度和精度之间取得了很均衡的点尤其当我把骨干网络换成ResNeXt-101-32x4d后特征提取能力比默认的ResNet50强一截小缺陷的漏检率有明显改善。所以最终选型就是RetinaNet的框架 更强的骨干 FPN多尺度 PISA样本重加权。2. 数据先行瑕疵样本采集、标注细节与增强策略模型选型做得再好缺陷检测这个项目里数据永远是第一瓶颈。我甚至可以这么说在手机玻璃盖板场景数据质量对最终漏检率的影响超过网络结构。这一节把采集、标注、增强三件事讲透。2.1 采图与打光不是所有缺陷都能被一张图像捕获很多团队犯的第一个错误是想用一张“通用的亮场图”去包含所有缺陷。这在玻璃上完全不成立。举例来说划痕是表面微损伤需要在低角度光下才能形成亮线脏污是附着物在漫射光下呈暗斑或灰影崩边是边缘崩损在透射背光下最清晰而丝印区的油墨不良得在镀膜和丝印之后用特定波段光照才能看出差异。我在项目里的做法是把缺陷按照“最佳照明方式”分组建立多套采图通道。每个通道用不同的光源和相机角度但图像经过标定对齐到同一坐标系。这样一套图像序列相当于给模型提供了“多视角”信息。缺点是图像数量翻倍标注工作量也跟着涨。如果现场硬件条件有限至少要把“低角度光划痕图”和“漫射光脏污图”两路做齐因为这两类缺陷在盖板检测中占比最大。采图还要注意样品覆盖度不能只在实验室里拍几十片“标准缺陷样片”一定要去产线端收集。实际生产的缺陷形态千奇百怪比如同一类划伤有细针状的点划、有连续长线划、有弧形的压伤形态差异非常大。模型如果没见过产线上的真实形态部署后必然在“新形态缺陷”上翻车。2.2 标注规范这个环节决定了模型能力的上限目标检测的标注看起来简单就是画框、贴标签但玻璃缺陷的标注细节非常多。类别定义上我建议不要分得过细。很多工厂的缺陷代码表里划伤有五六种细分代码但视觉上它们可能来自同样的物理机理强行分成多类只会让每类样本变少模型学不好。我当时把项目里的缺陷归并为五类划痕、崩边、脏污、异物、凹凸每类下细分的形态差异通过数据增强让模型自己学。注意如果两类缺陷在图像上确实难以区分即使工厂工艺要求分开也建议先合并训练、再加后处理规则去做细分类而不是直接让模型分。画框策略上细长划痕是最难的。模型回归的是边界框而一条划痕的包围盒可能极度细长横跨三分之一的图幅。坦白说如果按“紧密包围盒”的规范去标注长宽比可以到1:50以上这在训练时的anchor匹配阶段会带来很大困难。我实测后的做法是对长度超过一定阈值的划痕按等长度拆分标注成多个框每个框的长宽比控制在1:8以内。这样既保留了缺陷位置信息又不会让回归目标过度极端。标注一致性也要重点盯。同一个缺陷A标成划痕B标成脏污模型就会迷茫。我在项目里每周抽检标注样本做一轮标注一致性评审发现分歧立即统一标准。这个工作看似琐碎但对AP数值的影响非常大最容易让模型在难样本上行为不稳定。2.3 数据增强围绕“光学扰动”而不是“语义扰动”缺陷检测里的增强要谨慎因为很多破坏性增强会直接抹掉真实的缺陷信号。比如对脏污做强烈的颜色抖动可能把它变得像噪声对细划痕做大的随机裁剪可能让目标变得不可辨。我实际用的增强组合是水平翻转、垂直翻转、小角度旋转、随机缩放0.8到1.2、亮度对比度微调。这些增强的本质是模拟产线上光源波动、产品摆放角度变化、玻璃批次反光度差异。这里有个技巧亮度扰动的幅度不要超过真实产线光照波动的范围否则模型会把“过暗”或“过亮”当成一种正常模式反而降低判别能力。随机裁剪这种增强我没有用因为RetinaNet本身就是在原图分辨率上做密集预测裁剪后目标被切碎对细划痕是致命打击。相比之下我更推荐对困难样本做“局部放大复制”——把小缺陷区域在原图中复制粘贴多个副本强制模型在这些难例上多学几遍。这个操作在样本量少时非常有效。3. 特征链路拆解X101-32x4d与FPN的尺度分工网络结构这块我先给一个整体认知RetinaNet的检测头其实很轻真正决定性能的是它前边的特征提取链路。这个项目里主干是ResNeXt-101-32x4d特征融合是FPN检测器默认配置下的两条子网分别是4层卷积的分类分支和4层卷积的回归分支。逐层拆开讲。3.1 ResNeXt-101-32x4d在缺陷检测里到底强在哪ResNeXt是ResNet的“分组卷积升级版”核心是把原来单个3x3卷积换成多组并行的小型变换论文里叫cardinality。32x4d的意思是32个分组每个分组内宽度为4所以它并不是简单地做得更深而是把特征变换拆成多条更窄的路径。这个结构对玻璃缺陷场景有几个实际好处。第一是提升了特征表达能力同等计算量下ResNeXt的分组结构能学习到比ResNet更丰富的特征组合这对缺陷这种高纹理、低语义的目标很重要。玻璃上的划痕本质上是一种局部纹理异常需要网络有足够的低层纹理敏感度ResNeXt的分组基元恰好能增强不同方向、不同频段的纹理响应。第二是“宽而薄”的路径天然带有正则效果。分组卷积限制了每组感受野的交互范围相当于在网络上加了一个结构约束在样本量不是特别大的工业数据集上不容易过拟合。我用X101-32x4d对比过ResNet50作为主干时的效果在小划痕类别的AP上能提升约3到5个百分点代价是显存占用和单次迭代时间大约上浮40%在项目里是可接受的。3.2 FPN的五层特征图分别承担什么角色FPN的全称是特征金字塔网络它做的事情可以概括成一句话把深层的高语义信息和浅层的高分辨率信息做融合让不同尺度的目标都有“对口的特征层”。RetinaNet标准实现里的FPN输出五层特征图分别记为P3、P4、P5、P6、P7。P3从骨干的Stage3引出分辨率是高P7通过额外的下采样得到分辨率是低。每层特征图上设置的anchor尺寸也严格分级特征层对应输入图尺度区间主要服务目标P3约0-64像素细划痕、小脏污、小异物P4约64-128像素中等大小的凹凸、短划痕P5约128-256像素较大块脏污、油墨不良斑P6约256-512像素崩边、大块异物P7大于512像素大范围破损、整面裂伤在做玻璃盖板项目时我做的第一件事就是把各类缺陷的像素尺度统计画出来然后检查它们落在哪几层FPN上。结果是崩边主要落在P5/P6划痕横跨P3/P4/P5脏污则散布在P3到P5之间。这说明只靠单层特征图去检测单一缺陷是做不到的必须让FPN在训练中自主分配这也是为什么这套结构先天适合“缺陷尺寸跨度大”的场景。3.3 Anchor设置长宽比和尺度要向数据靠拢RetinaNet默认的anchor配置是ratios为[0.5, 1.0, 2.0]scales为[2^0, 2^(1/3), 2^(2/3)]每个位置9个anchor。但玻璃缺陷里划痕极长极窄默认的ratio根本覆盖不到。我把ratios扩展成了[0.25, 0.5, 1.0, 2.0, 4.0]这五档其中0.25和4.0就是为了对应极端长宽比的划痕。大类崩边往往长宽比接近1用默认的1.0档就够了。这里有个隐含代价每层每个位置的anchor数量从9个变成15个正负样本数量一下大了不少训练时会变慢但对细长目标的召回提升很值。anchor匹配的IoU阈值我用的是0.5正样本、0.4负样本中间地带做忽略。这条在缺陷检测里也适用因为玻璃上的缺陷边缘往往模糊如果阈值卡太严很多边缘部分重叠的框会被标成负样本给模型传递错误信号。4. PISA重加权机制到底给模型加了什么buffPISA全称是Prime Sample Attention简单说就是对训练过程中的样本做“价值排序”把有限的训练容量分配给对最终检测精度贡献更大的样本。在一片玻璃图上负样本可能有几十万个正样本只有几百个光靠Focal Loss按置信度降权还不够PISA做得更细。4.1 为什么Focal Loss之后还需要PISARetinaNet为了解决一阶段检测器和正负样本失衡提出了Focal Loss。它把大量容易被分对的背景样本的梯度压得很低相当于自动降低简单样本的权重。但Focal Loss只看样本的分类难易程度它不理解一个样本的“回归质量”。举个例子某块背景纹理长得非常像划痕模型经常误判Focal Loss会认为它是“难样本”给它保留下比较大的训练权重。但本质上它是背景再怎么学也学成背景反而把模型的注意力从真正的划痕上引开了。这就是Focal Loss的一个局限——难样本不一定是好样本。PISA换了一个思路根据样本和真实框的IoU以及分类置信度计算一个综合的“样本重要性”指标。一个样本如果IoU高、分类置信度相对低说明它是有潜力被拟合到更好的位置和类别上的“优势样本”反过来IoU不高但分类很确定它对最终检测精度的贡献就小。训练时把权重更多倾斜给前者这就是PISA的核心。4.2 PISA在RetinaNet里的两个作用分支PISA在代码实现里是两个独立的模块。一个是回归分支的IoU-HLR Loss它先按每个真实框匹配到的所有anchor的IoU做排序然后按排序位置分配回归权重排位越靠前权重越大。这样做的好处是模型会把精力集中在和真实框重叠程度高的anchor上尽早把这些anchor的边框回归推到精确位置而不是浪费梯度在大量IoU在0.4到0.5之间的模糊anchor上。另一个是分类分支的Prime Sample重加权。它根据分类得分和IoU的联合分布动态调整每个样本的分类loss权重。具体实现会在每个mini-batch内对样本做一次按“目标分数”的排序然后再套用类似Focal Loss的降权曲线但降权对象不再是全部低置信度样本而是那些综合评分不高的样本。配置上MMDetection系框架里有现成的pisa_retinanet实现我当时的做法是在基础RetinaNet配置上增加pisa相关设置model dict( typeRetinaNet, backbonedict( typeResNeXt, depth101, groups32, base_width4 ), neckdict( typeFPN, in_channels[256, 512, 1024, 2048], out_channels256, start_level1, add_extra_convson_input ), bbox_headdict( typeRetinaHead, num_classes5, anchor_generatordict( typeAnchorGenerator, ratios[0.25, 0.5, 1.0, 2.0, 4.0], scales[2**0, 2**(1/3), 2**(2/3)] ) ), train_cfgdict( assignerdict(typeMaxIoUAssigner, pos_iou_thr0.5, neg_iou_thr0.4), allowed_border-1, pos_weight-1, debugFalse ), test_cfgdict( nms_pre1000, min_bbox_size0, score_thr0.05, nmsdict(typenms, iou_threshold0.5), max_per_img100 ) )PISA的参数不需要额外调太多我一般只关注排序方式、温度系数和重加权幅度。默认值在缺陷数据集上通常就能看到收益。4.3 在玻璃缺陷场景下PISA的实际收益我在项目里做过消融对比同一批数据、同一个主干、同样的训练轮数开启PISA和关闭PISA。结果最明显的变化是“中等难度样本”上的AP提升。简单的大块脏污和崩边两层配置都差不多但划痕这种难样本AP提升在2到4个点之间。这个收益看着不大但对漏检率的影响不小因为划痕恰恰是最容易漏的那一类。另外PISA还有一个隐性好处训练收敛更稳。因为样本权重不再被极端难负样本牵着走loss曲线在训练中段的震荡明显变小。我后来在分析badcase时发现PISA开启后模型的输出框定位更紧很多原本框住了一半划痕的预测变得完整这一点在后处理NMS时也很关键。5. 从Config到收敛训练参数与判断标准这一节讲实际跑训练时大家最容易纠结的事情学习率、训练轮数、batch size怎么定以及怎么判断模型到底练好了没有。5.1 输入分辨率、batch size与学习率工业缺陷检测里我强烈不建议为了速度把输入分辨率压太低。RetinaNet的输入尺寸标准是宽和高按一定步长缩放到max size我当时的项目最大边设到1600像素主要原因是玻璃盖板上的细小划痕在原图里经常不到20像素宽如果缩到800像素俨然就看不见了。代价是显存占用很高X101骨干加PISA配置batch size为4时一张A100级别显卡已经接近满载。学习率方面我用的SGDmomentum 0.9weight decay 0.0001初始学习率按batch size线性缩放。如果是单卡batch size为4基础学习率取0.002到0.005之间如果多卡按倍数放大。这里有一个容易踩的坑batch size太小时BN统计量不稳定RetinaNet官方实现里用到了骨干的BN层我建议训练前的骨干加载ImageNet预训练权重后先冻结BN参数或者干脆用大一点的batch size否则前几个epoch的loss会跳得极其夸张。5.2 训练轮数与学习率衰减缺陷检测要“多跑一会”标准COCO上RetinaNet跑1x即12个epoch就足够但工业缺陷场景我建议跑2x甚至3x。原因很简单缺陷形态的类内方差非常大而且大量样本是难样本模型需要更多轮次才能从“见多识广”变成“精确定位”。我当时的策略是总共训练24个epoch在第16和第22个epoch各做一次学习率衰减衰减系数0.1。最后两到三个epoch收敛到平台期时验证集AP基本就不再大幅变化。注意不要只看总loss下降很多情况下总loss还在降但小缺陷类别的AP已经过拟合了。判断过拟合的直观信号是验证集上小划痕类别AP先升后降而训练集loss还在缓慢下降。这种情况要果断早停或恢复衰减前权重。5.3 用哪些指标判断模型是否合格工业缺陷检测不能只看整体mAP要看类别分项AP和不同尺度下的AP。我会专门记录三类指标每类缺陷的AP50和AP75AP75尤其反映了定位精度如果AP50高但AP75低说明框画得不够准在后处理阶段会引出很多误会。小目标像素面积小于32x32的AP这是玻璃缺陷检测的生死项。过杀率与漏检率这两个指标是产线端换算出来的和AP的关系不完全线性通常要结合置信度阈值调整。在迭代中我发现一个规律如果模型在当前版本上漏检集中在某一种细划痕通常不是因为阈值设高了而是这类样本在FPN某层的响应明显不足。排查手段是可视化该层的anchor匹配情况确认是正样本太少还是特征本身没有区分度。这一步能直接指导是收集更多数据还是调整anchor配置。6. 部署落地与误检治理实验室精度不等于产线效果模型在验证集上AP做得再漂亮到了产线上依然会出现一连串状况。这是工业视觉项目的最后一公里也是最容易让工程师崩溃的一公里。6.1 推理链路的工程化我把训练好的PyTorch模型导出为ONNX再转成推理引擎的FP16模型。这个过程中要注意的是ONNX导出时算子的固定输入、非极大值抑制的部署方式、以及预处理细节要保持和训练时一致。很多团队在这个环节出问题是因为把归一化方式从减均值除方差改成了别的导致部署后模型整体失灵。我踩过最典型的坑是输入尺寸。训练时是等比例缩放加pad部署时图省事直接resize到固定尺寸结果细长划痕被拉伸变形检测框偏移严重。所以部署代码里一定要复刻训练时的resize逻辑包括scale方式、pad填充值、以及BGR和RGB的通道顺序。这些细节错一个精度掉一半都有可能。6.2 误检从哪里来产线上的误检目标主要不是缺陷而是玻璃本身的材质和周边环境。我统计过项目的badcase最多的一类是“反光带”被检成划痕玻璃弧边的高光区域在低角度光下会形成一条亮带和划痕的图像特征高度相似。第二大类是灰尘和纤维。产线车间并非完全无尘落在玻璃表面的纤维在低角度光下同样呈亮线状模型根本无法区分“这是玻璃上的划痕”还是“这是环境里的灰尘”。这类问题靠模型本身几乎无解必须要从产线环境治理的角度做比如加强风淋、减少玻璃在处理工序间暴露时间。第三类是玻璃纹。镀膜后的盖板表面有时会出现规律纹理在图像上呈周期性明暗条纹。模型如果没有见过这种“伪缺陷”很容易把它们当成脏污或异物。这类样本需要持续从现场回流加入训练集作为“背景类”或“忽略类”模型后续版本就不再误检。6.3 阈值的设置与漏检/过杀的权衡部署时每个类别和每个尺度的置信度阈值可以分开设。我一般对小划痕类别用稍低的阈值对大块脏污类别用高一些的阈值。原因是过杀和漏检的代价在产线上不同某些工位宁可多杀几个也不愿放走缺陷而有些工位则相反。这里给一个实操建议不要只按验证集的最优F1来定阈值而是拿至少200片带有标注的现场图像做一次“离线试运行”把所有预测结果的confidence score排序记录然后直接画出漏检率和过杀率的关系曲线。用这条曲线和产线工艺人员对拍确定最终的阈值点。这个沟通很有价值因为生产侧可能对不同类型的缺陷容忍度要求完全不一样这个信息模型不知道但工艺人员很清楚。6.4 模型迭代形成“现场badcase回流”的闭环部署只是开始。我当时给项目搭了一个badcase回流通道现场每批次检测中所有“被判为NG但人工复核为OK”和“被判为OK但人工抽检发现缺陷”的图按周自动进入待标注池由标注团队按2.2节的规范补充标注再定期增量训练出下一版模型。这个闭环做起来以后模型对现场的适应能力会越来越强。我见过最快的迭代节奏是两周一个模型版本每版控制在10到20个epoch的小训练量上验证集和现场badcase曲线的改善肉眼可见。与之相比一上来就追求“一次训练永久使用”的策略在工业缺陷检测领域几乎注定失败——因为产线的工况、材料批次、工艺参数一直在缓慢变化模型不跟着变精度一定会退化。最后说一点我个人在项目里的体会手机玻璃盖板缺陷检测这个任务没有哪个单一技巧能包打天下。RetinaNet提供的是一个稳定可靠的框架X101-32x4d让特征提取更充分FPN解决了多尺度难题PISA又把样本利用率往上推了一层。但真正决定项目成败的其实是数据收集的完整度、标注规范的一致性以及部署阶段对产线badcase的持续治理。这套组合拳打下来漏检和过杀率是能做到产线可接受范围的只是中间的细节远比比想象中多。