☰
基于深度学习的钢铁缺陷识别与产线部署实践
2026/10/27 11:26:26 网站建设 项目流程

三年前我第一次被拉到某钢厂冷轧精整线的尾端,站在那条跑得飞快的带钢旁边,手里攥着一份“人工开卷检查”的排班表。检查工拿着手电筒趴在带钢上找缺陷,一卷几千米,眼睛盯到发花,漏检全靠运气,一卷判级判错就是几万块的损失。那一刻我脑子里只有一个念头:这事儿必须交给深度学习来做,靠人眼和传统算子已经顶不住了。基于深度学习的钢铁缺陷识别,说白了就是用卷积网络代替人眼,在带钢、板坯、棒材甚至铸坯表面上自动找出裂纹、夹杂、划痕、麻点这些毛病,并且把位置、类别、尺寸一起报出来。它解决的核心问题不是“能不能看见”,而是“在每秒几米的生产节拍下、在油污粉尘和反光干扰下,稳定地把缺陷和正常纹理分开,同时把过杀率压到产线能接受的水平”。

这套东西适合谁来参考?如果你是在工厂里做自动化改造的工程师,看第三、五章就够上手;如果你是做视觉算法的,第二、四章的数据和调参细节更对胃口;如果你只是刚接触工业质检的学生或者转行者,前面两章的背景和路线对比能帮你少走半年弯路。我下面写的是我自己从数据集搭建、模型训练到产线部署踩过来的完整链路,包含大量“文档里不会写、但现场一定会遇到”的细节。很多环节的结论和网上教程是反着来的,比如数据增强不能随便翻转、评估指标不能只看准确率、模型精度往往不是瓶颈而存储带宽才是。

1. 先想清楚:钢铁缺陷检测为什么必须换赛道

1.1 传统机器视觉方案的天花板到底在哪

很多工厂在换深度学习之前,其实已经上过一套基于传统算子的视觉系统:打一束条形光,用高分辨率相机拍灰度图,然后做中值滤波、Sobel 边缘、阈值分割、形态学闭运算,最后按面积和长宽比判缺陷。这套方案在缺陷对比度高、背景干净的场景下确实能跑,比如某些镀层表面的明显划伤。但只要换个钢种、换一批来料、换一个班次的照明状态,阈值就得重调,调完这一批好使,下一批又崩。

根本原因在于,钢铁表面缺陷的“特征”根本不是几何特征。裂纹可能只有几个像素宽、灰度差不到 10 个灰阶,和轧制纹路、氧化皮残留、水渍在低层特征上几乎无法区分。传统算子做的是“人工定义规则”,而人的规则表达能力有上限;深度学习做的是“从数据里学规则”,它能利用纹理的上下文、缺陷的走向连续性、周边区域的统计分布这些很难用公式写出来的东西。我做过一个对比:同一批含夹杂和氧化皮压入的样本,传统方案在调到最优阈值时召回率 76%、过杀率 18%;换成一个只有 4.7M 参数的轻量分类网络,召回率 93%、过杀率 6%。这个差距不是调参能补上的,是方法论层面的代差。

但我也要说句公道话:传统算子不是没用。在整套系统里,它最适合干“粗定位”和“异常初筛”的活——用简单的行列灰度投影、局部方差突变先把可疑区域圈出来,把送给神经网络的图像面积压缩到原来的 1%~5%。这一步省下来的算力,比你在模型上抠十天参数都值钱。

1.2 工业现场的四道硬约束,决定了你所有的技术选型

做学术数据集调模型,你只需要关心 mAP;做产线系统,你要同时被四根绳子拴着,任何一根断了系统就废了。

第一根是节拍。精整线带钢速度常见 2~5 m/s,高端线更快。假设检测精度要求 0.1 mm/pixel,线速度 3 m/s,那么线阵相机的行频必须是 3000 / 0.1 = 30000 行/秒,也就是 30 kHz。这个行频下,单台 8K 灰度相机每秒吐出约 8192 × 30000 × 1 字节 ≈ 245 MB 的原始数据,双相机拼一幅 16000 像素宽的全幅图,数据量直接翻倍。你没有可能把这玩意儿全存下来,更没可能全送进 GPU。所以架构上必须做“选择性留存 + 分级筛查”,这一点后面第五章会展开算。

