小样本缺陷检测实战:PyTorch数据增广与漏检控制全流程
发布时间:2026/10/6 17:47:46 作者:尧图编辑部 阅读量:1,286

产线跑不起来的时候谁也顾不上什么“算法优雅”。缺陷检测这个项目难的地方从来不是模型有多先进而是你在现场拿到的数据就那么几百张废品率要压到千分之一以内漏检一多客户那边直接就不干了。我做过的几个工业视觉项目几乎都是这个套路开局甲方给一批良品图和少量缺陷图标注质量参差不齐缺陷类型五花八门然后要求算法上线后不能漏、不能多报。这篇文章就是把我在小样本条件下做缺陷检测的完整流程和个人经验写出来从数据怎么扩、模型怎么选、阈值怎么定到漏检怎么控制和排查一条线串清楚。我会结合PyTorch这套主流技术栈来讲同时也给出部署和联调阶段的实操细节。如果你是做算法开发、质检系统实施或者是工厂里要自己搭视觉方案的工程师照着这个流程走一遍至少能少踩一半的坑。1. 整体设计与方案选型1.1 小样本缺陷检测的难点在哪缺陷检测跟通用分类任务有个本质区别——缺陷的出现是有长尾特征的。同一个产品上划痕可能有几十种形态脏污又有几十种形态而且这些形态彼此之间还有交叉。你要是用常规的思路把缺陷检测当成一个单纯的“分类问题”来做就会立刻卡在样本采集上每个类别至少要几百张图才能训得像样但产线上真正能采到的缺陷样本十个类别加起来可能就两三百张。小样本问题的第一层难点是数据量不够第二层难点是缺陷的形态方差太大。同样叫“划痕”有的是细线状有的是块状有的高亮有的暗纹。如果模型只见过其中一部分上线后遇到没见过的形态就很容易漏检。所以我做这个项目的第一个决定就是不把缺陷检测当单纯的分类来做而是当成“异常检测细分类”的组合问题来解——先用一个能区分“正常”和“不正常”的模型把大面挡住再在这个基础上去做缺陷类型的判定。这样做的好处是正常样本大量可采异常检测模型对“未知缺陷”的容忍度天然就高比一头扎进小样本分类任务要稳得多。第二层难点是标注噪声。工业现场的标注通常都很粗糙同一个缺陷A标注员画了个框B标注员画了半个框还有的缺陷类间边界模糊标注本身就有争议。你用小样本去训一个有监督模型这些标注噪声会被模型成倍放大导致训练过程震荡。所以数据清洗和预处理这一步在小样本场景下比模型结构都重要。1.2 方案选型模型结构与预训练策略选型上我用了两条腿走路有监督路线对于有明确标注的缺陷数据用轻量级卷积模型做分类和定位。考虑到产线上的显卡资源通常很紧张我一般选ResNet系列作为主干如果缺陷目标很小再叠加FPN结构做特征融合。无监督/自监督路线用大量正常样本做自监督预训练让模型先学会“什么是正常的”再拿少量缺陷样本微调。这一步非常关键因为小样本场景下直接随机初始化从头训模型模型学不到足够的底层视觉特征收敛极慢且效果差。自监督预训练这里我推荐用MAEMasked Autoencoder或者SimCLR这类方法它们不需要标签只需要把正常样本喂进去学习特征。一个比较实在的做法是先用正常样本训练MAE重建任务训练200个epoch左右把Encoder部分拿出来做下游任务的初始化权重再用少量缺陷样本微调分类头。实测下来这个策略能把小样本分类的准确率提升10到15个点尤其在缺陷种类超过5类的时候收益更明显。还有一个可行但工业界用得还不太多的方案是直接用CLIP这类图文预训练模型做zero-shot。如果产线采集的图片配合了文本描述比如“表面浅划痕”、“边缘破损”可以用CLIP做初步缺陷筛选再结合手工规则过滤。不过这个方案依赖对缺陷语义的准确描述在复杂纹理场景下表现不稳定我一般只用它来做快速原型验证不直接用于生产。2. 数据准备与增广的细节2.1 可控增广是唯一的出路小样本条件下数据增广不是“锦上添花”是“保命手段”。但增广不是无脑堆堆错了反而会让模型学到错误的不变性。我自己的经验是把增广分为三类几何增广翻转、旋转、缩放、裁剪。这组增广对工业场景基本都能用因为缺陷在图像里的位置往往是随机的、角度也是随机的。颜色/光照增广亮度、对比度、饱和度、色调调整。产线的光源稳定性虽然比自然光好但不同班次、不同设备、不同老化程度的灯源还是会导致图像整体分布偏移这组增广能大幅提升模型对不同采图环境的适应能力。缺陷专用增广混合切割Cutmix、粘贴复制。特别说明一下粘贴复制就是把缺陷区域从一张图里裁剪出来随机贴到正常样本的随机位置。这个方案在工业场景下极其好用因为很多缺陷本质上就是局部纹理异常并不依赖全局上下文。缺陷专用增广里有个关键细节如果你用随机粘贴的方式生成训练样本一定要记录粘贴的位置和对应的标签这样可以在训练时让模型只关注缺陷区域而不是把整个图都当成“有缺陷”。我用的是给粘贴区域生成一个mask的方案——粘贴完缺陷区域后同时生成一个二值mask模型在计算loss的时候背景区域的梯度清零只让前景缺陷区域回传。这个做法对小目标缺陷特别有效不然模型会被大量正常背景淹没根本学不到缺陷特征。另外一个非常实用的技巧是缺陷融合从两张缺陷图里各取一块区域融合在一张图上。这能用有限的缺陷数据创造出组合形态尤其适用于像“划伤脏污”这类复合缺陷。融合时要控制好两个区域的透明度和边界平滑度否则生成出来的样本会带明显的拼接痕迹模型学到的就不是缺陷特征而是拼接痕迹了。2.2 数据清洗与标注校准小样本场景下标注质量对最终效果的影响远大于数据量。我踩过的坑是有一批样本标注人员把“正常纹理极值”标成了“划伤”这一类别里混入了差不多10%的错误样本结果训练出来的模型在验证集上反复出现误检怎么调阈值都没用。所以我定了一个流程先做数据预清洗再做标注校准。用异常检测模型比如用一个简单的AE重建误差跑一遍所有样本把正常样本里重建误差特别高的挑出来人工确认是不是漏标的缺陷把所有同类型缺陷图放到一起做聚类肉眼过一遍聚类后的特征分布把离群样本找出来逐张确认——这时你会发现有不少实际是错标或者混类的情况对有争议的类间边界统一口径。比如“浅划伤”和“正常纹理波动”的分界线是什么要在动手训练前就定清楚否则模型会学到你这个判定标准以外的东西。上面这三步听起来繁琐但每一步都能实打实降低漏检和误检。3. 训练策略与模型调优实战3.1 模型训练关键参数与流程我以PyTorch为例整理了一份在小样本缺陷检测任务上跑得比较稳的流程代码结构和参数可以直接拿来参考。首先是数据集划分。小样本场景下不能按常规比例划分训练集、验证集因为缺陷样本本来就少再拿20%去做验证训练数据就所剩无几了。我建议采用K-Fold交叉验证K取3或者5每一轮用一部分样本做验证最后对结果取平均。这样能充分利用有限的缺陷数据也能比较稳定地评估模型效果。然后是训练流程。我用的方案是先用大量正常样本跑自监督预训练MAE或对比学习再用少量缺陷样本微调分类模型。import torch import torch.nn as nn import torchvision.models as models from torch.utils.data import DataLoader from torchvision import transforms # 自监督预训练阶段MAE 或 SimCLR 以 SimCLR 为例 # 这里简化为用正常样本训练一个子网络输出为特征向量 class ProjectionHead(nn.Module): def __init__(self, in_dim2048, out_dim128): super().__init__() self.layers nn.Sequential( nn.Linear(in_dim, in_dim, biasFalse), nn.BatchNorm1d(in_dim), nn.ReLU(inplaceTrue), nn.Linear(in_dim, out_dim, biasFalse) ) def forward(self, x): return self.layers(x) backbone models.resnet50(weightsNone) backbone.fc nn.Identity() # 去掉分类头只保留特征 # 自监督训练时用对比损失输入为正常样本的两个不同增强视角 # 伪代码示意view1, view2 是同一个样本经过不同增广后的结果 # feat1 backbone(view1); feat2 backbone(view2) # loss contrastive_loss(feat1, feat2) # 训练完成后backbone 即为预训练权重微调阶段加载上述预训练权重替换分类头用有标注的缺陷数据训练# 微调阶段 model models.resnet50(weightsNone) pretrained_dict torch.load(pretrained_backbone.pth, map_locationcuda) model.load_state_dict(pretrained_dict, strictFalse) model.fc nn.Linear(2048, num_classes) # num_classes 缺陷类别数 1类正常 criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) # 训练循环略建议微调 50-100 个epoch并用K-Fold交叉验证微调阶段有3个参数值得注意学习率预训练权重的学习率要比随机初始化小一个量级我习惯用1e-4起步按CosineAnnealing衰减weight_decay小样本场景下正则化很重要1e-4的weight_decay是默认值如果出现过拟合可以适度加大Batch Size因为总样本量不大batch size建议设小一点16或者32太大容易在局部震荡。3.2 loss函数与类别不平衡处理小样本缺陷检测另一个常见问题是类别不平衡。极端情况下正常样本几万张缺陷样本只有两三百张缺陷里还分了好几个子类有的子类可能只有三四十张。直接拿CrossEntropy去训模型会无脑倾向多数类。我建议的组合方案是使用Focal Loss替代普通CrossEntropyFocal Loss通过调制因子让模型关注难分类的少样本类别对于极度不平衡的子类用Class-Balanced Loss或者给每个类设置权重权重跟该类样本数量的平方根成反比对于需要定位缺陷的任务还可以在分类loss之外叠加一个基于特征距离的度量学习loss拉近同类特征、推远异类特征。这里给出Focal Loss的一个简洁实现可以直接嵌入训练代码class FocalLoss(nn.Module): def __init__(self, gamma2.0, alpha0.25, num_classes5): super().__init__() self.gamma gamma self.alpha alpha self.ce nn.CrossEntropyLoss(weightNone) def forward(self, logits, targets): ce_loss self.ce(logits, targets) pt torch.exp(-ce_loss) focal_loss (1 - pt) ** self.gamma * ce_loss return focal_lossgamma2.0会让模型更关注那些预测概率低的难样本。alpha是类别权重参数如果是多分类建议按类别样本数的倒数归一化后传入CrossEntropy的weight参数效果会更好。在验证和调参阶段我强烈建议不要只看准确率要同时关注每类的Precision、Recall和F1。尤其是在漏检控制方面Recall的意义远大于Accuracy。如果你发现某个缺陷类别的Recall一直上不去先不要急着调模型结构可以先看这一类样本的特征分布是否太少或者增广策略是不是有问题。3.3 阈值选择与置信度校准模型输出的是每个类别的概率分布但部署时给不给过、给不给判废完全取决于阈值怎么设。很多新手工程师会把0.5作为默认阈值这在工业场景是行不通的——如果某项缺陷的漏检率要求是控制在0.1%以内0.5的阈值通常太激进模型稍微犹豫一下就把缺陷漏过去了。我的做法是在训练完成后单独跑一遍验证集统计每个类别在不同置信度阈值下的Recall和Precision曲线然后根据客户对漏检和误检容忍度的具体要求去选阈值。这个步骤通俗讲叫“置信度校准后的阈值搜索”。举个具体的例子假设客户要求“缺陷检出率不低于99.9%误检率不做硬性要求”那你的阈值就要往低里调一般调到0.2甚至0.1把大包围打出来宁可多报也不能漏。如果客户同时要求误检率不能超过某个值那就要做一个阈值搜索的最优化遍历0.05到0.95之间的每个值计算每个阈值下Recall和Precision的变化找到两者trade-off最平滑的那个点。这个过程我习惯写成脚本每次模型更新后自动跑一遍输出一张阈值-指标曲线表。3.4 验证策略别被单次划分骗了小样本场景下模型的验证结果方差极大。同一个模型换一组验证集准确率可能从95%掉到80%。所以我强烈建议用小样本专用的验证策略——K-Fold交叉验证是必须的。K5的时候每次拿80%的缺陷样本训练、20%验证五次平均后的指标才有参考价值。还有一个来自实际项目的细节为了模拟真实产线场景我在划分数据时会把“同一产品同一批次的样本尽量放进同一个fold”防止模型利用批次背景信息做“作弊式”分类。这个坑很隐蔽如果不去控制交叉验证结果会虚高上线后立刻现原形。4. 漏检率控制与问题排查思路4.1 为什么漏检比误检更难处理在工业检测场景里漏检和误检的代价完全不对等。一次漏检可能导致整批次产品流向客户端后面就是客诉甚至召回代价可能是百万级的。误检只是让产线多停机检查几秒钟代价相对可控。所以工程上几乎所有客户都会优先压漏检率。但从算法角度看漏检的优化空间比误检小得多。误检可以通过后处理规则轻松压掉比如限定缺陷的最小面积、最小对比度、与周围纹理亮度差的阈值等。而漏检意味着模型压根没“看懂”某个缺陷这时候你要么提供更多样化的缺陷样本去扩分布要么降低阈值把模型预测的模糊地带全部暴露出来再配合规则过滤把误检收回来。这个思路总结起来就是一句话“先放开再收敛”。先把阈值压低让模型尽可能多地输出候选缺陷然后靠规则和人工确认来收敛这是工业界最稳的漏检控制路线。4.2 多层过滤规则后处理控制误检模型输出后经过一层业务规则过滤能显著降低误检率。我总结了一套常用的规则优先级按从简单到复杂排列面积过滤缺陷连通域面积小于某一个阈值的直接忽略位置过滤某些产品边缘区域的缺陷判定标准可以放宽或者反过来收紧对比度/梯度过滤缺陷区域与背景的灰度差、梯度强度不够的忽略形状过滤按连通域的长宽比、凸度、圆形度做判定滤掉一些“伪缺陷点”多模型投票过滤训练两个不同结构的模型比如一个CNN、一个Transformer两个模型都判为缺陷才给出缺陷告警。这个方案能显著降低偶然性误检但会轻微牺牲召回适合误检要求极高的场景。后处理逻辑需要在产线试运行阶段持续调参不能一梭子打完就撒手。我通常安排两周的试运行期前一周每天和质检员对一遍误检结果把误检案例攒下来逐条分析是阈值问题、规则问题还是模型问题。这套机制看着笨但它是真正能把漏检率打下去的土办法比任何复杂的算法理论都管用。4.3 常见问题速查与实战排障表跑小样本缺陷检测项目遇到最多的问题其实就那几类。我整理了一个速查表你在排障的时候可以按图索骥症状可能原因排查顺序与解法训练loss不下降学习率过大或过小、数据归一化错误先检查数据预处理再调整学习率尝试warm-up训练集准确率很高验证集很差过拟合常见于小样本增加数据增广、加大weight_decay、引入Dropout某类缺陷召回率始终上不去该类样本形态方差太大或者样本量太少重点增强该类别的增广粘贴复制缺陷融合误检集中在某一批正常图正常样本的纹理特征和缺陷样本太接近收集误检图加入训练集作为硬负样本重新微调线上效果与验证集差异大图像采集环境不一致光源、角度、分辨率验证集加入跨环境样本图像预处理统一规范化推理速度太慢模型太大或后处理太重换轻量级backbone改TensorRT半精度推理第二行说的过拟合我再展开一句。小样本训练时过拟合是最常见的问题。除了增广和正则化还有一个我经常用的办法早停。在验证集上跑几个epoch后如果loss不再下降或者开始回升果断把训练停掉不要让它继续磨。多磨几轮看着训练精度在涨其实模型已经退化到只会背训练集了。4.4 上线前的压测与回归项目上线不是训练结束就完事上线前必须做一轮压测和回归。压测主要测两点一是推理耗时看单张图的检测时间能不能跟上产线节拍二是稳定性用一段长时间采集的产线真实数据去跑观察模型是否有偶发性的输出异常。我把压测标准定为三个维度单张推理耗时100ms视具体硬件而定连续跑24小时单卡显存占用稳定无内存泄漏漏检率、误检率在客户要求的指标范围内波动且不能出现“时好时坏”的抖动。回归测试指的是每次更新模型或调参后把历史积累的验证集全部重跑一遍对比新旧版本在每个类别上的指标变化。这样能防止为了修一个bug把另一个bug引进来。回归测试的数据集是项目执行过程中需要“滚雪球”一样积累的。凡是线上出现过误检、漏检的案例图确认后全部归档进回归库。这个库越厚模型迭代就越稳后面每一个新版本上线前心里都有底。多提一句我遇到很多项目在试运行阶段发现漏检率“跑到后面又升回来了”这类问题大多不是模型退化而是产线工艺变化了——换了材料批次、调了注塑参数、设备磨损导致纹理变了。针对这种情况光调模型没用要让现场工程师同步关注工艺参数变化必要的时候把新工况下的正常样本也拉回来做增量更新。模型上线不是一次性的交付而是和产线“共同演进”的过程。从项目里带走的经验小样本缺陷检测这个方向说到底比的不是谁的模型结构花哨而是谁在小数据条件下更懂取舍、更会在工程链路里找漏洞。数据增广、预训练策略、阈值控制、多层过滤每一环都是系统工程的一部分。我个人体会最深的一点是把验证体系建好比训练出一个漂亮模型更重要。当你的验证集、回归库、指标采集机制足够扎实每个版本迭代的收益会被严格量化项目就不会陷入“感觉变好了、又感觉变差了”的泥潭。最后分享一个带过很多项目的工程师都认可的土办法把所有漏检案例打印出来贴在工位上每天看一遍逼自己去想为什么模型没抓到。这个过程虽然原始但比翻论文有用得多。缺陷检测这个行业真正的干货从来都在现场不在纸上。