
简介在工业视觉质检中语义分割通过逐像素分类实现缺陷的精准定位相比目标检测更适合裂纹、夹杂、划痕等不规则表面缺陷。面对背景像素占比极高的类别不平衡问题单纯依靠准确率评估会严重失真需引入IoU、Dice系数与召回率。本文以4100张钢材缺陷图像为样本介绍U-Net与预训练编码器的基线搭建详细讲解加权交叉熵与Dice Loss的组合策略并针对细长缺陷漏检、背景纹理误检等工程难题给出可复用的调优方案。该方法可推广至钢板、纺织、半导体等表面质检场景为工业视觉开发者提供从数据到模型落地的完整参考。 钢材缺陷分割这个方向很多人一上来就奔着深度学习模型去但真正决定项目成败的往往是数据本身。这次做的是4100张带像素级标签的钢材表面缺陷数据任务很明确——多类别分割把裂纹、夹杂、划痕三类缺陷从背景里逐像素抠出来属于标准的语义分割框架下的工业缺陷检测项目。整套流程从数据划分、模型选型到损失函数调优我踩了不少坑也沉淀了一些可以复用的经验这篇就把它完整拆开讲。1. 项目全貌任务定义与数据集解读1.1 一个什么样的任务多类别缺陷的像素级定位工业缺陷检测最常听到的词是目标检测也就是用框把缺陷框出来输出“这里有问题”。但钢材表面的裂纹、夹杂、划痕形状极不规则裂纹是细长条、夹杂是不规则块、划痕是长度不等的线状区域用矩形框去表达这些形态会产生大量背景噪音漏检率和误检率都不好压。所以这个项目选择了语义分割路线。语义分割做的事情是给图像里每一个像素分配一个类别标签输出和输入同分辨率的掩膜图。在多类别分割的场景下每个像素要么是背景要么属于三类缺陷之一这样就能得到缺陷的精确轮廓、面积、位置后续还能拿这些掩膜去做缺陷评级。这个任务对深度学习模型的考验在于裂纹和划痕占整张图的像素比例非常低常常只有百分之零点几而背景占了绝大多数。模型稍有不慎就会把所有像素都预测成背景得到很高的“准确率”却对缺陷毫无感知。这种极端类别不平衡是工业分割和自然图像分割最大的差异点也是整个项目里最需要花心思的地方。1.2 4100张带标签数据不是简单的“拍脑袋”规模这次项目用的是钢材表面缺陷公开数据集SEV数据集的常见配置总计4100张图像每张都有对应的像素级标签。这里要特别说明一个容易混淆的点4100张是总数据量其中非缺陷样本占了大多数真正带缺陷的样本反而是少数。按照常用的官方划分方式训练集2500张其中有缺陷样本约400张、无缺陷样本约2100张验证集832张其中有缺陷样本约112张、无缺陷样本约720张测试集768张其中有缺陷样本约46张、无缺陷样本约722张。这个配置很典型也很反映真实工业场景产线上绝大部分钢板是正常的真正需要模型揪出来的就是少数缺陷。如果按普通分类任务的习惯随机切分数据集很可能出现测试集里某一类缺陷几乎没有的情况评估结论完全失真。我的做法是分层切分先按类别把带缺陷的样本单独拎出来再按比例分配到训练、验证、测试三个集合里保证每个集合都覆盖三类缺陷。这种数据规模在工业分割项目里算“小而精”既没有海量数据撑腰又要求模型具备实用精度反而是学习语义分割和工业检测的好素材。如果换成一个几十万张的工业数据集很多经验反而不容易复现。1.3 三类缺陷的形态差异与标注口径标注口径直接影响模型的收敛上限。这个数据集里定义了三类缺陷标签分别是裂纹、夹杂、划痕各自有明确的物理含义裂纹呈细长、扭曲的条状表面带有分支或断续灰度偏低和背景有较明显的边缘对比。夹杂呈颗粒状或块状边界相对清晰灰度可能偏亮也可能偏暗有点像异物压入钢板表面。划痕呈线状或带状方向性比较强宽度通常比裂纹大一些灰度变化平缓。这三类的形态差异给多类别分割带来了一个天然挑战模型不仅要判断“是不是缺陷”还要分清楚“是哪一种”。实际操作中我见过不少模型把细长的划痕和裂纹混淆尤其是当两者灰度接近时。所以在训练前我习惯先把每类的样本数量、像素占比、形态特征统计一遍做到心里有数再决定数据增强和损失函数的配比。2. 方案选型为什么语义分割框架选择了U-Net系2.1 类别不平衡非缺陷样本占比84%意味着什么把训练集里2100张非缺陷样本和400张有缺陷样本放在一起算非缺陷图像占了大约84%。再往像素层面看一张200×200的图里背景像素几乎占据98%以上缺陷像素常常只有几百到几千个。这意味着如果模型是个“懒汉”把所有像素都预测为背景它在像素准确率上也能拿到95%以上的分数。这种假象在工业项目里非常坑人。只看准确率觉得模型已经很好但放到产线上一跑真正的缺陷一个都没揪出来。所以评估指标绝不能只看准确率要看IoU交并比、Dice系数、以及每类缺陷的召回率。召回率在质检场景里优先级最高漏掉一个缺陷可能意味着整批钢材流向客户端损失远远大于误报。针对类别不平衡常规做法有两类一是重采样让模型在训练时更频繁地看到缺陷样本二是改损失函数给稀有类别更高的权重。这个项目里我用的组合是“加权交叉熵 Dice Loss”后面会专门展开。2.2 疑似缺陷样本为什么难处理SEV数据集里有个很典型的现象很多背景纹理看起来跟缺陷很像材料表面的氧化皮、水渍、辊印、灰度跳变长得都像裂纹或者夹杂。这些被称为疑似缺陷它们的标签仍然是背景但视觉特征和真实缺陷高度重叠。这让模型非常纠结。如果模型把真实缺陷识别出来了但同时也把疑似缺陷判成缺陷那么精确率会下降反过来如果模型为了避开疑似缺陷变得保守真实缺陷的召回率又会掉。这是一个典型的“误检”和“漏检”之间的跷跷板。我的处理思路是数据层面不回避这些样本保留下来让模型见过足够多的“难背景”同时在后处理阶段用形态学操作过滤掉面积过小的预测区域。模型层面则通过合理的损失权重让模型在“背景上犯错”的代价足够高从而学会区分细微差异。2.3 从检测到分割任务需求倒推技术路线有些同事问过我为什么不用目标检测或者实例分割原因在于需求端。这个项目的最终目标是输出缺陷的面积、形态、分布密度用于给钢板做质量评级。目标检测只能给出矩形框无法准确量出裂纹的像素面积和形态走向实例分割虽然能做到逐实例区分但在工业界分割同一类缺陷时往往不需要区分“第1条裂纹、第2条裂纹”反而增加了标注和模型的复杂度。语义分割恰好落在“分类定位形状”的交集上一图一掩膜既直观又容易转换成后续的统计指标。泛化到别的工业场景时也是一样先想清楚“下游要这个掩膜做什么”再决定用哪一种范式不要为了追新而追新。2.4 基线模型怎么选U-Net与Segmentation Models PyTorch模型选型上我第一版跑的是U-Net。U-Net的结构是编码器逐层提取特征、解码器逐步恢复分辨率中间通过跳跃连接把浅层细节和深层语义拼起来。它训练参数少、收敛快在中小规模数据集上表现非常稳适合作为工业项目的第一版基线。实现的时候我直接用了Segmentation Models PyTorchSMP这个库它把很多主流分割模型打包成类似乐高积木的接口一行代码就能实例化一个带预训练编码器的U-Net。我的做法是先用ResNet34作为backbone初始化权重用ImageNet预训练权重虽然钢材表面图像和自然图像差异不小但预训练权重在纹理和边缘特征上的先验仍然能明显加速收敛尤其在数据量不够大的时候。如果SMP库不方便装也可以用PyTorch自己搭一个轻量U-Net结构不复杂但训练时间会长一些。我的建议是初期不要纠结模型结构有多花哨先把U-Net的baseline跑通拿到一组可信的指标再谈升级。3. 训练细节与损失函数调优3.1 预处理与数据划分实操数据预处理这一步看起来平淡实际影响很大。原始图像是灰度图但读入时可以保留单通道也可以复制成三通道配合ImageNet预训练权重。我实测下来三通道输入配合预训练编码器整体更稳虽然多了一点计算量但收敛速度和最终精度都比单通道好。数据划分的细节更不能忽略。先按缺陷类别对应文件列表进行分层划分保证训练集、验证集、测试集中出现过的各类缺陷数量比例一致。然后做像素级别统计计算每一类缺陷的像素占总像素的比重这个比重直接用于加权交叉熵的权重设定。预处理流程可以收敛成下面几步读取原图和对应掩膜统一尺寸到256×256或384×384。灰度图复制成三通道掩膜标签按0、1、2、3映射为背景、裂纹、夹杂、划痕。对输入做标准化均值和方差用训练集的统计值不要用ImageNet的默认值硬套。在线增强时同步对图像和掩膜做同样的空间变换。3.2 数据增强的控制与切图策略数据增强是工业分割项目里最需要“克制”的一环。钢材表面缺陷的形态具有明确的物理意义比如裂纹是细长条划痕有方向性如果无脑做大幅旋转和裁剪可能造出不符合真实场景的样本反而让模型学到错误模式。我用了一组比较保守的增强策略水平翻转、垂直翻转、随机90度旋转这几项可以放心用。随机亮度对比度调整幅度控制在0.8到1.2之间钢材图像的灰度变化本来就不大。随机裁剪从原图中裁出更小的patch相当于变相增加样本量。不轻易用弹性形变或随机擦除前者会扭曲缺陷的细长形态后者容易把真实缺陷擦掉导致标签错误。训练时还可以配合多尺度比如随机把输入缩放到0.75倍到1.25倍但只做轻微缩放避免裂纹这类细长结构在缩放后断裂。3.3 加权交叉熵与Dice Loss的配合损失函数是这个项目里最值得调的部分。我先说结论只用交叉熵在极端类别不平衡下基本不行只用Dice Loss在小目标上又容易震荡两者按比例相加是最稳的组合。加权交叉熵的做法是给每个类别一个权重背景类权重压低缺陷类权重抬高。比如背景是0.05裂纹是0.6夹杂是0.15划痕是0.2具体数值根据像素占比反比调整。它的作用是让模型在“背景上犯错”的代价变大从梯度上直接缓解类别不平衡。Dice Loss则是从重叠度出发直接优化预测掩膜和真实掩膜的交并比。它对小目标更友好因为不管目标多小只要预测和真实的重叠少了损失就大。两者相加之后加权交叉熵负责稳定梯度Dice Loss负责拉高目标区域的重合度在SEV这类数据上实测下来mIoU能提升3到5个点。我推荐用如下组合方式import torch import torch.nn.functional as F def weighted_ce_dice_loss(pred, target, class_weights, dice_weight0.5): ce_loss F.cross_entropy(pred, target, weightclass_weights) pred_prob F.softmax(pred, dim1) # 计算多类别Dice Loss忽略背景类 dice_loss 0.0 for cls in range(1, pred.shape[1]): pred_cls pred_prob[:, cls] target_cls (target cls).float() intersection (pred_cls * target_cls).sum() union pred_cls.sum() target_cls.sum() 1e-6 dice_loss 1 - (2 * intersection / union) dice_loss / (pred.shape[1] - 1) return ce_loss dice_weight * dice_loss这里dice_weight我一般取0.5到1.0如果发现模型过于激进、误检增多就把dice_weight调低一点点。3.4 训练配置与收敛节奏训练配置直接给一组可复用的参数基于单张入门级显卡就能跑起来优化器AdamW初始学习率1e-4weight_decay设为1e-5。学习率调度CosineAnnealingWarmRestarts周期T_010T_mult2配合early stopping。Batch size8到16取决于显存过小会导致梯度噪声大缺陷样本少的类别难以稳定学习。Epochs60到80配合early stoppingpatience设12。评估频率每个epoch结束跑一次验证集记录mIoU、每类IoU、Recall。收敛节奏上前10个epoch指标可能涨得很慢因为模型还在适应类别不平衡到了20个epoch以后随着学习率周期重启指标会有一个明显的台阶式上涨。这时候最容易出现的情况是验证指标光涨不动训练集指标继续下降说明过拟合开始需要及时早停并考虑加一点轻量正则化。4. 落地过程常见问题与排查实录4.1 裂纹这类细长缺陷频繁漏检怎么办裂纹是三类缺陷里最让人头疼的因为它细长且占比极低模型在深层的特征图上经常把它弄丢。我在第一次训练时就遇到了裂纹召回率只有0.3左右的情况预测掩膜要么缺一段要么整条都没出来。排查之后定位到两个原因一是深层特征图分辨率太低小目标信息丢失二是损失函数里裂纹类权重仍然偏小。解决方法是双管齐下先把裂纹的class weight调高到0.7让梯度更偏向裂纹同时把模型输入尺寸从256×256上调到384×384给裂纹多保留一些像素。还有一个很实用的技巧就是给解码器中间层加辅助监督。在U-Net解码器倒数第二层单独接一个分割头计算它和真实掩膜的加权交叉熵损失然后和主损失相加。这相当于让中间层也学习“找裂纹”的能力对细长缺陷的恢复帮助明显。4.2 背景纹理被误判成缺陷怎么处理模型训练到中后期最常见的现象是背景纹理大量被预测成缺陷尤其氧化皮和辊印区域。这个问题本质上是模型没有足够强的“不是缺陷”的证据而归因于数据里真实缺陷和疑似缺陷的样本量差距过大。我的处理方式分三层数据层把验证集里误检最严重的疑似缺陷图像挑出来看它们的掩膜预测结果如果确实和真实缺陷差异很大就考虑用这些图像做一次额外训练相当于hard example mining。模型层降低Dice Loss的权重因为Dice Loss对小目标的敏感性会把一些纹理区域也当成目标拉高。后处理层对预测概率图做条件随机场或简单形态学过滤把面积小于阈值的连通域直接置为背景。形态学过滤是最快见效的手段。用OpenCV的connectedComponentsWithStats统计每个连通域的面积面积小于50像素的直接删除能在几乎不损失召回率的情况下显著降低误检率。4.3 训练Loss不降、评估指标原地打转有几次训练遇到loss卡在某个平台期不动验证mIoU也纹丝不动。排查时先检查标签和输入是否对齐其次检查类别权重是否设置正确最后再怀疑优化器配置。一个容易被忽略的坑是标准化时如果直接用了ImageNet的均值方差而钢材图像本身是灰度、均值方差差异很大模型可能会花很长时间来适应这种分布偏移。后来我改成统计训练集自己的均值方差loss平台的周期明显缩短。另外一个坑是batch size太小。缺陷像素本来就少如果batch size只有4一个batch里可能只有一张带缺陷的图梯度更新不稳定指标会一直震荡。把batch size提到16之后训练节奏稳了很多收敛也更快。4.4 一张速查表常见问题可能原因排查/解决手段裂纹漏检严重缺陷像素占比太低、输入分辨率不足提高裂纹类别权重、增大输入尺寸、增加中间层辅助监督背景纹理误检为缺陷疑似缺陷样本多、模型过于敏感难例挖掘、降低Dice权重、对预测结果做连通域面积过滤Loss长时间不降标签错位、标准化参数不合理、batch太小检查数据加载逻辑、改用训练集均值方差、加大batch size验证指标震荡学习率过大、验证集缺陷样本少调低学习率、使用分层采样构建验证集预测掩膜边缘粗糙解码器上采样过于简单换用带注意力模块的解码器或增加深监督5. 大模型在工业缺陷分割里的定位最近SAMSegment Anything Model系列大模型很火很多人问能不能直接拿来做钢材缺陷分割。我的观点是SAM在工业场景里定位应该是“标注辅助工具”而不是“最终推理模型”。SAM的zero-shot分割能力在自然图像上很强但在钢材表面这类灰度纹理图像上直接推理的边界往往不精准容易把疑似背景区域一起分割进来而且它输出的是一类一类的掩膜没有语义类别信息你还需要自己判断这个掩膜是裂纹还是划痕。如果要用我建议把它用在数据标注阶段。比如先用SAM在无标签的钢材图像上生成候选掩膜再人工筛选和标注能显著减少逐像素标注的时间。至于模型推理环节还是老老实实用自己训练的U-Net或者DeepLabV3这类轻量分割模型稳定可控、推理快、容易部署。大模型的价值在于降低数据生产门槛而不是替代工业场景里的专用模型。这个认知想清楚后面再做类似项目就不容易被热点带着跑偏。整套项目跑下来最深的体会是工业缺陷分割的难点不在模型结构而在数据理解。4100张图不算多但把类别不平衡、疑似缺陷、细长目标这些老问题都暴露了一遍。先想清楚任务要什么再决定技术方案然后花耐心去调损失函数和数据策略最后用合理的评估方式守住可靠性的底线——这套方法论放在其他工业视觉场景里一样能用。本文还有配套的精品资源点击获取