第二根是精度。客户不会跟你说“mAP 要到多少”,他们说的是“漏检率低于 0.5%,过杀率不高于 5%”。注意这两个指标是互相拉扯的,你把置信度阈值调低,漏检下去了,过杀立刻上来,产线因为频繁误报停机,班组长第二天就来找你。真正可用的工作点,是在过杀率约束下把召回做到最高,而不是追求某个单一指标的漂亮数字。

第三根是光照与污染。钢厂环境里镜头一天不擦就一层灰,轧机乳化液飞溅、氧化铁皮粉尘、水雾,全都会落在镜头和光源罩上。这意味着你的模型必须对“整体对比度缓慢下降”这件事有鲁棒性,而不是只在你实验室那套打光下有效。

第四根是样本。缺陷是小概率事件,一条线一天可能就出几十个缺陷,你要凑够每类几千张标注样本,往往要攒几个月甚至跨产线收集。所以少样本、类别不均衡、标注成本,这三件事从一开始就得设计进方案里,不能等训练崩了再补。

2. 数据集这件事,决定了项目八成的成败

2.1 公开数据集只能用来验证思路,别指望它上线

业内流传比较广的公开钢材表面缺陷数据集,一般是六类典型缺陷、几百到一千多张图、200×200 左右的灰度图,类别包括夹杂物、斑块、麻点、氧化皮压入、划痕、裂纹。拿来跑通流程、对比几个骨干网络、验证损失函数有没有写错,它非常好用。但我必须直说:拿它训出来的模型直接上线,一次都不会成功。

原因有三个。第一是成像条件完全不同,公开集是离线采集、打光均匀、背景干净的裁剪块,产线是线扫、有反光带、有周期性轧制纹路。第二是缺陷尺度分布不同,公开集里缺陷基本占满画面,产线上一个 0.3 mm 的点状麻点在 512×512 的图里可能只占 3×3 个像素。第三是类别定义不同,公开集的“斑块”和产线上的“氧化皮压入”在视觉上高度相似,你按公开集的标签体系训练,上线后必然出现大量类别混淆。

我的做法是:公开集只用来做两件事——快速验证训练脚本和损失函数是否正常(loss 能不能收敛、能不能过拟合一个小批次),以及做骨干网络的初步选型对比。真正的训练集,必须从目标产线自己采。

2.2 类别定义与标注规范,这是最容易翻车的地方

你现在让三个老师傅去标同一批图,他们给出的标签一致率可能只有 70%。这不是他们不专业,是缺陷本身的边界就模糊。所以正式标注之前,必须先把标签体系定死,写成一张表,标注人员照着执行。

缺陷类别视觉特征标注边界规则常见混淆对象
裂纹细长、走向连续、灰度偏暗、两端渐细按可见连续性框完整条,断开超过 5 mm 拆成两个划痕、轧制纹
夹杂块状或条带状、内部灰度不均、边界清晰沿外轮廓紧贴框选,不含周边过渡区氧化皮压入、斑块
划痕直线或弧线、宽度均匀、常成组平行只框划痕本体,不含机械压痕裂纹、导板擦伤
麻点密集点状、直径常在 1 mm 以下密集区整体框一个,稀疏点逐个框氧化铁皮颗粒、水渍
氧化皮压入不规则片状、边缘参差、灰度深浅混杂整片框选,允许包含内部正常区夹杂、斑块
辊印周期性横向条纹、间距与辊径对应整条周期性区域框选,标注周期振动纹、来料原始条纹

规范之外还有两个动作必须做。一是双人交叉复核,任何一张图至少两个人标过,不一致的进仲裁队列,由工艺工程师拍板。二是负样本要主动收集,很多人只标缺陷,结果模型没见过“长得像缺陷的正常纹理”,一上线到处误报。我通常会按 1:3 到 1:5 的比例加入难负样本,专门挑那些曾经误报过的区域重新标注为“正常”,这部分数据的价值比额外的正样本高得多。

提示:标注工具里一定要保留“不确定”这个选项。强迫标注员在二选一里猜,最后污染的是整个数据集,而且你事后根本查不出来哪些是猜的。

2.3 数据增强有边界,钢厂的图和自然图像不是一回事

网上讲数据增强的文章,一上来就是随机旋转、随机裁剪、色彩抖动、MixUp。放到钢铁缺陷上,其中一半是毒药。

随机旋转要禁用或者严格限制。轧制方向是有物理意义的,裂纹基本沿轧向延伸,辊印是横向周期性条纹,你把图转 90 度,裂纹变成横向,模型学到的方向先验就被破坏了。我一般只允许 ±5 度以内的小角度旋转,或者直接不转。

水平/垂直翻转要分情况。对于斑块、麻点这类各向同性的缺陷,翻转没问题;对于裂纹、划痕这种有明确走向的,翻转相当于凭空造了一批现实中不存在的样本,会拉低模型对走向的敏感度。稳妥做法是:只在各向同性类别上开翻转。

真正有效的是这几类:亮度和对比度的随机扰动(模拟光源老化和镜头污染)、高斯噪声和模糊(模拟对焦漂移)、随机遮挡小块(模拟水滴和粉尘遮挡)、以及针对小样本缺陷的 Copy-Paste——把标注好的缺陷实例抠出来,随机贴到正常背景上。Copy-Paste 对小样本类别特别管用,我用它把夹杂类的样本量从 300 张扩到 4000 张,那一类的召回直接从 68% 拉到 88%。但注意贴合边缘要做羽化和泊松融合,硬贴会产生明显的接缝伪影,模型会去学那个接缝而不是缺陷本身。

import random, cv2, numpy as np def industrial_augment(img, label, defect_cls): # 各向同性类别才允许翻转 isotropic = {"pitted", "patch", "inclusion"} if defect_cls in isotropic: if random.random() < 0.5: img = cv2.flip(img, 1) # 小角度旋转,绝不 90 度 if random.random() < 0.3: ang = random.uniform(-5, 5) h, w = img.shape[:2] M = cv2.getRotationMatrix2D((w / 2, h / 2), ang, 1.0) img = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_REFLECT) # 光照扰动:模拟镜头污染导致的对比度衰减 if random.random() < 0.5: alpha = random.uniform(0.75, 1.15) # 对比度 beta = random.uniform(-18, 18) # 亮度偏移 img = np.clip(img.astype(np.float32) * alpha + beta, 0, 255).astype(np.uint8) # 高斯模糊:模拟对焦漂移与粉尘 if random.random() < 0.2: k = random.choice([3, 5]) img = cv2.GaussianBlur(img, (k, k), 0) return img

3. 模型选型:先问要输出什么,再问用什么网络

3.1 分类、检测、分割三条路线,别一上来就上最贵的

选型的第一步不是挑骨干网络,是确定你要的输出粒度。这三条路线的成本差距能达到十倍以上,很多项目死在“明明只需要判有没有,却非要上分割”。

路线输出标注成本推理开销适用场景
图像分类整张图的类别最低,只标图级标签极低固定工位、缺陷必定落在视野中心、只需报警不需定位
目标检测缺陷框 + 类别 + 置信度中,需画框中大部分带钢表面检测,需要知道位置和尺寸
语义/实例分割像素级掩膜最高,需描边高需要精确计算缺陷面积、影响判级,如夹杂面积占比

我的经验是:先做分类,再做检测,分割按需。分类模型可以作为第一级粗筛,把“明显正常”的图挡掉,只把可疑图交给后面的检测模型,整个系统的吞吐能提升五到十倍。而分割只有在客户明确要求“输出缺陷面积用于判级”时才值得上,因为像素级标注的成本是画框的三到五倍,而且标注一致性更差。

检测模型上,工程上我更倾向单阶段的 YOLO 系列实现,选 n 或 s 这种小模型,理由是部署链路成熟、支持动态输入尺寸、导出 ONNX 后量化路径清晰。Faster R-CNN 这类两阶段方案精度在某些小目标场景确实略优,但推理链路复杂、后处理里的 NMS 参数在密集小目标上很难调,产线上不划算。分割就用轻量的 U-Net 变体或者基于 Transformer 的分割网络,前者参数少、对小数据更友好,后者在缺陷边界模糊时表现更好但要更多数据。

3.2 骨干网络与轻量化的取舍逻辑

骨干网络的选型要看你的算力预算和精度缺口。给一组我实测过的数字,输入 512×512 灰度图,单张 T4 上的推理耗时(FP16,含后处理):

骨干参数量单图耗时缺陷召回备注
MobileNetV3-Small2.5M2.1 ms86.2%边缘设备首选,精度略欠
ResNet1811.7M4.8 ms91.5%性价比最高的基线
ResNet5025.6M11.3 ms93.1%精度提升边际递减明显
EfficientNet-B05.3M5.6 ms92.4%小数据上容易过拟合
轻量 ViT 变体5.7M7.9 ms92.8%需要更多数据,标注少时别碰

从 ResNet18 换到 ResNet50,参数量翻了一倍多,耗时翻了一倍多,召回只涨了 1.6 个百分点。这 1.6 个点在产线上值不值,取决于你的 GPU 预算和节拍余量。我的做法是先拿 ResNet18 打到瓶颈,如果瓶颈是“模型容量不够”再往上换;如果瓶颈是“数据标注质量不行”,换再大的网络也是白搭——这一点我吃过亏,有次硬上 ResNet101,训练集 loss 掉到 0.01,验证集纹丝不动,问题出在训练集里有一批标签标错了类别。

另外要提醒一句:工业图像很多是单通道灰度,别硬转成三通道喂给 ImageNet 预训练模型就完事。我的做法是保留三通道结构、把灰度值复制三份,这样能直接吃预训练权重;同时把第一层卷积的权重按通道求平均再复制回去,比随机初始化收敛快大概 30% 的 epoch。

3.3 细长缺陷和小目标,是这类任务的真正难点

钢铁缺陷里最难的不是块状缺陷,是裂纹。一条长 200 mm、宽 0.2 mm 的裂纹,在 0.1 mm/pixel 的图上就是 2000×2 像素,长宽比 1000:1。你用什么 anchor 都覆盖不了,常规的 3×3 卷积核在感受野里也几乎看不到它。

我试过三种解法,效果从差到好排列。第一种是加大输入分辨率,直接把 512 提到 1024,小目标确实好了一些,但推理耗时翻了四倍,节拍撑不住。第二种是改 anchor 比例,加上 1:8、1:12 这种极端长宽比,召回涨了 5 个点,但对不规则走向的裂纹依然无力。第三种是把图像切成沿轧向的长条 tile,比如 512×128,专门给裂纹类做一条独立的检测分支,和主分支的结果做融合。这个方案召回能到 90% 以上,代价是推理要做两次前向。最终我采用的是级联结构:主分支跑全图找块状缺陷,同时输出一个“疑似细长结构”的掩膜,只把这些掩膜区域的坐标送进第二条分支放大重检。综合耗时只增加了 18%,但裂纹召回从 71% 提到了 89%。

还有一个偏门但特别管用的小技巧:在通道维度上叠加梯度幅值图。把原图和它的 Sobel 幅值图堆成两通道输入,模型对边缘的敏感度会明显提升,尤其是低对比度的浅裂纹。这个改动只增加了第一层卷积的输入通道数,几乎不增加计算量。

4. 训练与调参:那些让你模型“看着能跑其实不能上线”的坑

4.1 类别不均衡,是所有工业质检的共性难题

正负样本比 1:100 是常态,某些稀有缺陷能到 1:2000。这时候用普通的交叉熵,模型会学到一个“全部判正常”的退化解,准确率还有 99%,看起来特别漂亮。

常规解法是 Focal Loss 或者加权交叉熵,但我实测下来,在钢铁缺陷上更有效的是难例挖掘 + 重采样组合拳。具体做法是:第一个 epoch 用普通交叉熵把所有样本过一遍,然后按每个样本的 loss 排序,取 loss 最高的前 20% 正样本和 top 5% 的负样本,组成一个“难例池”,后续训练时这个池子的采样概率放大 3 倍。Focal Loss 的 γ 参数我会设 1.5 而不是论文常用的 2.0,因为工业缺陷的难负样本(氧化皮质感)本身就很难,γ 太大容易把这些难负样本直接放弃掉,反而导致过杀率上升。

分割任务上,Dice Loss 和 BCE 的混合比单用任何一个都稳,比例大概 1:1。Dice 负责拉整体重叠度,BCE 负责稳住像素级分类的梯度。我在一个夹杂分割任务上试过纯 Dice,训练前期 loss 震荡得非常厉害,混了 BCE 之后曲线立刻平滑。

4.2 超参数不需要玄学,按数据量推就行

很多人调参靠试,我给一套我常用的推算逻辑。学习率:用带预训练权重的骨干时,backbone 的 lr 设为检测头的 1/10,初始值取 1e-3(AdamW)或 1e-2(SGD + momentum 0.9)。Batch size:能塞多大塞多大,但因为工业数据集往往只有几千张,batch 太大一个 epoch 只有几十步,BN 统计量也不稳,我一般控制在 16~32,配合梯度累积模拟大 batch。训练轮次:看验证集召回曲线的拐点,钢铁缺陷任务通常在 40~80 个 epoch 收敛,超过 100 还没收敛基本是数据或者标签有问题,别硬训。

学习率调度用余弦退火 + 3 个 epoch 的 warmup,warmup 这一条千万别省。工业数据集小,开头几步梯度方向噪声很大,不做 warmup 很容易一开始就把预训练权重带偏,后面怎么训都回不来。我遇到过最典型的一次,去掉 warmup 后模型前 5 个 epoch 的 loss 一直在 0.69 附近打转(二分类的随机水平),加回 warmup 立刻正常下降。

import torch, torch.nn as nn from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR def build_optim(model, backbone_lr=1e-4, head_lr=1e-3, warmup_epochs=3, total_epochs=60): backbone_params, head_params = [], [] for name, p in model.named_parameters(): (backbone_params if name.startswith("backbone") else head_params).append(p) opt = torch.optim.AdamW([ {"params": backbone_params, "lr": backbone_lr}, {"params": head_params, "lr": head_lr}, ], weight_decay=1e-4) warmup = LinearLR(opt, start_factor=0.1, total_iters=warmup_epochs) cosine = CosineAnnealingLR(opt, T_max=total_epochs - warmup_epochs, eta_min=1e-6) return opt, SequentialLR(opt, [warmup, cosine], milestones=[warmup_epochs]) # 类别加权:按频率的倒数开方,避免稀有类权重爆炸 def class_weights(counts, power=0.5): inv = 1.0 / torch.tensor(counts, dtype=torch.float32).clamp(min=1) w = inv.pow(power) return (w / w.sum() * len(counts)).clamp(max=8.0)

注意最后那个clamp(max=8.0),这是我自己加的保险。某些稀有类样本只有几十张,按倒数算出来的权重能到 50 以上,训练直接发散。开方之后再截断,是我试下来最稳的组合。

4.3 评估指标别只看准确率,那玩意儿在工业上会骗人

产线关心的指标和学术界完全不是一套。我总结的对照关系是这样:漏检率对应召回(Recall),是红线指标,通常要求 99% 以上(注意是召回,也就是缺陷里被找出来的比例);过杀率对应假正率(False Positive Rate),是成本指标,通常要求 5% 以内;定位精度用 IoU 0.5 下的 mAP 衡量,主要影响后续自动判级;响应延迟是硬指标,超过节拍就是废的。

还有一个特别容易被忽略的指标:按卷统计的稳定性。单张图的指标好看,不代表一整卷几千米的检测稳定——可能某一段光照变了,模型就整段失效。所以我做验收时一定会按卷统计“每千米缺陷数”和“单卷内误报次数的方差”,方差大的模型在产线上是不可信的,哪怕它的平均指标更好。

5. 从实验室到产线:部署这一段才是真正的地狱

5.1 算力预算要先算清楚,再决定模型多大

我在第五章开头提过数据量,这里把它算完整。假设幅宽 1500 mm,精度 0.1 mm/pixel,则横向需要 15000 像素,用两台 8192 像素的线阵相机拼接覆盖。线速 3 m/s,行频 30 kHz。如果按 512×512 的 tile 做推理,每个 tile 覆盖 51.2 mm × 51.2 mm,那么每秒钟产生的 tile 数是:

横向 15000 / 512 ≈ 30 列,纵向 3000 mm/s / 51.2 mm ≈ 58.6 行/秒,合计约 1758 个 tile/秒。

就算单张推理只要 2 ms,1758 张也需要 3.5 秒的 GPU 时间,而实际只有 1 秒。所以你绝无可能对全幅全速做逐 tile 推理。可行的架构只有一种:两级筛查。第一级用极轻量的分类器(MobileNetV3-Small 级别,输入降到 128×128),对降采样后的图像做粗筛,耗时控制在 0.2 ms 以内,理论吞吐能到 5000 张/秒,把所有 tile 快速过一遍,只保留置信度落在可疑区间的 3%~8%。第二级才用高分辨率检测模型处理这些可疑 tile,此时每秒只需要处理 50~140 张,算力完全富余。

环节输入模型单张耗时每秒处理量硬件
一级粗筛128×128轻量分类0.2 ms5000单张 T4
二级精检512×512小目标检测4.8 ms200单张 T4
分割(可选)512×512轻量 U-Net9 ms110单张 T4

这套架构还有个额外好处:一级粗筛的模型更新频率可以很高,因为它训练成本低、对光照漂移最敏感,出了新样本一周就能迭代一版;二级模型则保持稳定,不轻易动。

5.2 与产线联动,坐标映射比模型本身更容易出错

模型输出的是“图上的框”,产线要的是“第 3 号卷、距头部 1247.3 米、距操作侧 682 mm 处有一处长 15 mm 的裂纹”。这中间的坐标映射,是整个项目里最容易出低级错误的地方,而且错了很难发现——因为系统看起来在正常工作,只是位置全偏了。

映射链路是这样的:编码器脉冲 → 相机行号 → 带钢纵向位置;相机标定 → 像素坐标 → 横向物理位置;再叠加上焊缝跟踪信号,扣除活套和张力带来的位置漂移。每一步都要单独验证。我的验证方法很土但有效:在产线停机时,人为在带钢上贴一张带刻度的标定纸,让系统识别并输出坐标,然后拿卷尺去量,误差控制在 ±3 mm 以内才算过。

注意:焊缝信号丢失的那几百毫秒,坐标会整体漂移,缺陷会被算到错误的卷上。这类问题的表现是“某个卷突然报了几十个缺陷、下一个卷干干净净”,排查时先看焊缝跟踪日志,别一头扎进模型里。

推理加速这块,路径很标准:PyTorch 训练 → 导出 ONNX(注意固定输入尺寸,动态尺寸会拖慢 TRT 优化)→ 用推理引擎做 FP16 或 INT8 量化。FP16 基本不掉精度,速度提升 1.6~2 倍,无脑上。INT8 要小心,我用 500 张正常图 + 200 张缺陷图做校准集,量化后召回掉了 2.3 个点,过杀涨了 1.8 个点,最后是“一级粗筛用 INT8、二级精检用 FP16”才把指标捞回来。

# 导出 ONNX,注意固定 batch=1、固定输入尺寸 python export.py \ --weights runs/detect/best.pt \ --imgsz 512 512 \ --batch 1 \ --opset 12 \ --simplify \ --include onnx

5.3 现场环境带来的三个“模型之外”的失效点

镜头污染是一次缓慢的正弦式衰减。刚擦完镜头那天,对比度最好,模型表现最佳;十天之后,整体对比度下降 15%,浅裂纹开始漏检。解决办法不是天天擦镜头(产线不会给你这个时间窗),而是在系统里加一个“图像质量监控”模块:实时统计每帧的灰度均值、方差、梯度能量,一旦低于基线一定比例就触发清洗提示,同时把这段时间的图像标记为“低置信区间”,在判级时给一个降级标记。这个功能上线后,客户抱怨“系统时好时坏”的声音基本消失了。

机械振动带来的是横向像素偏移,典型表现是缺陷框在同一个纵向位置上反复横跳。如果你定义了缺陷合并逻辑(同一位置的框要去重),横向抖动会导致同一个缺陷被算成两个,缺陷数量翻倍。我的处理是在后处理里加一个基于纵向距离的聚类,纵向间隔小于 20 mm 的同类框合并成一个,横向取加权中心。这个简单的后处理让缺陷计数准确率从 82% 提到了 96%。

光源频闪与相机行频不同步,会产生周期性的明暗条纹,这类条纹在某些角度下和辊印高度相似。这种问题必须从硬件上解决——用相机触发信号同步驱动光源,不能靠模型去学。我见过一个团队花了两个月调模型想消除条纹影响,最后发现是光源驱动器没接同步信号。

6. 常见问题与排查速查表

6.1 典型故障对照表

现象最可能原因排查动作处理方式
训练 loss 不下降,停在 0.69 附近学习率过大、无 warmup、标签全为同一类打印一个 batch 的标签分布加 warmup,lr 降 10 倍,检查标签
训练集 loss 极低,验证集召回很低标签错误、训练/验证集来自不同批次抽 50 张验证集图人工看预测结果重新核对标签,按批次划分数据集
上线后过杀率突增光照变化、镜头污染、模型与现场成像不匹配对比现场图和训练集图的灰度直方图补现场负样本重训,加图像质量监控
缺陷位置整体偏移编码器标定错误、焊缝信号丢失用标定纸做坐标验证重新标定,加焊缝信号冗余检测
同一缺陷被重复计数横向振动导致框分裂统计同一纵向位置的框数量加纵向距离聚类后处理
裂纹类长期漏检长宽比过大、输入分辨率不足统计裂纹类召回加长条 tile 分支或梯度通道
推理延迟超节拍全幅逐 tile 推理、未做量化用性能分析工具看耗时分布改两级筛查,上 FP16/INT8
模型版本更新后指标回退新数据集分布偏移、旧难例丢失对比新旧版本在固定测试集上的表现建立固定回归测试集,版本灰度的上线

6.2 几条用真金白银换来的经验

固定测试集必须冷冻,永远不许拿去训练。我在项目早期犯过一个错:每次补新样本,都顺手加进训练集,结果验证指标一直涨,上线毫无改善。后来才知道,我一直在用“见过的题”考自己。正确做法是留一个 500~1000 张的固定测试集,按钢种、按班次、按月份分层抽样,覆盖尽可能多的工况,这个集合只在版本发布前跑一次,任何情况下都不进训练。

模型的版本管理要和生产系统的灰度机制配合。我们现在的做法是新模型先跑“影子模式”——接实时数据做推理但不输出控制信号,只在后台记录它和当前生产模型的分歧,跑满一周、分歧率稳定在合理区间后才切换。这个机制救过好几次,有一次新模型在夜班时段(光照条件不同)召回掉了 12 个点,影子模式直接提前发现了。

别忽略“模型能解释”这件事。产线上的班组长不信任黑盒。我们用 Grad-CAM 做了一个简单的热力图叠加,缺陷框旁边能显示模型关注了哪块区域。这玩意儿对精度没任何帮助,但对项目推进的作用极大——老师傅看到模型关注的地方和他们的判断一致,才开始愿意相信系统。技术之外的说服工作,往往比调参更耗时间。

最后再补一个数据回流的设计。系统上线不等于结束,产线每天都会产生新的难例。我们在检测端做了一个“低置信度自动留图”的机制:任何置信度落在 0.3~0.7 区间的样本,连同上下文自动存盘并打上时间戳和卷号标签,每周由工艺人员复核标注一批,回灌进训练集。这个闭环跑起来之后,模型的月度迭代基本不需要额外组织采样,靠系统自己就能持续变好。这套东西听着简单,但真正在产线上跑通,从存储带宽、样本去重到标注任务的派发,每一步都得设计,我花了大概三个月才让它稳定运转起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询