ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

图像分类在工业缺陷检测中的实战:数据、训练与调参全攻略

图像分类在工业缺陷检测中的实战:数据、训练与调参全攻略 简介面向工业视觉质检场景的入门实践项目利用图像分类算法自动识别产品表面的划痕、裂纹、异物、颜色偏差等缺陷适合具备一定编程基础、希望将深度学习落地到产线质检的工程师和学生。资源以视频讲解加可运行代码的方式完整演示了从项目框架梳理、数据集构建到模型训练与预测的六个阶段帮助初学者建立整体认知。压缩包共1823个文件以bmp样本图像为主约1800张另有6段mp4操作视频、py训练与预测脚本、xml标注文件及pth模型权重等逻辑划分清晰便于按步骤对照学习整体大小约346.61MB。目前已有86人学习下载内容涵盖自定义数据集制作、CNN模型定义、训练参数调整、加载模型预测以及准确率与召回率评估等关键环节既能快速跑通现有案例也能参考代码改造适配自身工业检测需求。1. 基于图像分类的工业缺陷检测值得花一个周末复现的项目工业视觉的落地项目里新手最容易踩的第一个认知误区是认为缺陷检测必须上目标检测或者像素分割。实际带过产线的人会先问一句话产品有没有缺陷需不需要知道缺陷在哪很多质检场景打光固定、相机角度固定、背景单一过检只需要判“有”或“无”这时基于图像分类的工业缺陷检测恰恰是性价比最高的方案——数据标注成本低、模型训练快、推理延迟小产线换型也能快速重训。这个项目标题里的“视频讲解代码”指向的正是这样一条闭环从原始图片到可部署的模型权重每一步都能跟着做。适合手里有缺陷图库、想快速验证深度学习能不能压住误检率的工艺、视觉和软件工程师也适合准备转行工业视觉的算法岗新人用它当作第一个全流程基线。2. 为什么工业缺陷检测要选图像分类路线对比与适用边界2.1 三条技术路线的真实分界线先看需求文档再看模型工业缺陷检测常用的深度学习路线有三条图像分类、目标检测、语义分割。选型不是越高级越好而是看需求文档里的判定标准。图像分类输出的是“这张图有没有缺陷”的类别标签适合判断合格与不合格、或者把缺陷归为大类划伤、脏污、缺料目标检测输出缺陷的矩形框位置适合后续机械臂抓取或定位修复语义分割输出像素级掩码适合需要精确测量缺陷面积、长宽的场景比如电池极片露箔的面积占比。三个路线之间没有绝对的高低之分只有成本差异。在产线上分类模型的标注成本最低——给整张图打一个标签就行不需要画框也不需要描边。而检测或分割方案对标注质量的要求立刻上了一个台阶框偏了、边描歪了模型学出来的位置就不准。很多项目卡在数据环节不是没有算法而是标注返工了三轮。我一般会把需求先做减法如果质检员在现有工位上用肉眼判断只需要结论那就直接走分类。反过来如果后续工序要根据缺陷坐标做自动剔除分类模型的确不够这时再去考虑目标检测。这个取舍决定了整个项目的数据成本、工期和算法复杂度值得在开工前和业务方反复确认。2.2 数据规模与标注成本工业现场反而逼你选分类工业缺陷检测项目有个共性缺陷样本永远是稀缺的良品样本成堆。一个典型的场景是注塑件表面检测良品图能拍几千张但真正有代表性缺陷的可能只有几十张。这种情况下做目标检测或分割缺的不是算法是带标注的缺陷样本——每一张样本都要人工圈出缺陷位置几十张勉强训个检测模型泛化能力非常差。图像分类对样本量的容忍度高得多。每个缺陷类别只要有几十张图配合数据增强和预训练权重就能训出一个可用的初版模型。而且在产线快速换型时分类模型的迭代速度占绝对优势新增一种缺陷类型只需要把新类别的图片放进目录、重跑训练几十分钟内完成。这不是说分类模型不需要数据质量。需要警惕的是类别标签的噪声——同类缺陷的形态差异、光照差异都会影响训练。但从项目交付的角度看分类方案能在有限样本条件下先跑通流程、验证可行性这是它作为工业视觉入门项目首选路线的根本原因。2.3 分类模型在产线上的两个正确定位初筛与门禁图像分类模型在产线落地最务实的方式是做两级质检的第一级。高速相机拍下每一件产品分类模型先做粗筛把明显不良品和疑似品分流出来。疑似品再进高分辨率相机或者人工复判。这样分类模型的定位是“用低成本卡住大多数缺陷”而不是指望它在一张图上把所有缺陷都精确定位。另一个常见定位是门禁式质检只有分类模型判定为良品的工件才允许流入下一道工序。这种场景下阈值调优比模型结构调优更重要——工业现场最怕的不是把良品误杀成本多一次复检而是把缺陷品放过去变成客诉。这直接导向第 4 章要讲的置信度阈值问题。所以选择图像分类路线本质上选择了一个更简单的模型任务、更低的标注成本同时接受它“不给位置信息”的局限。这个定位想清楚了后面的代码、参数、坑就有了解释的上下文。3. 用 PyTorch 搭出最小可跑基线数据、训练、推理三段闭环3.1 数据目录组织与切分一个脚本把原始图变成训练集工业缺陷检测项目拿到手的原始素材往往是散落在多个文件夹里的图片命名规则混乱良品和缺陷混在一起甚至同一个缺陷类别有多个来源。第一步永远是把数据目录踩平、统一格式并保证切分时同一条生产批次的数据不会同时落在训练集和验证集。以下是常见做法的目录结构data/ raw/ good/ # 良品图片 scratch/ # 划伤缺陷图片 stain/ # 脏污缺陷图片 train/ good/ scratch/ stain/ val/ good/ scratch/ stain/ test/ good/ scratch/ stain/切分脚本里我一般会用按文件哈希去重、按批次编号分组再切分避免同一工件的连续多张图同时进入训练集和验证集。下面是关键脚本import os import random import shutil from collections import defaultdict random.seed(42) def split_dataset(src_root, dst_root, train_ratio0.7, val_ratio0.15): for class_name in os.listdir(src_root): class_dir os.path.join(src_root, class_name) if not os.path.isdir(class_dir): continue images [f for f in os.listdir(class_dir) if f.lower().endswith((.jpg, .png))] # 按文件名前缀的批次号分组防止同批次进训练集和验证集 batches defaultdict(list) # 文件名形如 batch_20250113_0001.jpg for img in images: batch_key img.split(_)[0] _ img.split(_)[1] batches[batch_key].append(img) batch_keys list(batches.keys()) random.shuffle(batch_keys) n_train int(len(batch_keys) * train_ratio) n_val int(len(batch_keys) * val_ratio) for idx, batch_key in enumerate(batch_keys): dest train if idx n_train else val if idx n_train n_val else test out_dir os.path.join(dst_root, dest, class_name) os.makedirs(out_dir, exist_okTrue) for img in batches[batch_key]: shutil.copy(os.path.join(class_dir, img), os.path.join(out_dir, img)) split_dataset(data/raw, data/processed)这段脚本做了一个很多入门项目不会做的动作按批次分组再做切分。原因是工业相机连拍时同一个工件的多张图背景几乎一样如果让它们同时出现在训练集和验证集验证集准确率会被严重高估后面的避坑章节会展开说这个问题。参数的基准值是 7:1.5:1.5缺陷样本总量不足 200 张时建议把比例调整为 7:3test 直接从 val 里再切保证训练样本尽量多。3.2 预处理与增强工业图与自然图差异最大的四个参数工业场景的图像和 ImageNet 自然图像有本质区别背景单一、目标位置固定、光照相对可控。因此预处理不能照搬自然图像分类的套路四个参数需要单独调输入分辨率、归一化均值方差、增强强度、色彩扰动范围。输入分辨率方面工业图一般不建议直接用 224×224。如果原始图已经有较大视场缺陷只占十几个像素强行缩到 224 会把缺陷细节抹掉。我一般会在项目中先用 512×512 起步显存不够再降到 384。归一化参数用 ImageNet 的均值[0.485, 0.456, 0.406]和方差[0.229, 0.224, 0.225]可以但更稳妥的做法是拿项目数据算一套自己的均值方差工业图颜色分布和自然图差很远。增强策略上不要用 ImageNet 风格的重度增强比如随机旋转 90 度、大面积 cutout——工业缺陷的形态和方向有物理约束划伤永远是长条状随机旋转会制造出产线上根本不存在的缺陷形态。推荐的增强组合是from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((512, 512)), transforms.RandomCrop(480, pad_if_neededTrue, fill0), # 模拟视场偏移 transforms.RandomHorizontalFlip(p0.3), transforms.ColorJitter(brightness0.1, contrast0.05, saturation0.05, hue0), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((512, 512)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])参数说明RandomCrop(480)比直接Resize多了一个视场轻微偏移的模拟工业相机安装后会有微小振动这个增强能提升模型对拍摄位置波动的容忍度。ColorJitter里hue0是刻意为之——工业光源下色调偏移不该发生动了色相只会让模型学到错误关联。亮度扰动只给了 0.1因为产线打光波动通常在小范围太大反而压制模型对缺陷对比度的敏感度。3.3 训练脚本从 ResNet 起步跑出第一个能用的权重模型选择上工业缺陷检测项目起步不建议直接上最新的图像分类模型也不建议一上来就用 Transformer 结构。先用 ResNet18 或 ResNet34 做基线理由有两个一是预训练权重好找通用特征对表面缺陷有迁移价值二是推理端部署简单ONNX 导出和量化都没什么坑。等基线跑通、确认数据没问题再考虑换更深的模型或引入 Transformer 结构做精度提升。下面是训练脚本核心部分import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, models from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) num_classes 3 # good / scratch / stain model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) model.to(device) train_dataset datasets.ImageFolder(data/processed/train, transformtrain_transform) val_dataset datasets.ImageFolder(data/processed/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size16, shuffleFalse, num_workers4, pin_memoryTrue) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) for epoch in range(30): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() scheduler.step() print(fEpoch {epoch1}/30, Loss: {running_loss/len(train_loader):.4f})参数说明batch_size16对应 512 分辨率下的常见显存配置如果你用 8GB 显存的卡batch_size 降到 8 并把num_workers调到 2 更稳。lr1e-4是迁移学习场景的安全起点因为骨干网络加载了预训练权重学习率太大容易把已经学好的通用特征冲掉。CosineAnnealingLR配合 30 个 epoch 足够完成从粗调到精调的过渡如果发现 Loss 在 20 个 epoch 后还在明显下降可以把T_max改成 40而不是盲目加大学习率。3.4 推理与质检报告把模型输出变成产线能看的表格训练完的模型要输出质检报告才具备实用价值。推理脚本需要两个输出每张图的类别预测和置信度分数以及按产线批次汇总的通过率统计。置信度分数就是第 4 章要调阈值的基础数据。import torch from torchvision import datasets, models, transforms from torch.utils.data import DataLoader import csv device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, 3) model.load_state_dict(torch.load(best_model.pth, map_locationdevice)) model.to(device) model.eval() test_dataset datasets.ImageFolder(data/processed/test, transformval_transform) test_loader DataLoader(test_dataset, batch_size16, shuffleFalse, num_workers4) class_names [good, scratch, stain] threshold 0.8 # 良品类别的置信度阈值低于这个值判为可疑 results [] with torch.no_grad(): for images, labels in test_loader: images images.to(device) probs torch.softmax(model(images), dim1) conf, preds torch.max(probs, dim1) for i in range(len(labels)): is_reliable conf[i].item() threshold results.append({ image: test_dataset.samples[0], # 实际代码用索引映射文件名 pred: class_names[preds[i].item()], confidence: round(conf[i].item(), 4), reliable: is_reliable }) with open(inspection_report.csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[image, pred, confidence, reliable]) writer.writeheader() writer.writerows(results)这个推理脚本的关键在于把reliable位单独拉出来。产线上真正要看的不是模型把每张图分成了哪类而是有哪些图落在阈值以下需要人工复判。逻辑说明softmax输出的概率不是严格的概率校准结果但对分类决策足够用threshold0.8是起步值具体调法在第 4 章展开。文件名映射在真实项目里需要从dataset.samples里对应索引取上面的代码为控制篇幅做了简化。4. 四个必调参数与收敛判断准确率从 80% 拉到 95% 的路径4.1 学习率与 batch size调参顺序比数值本身更重要很多人拿到代码第一件事是到处问“学习率设多少”这问错了。正确顺序是先定 batch size再反推学习率。batch size 由显存和图像分辨率共同决定512 分辨率、ResNet18、8GB 显存batch size 上限通常是 16如果输入分辨率提到 640batch size 就得降到 8。batch size 变化时学习率应该按线性缩放法则同步调整batch size 翻倍则学习率翻倍否则梯度估计的噪声水平变了模型收敛轨迹完全不同。工业场景下还有一个容易忽略的约束同一批次里不同类别的样本比例。如果缺陷类样本少每个 batch 里缺陷样本的占比会剧烈波动。缓解办法是在 DataLoader 里用WeightedRandomSampler按类别权重采样。这比单纯调学习率更直接因为它解决的是梯度方向被良品样本主导的问题。调参顺序建议是先确保每个 batch 里都有所有类别的样本再调学习率和 batch size。4.2 类别不均衡的三种解法过采样、加权损失、阈值迁移工业缺陷数据集天然不均衡良品可能占 90%划伤占 6%脏污占 4%。如果不做处理模型会把所有图都判成良品因为这样也能拿到 90% 准确率。三种常见解法可以叠加使用。过采样最简单——对缺陷类图片做增强生成多份变体让训练集类别比例接近 1:1:1。注意过采样生成的是增强后的变体而不是原图硬复制否则模型会过拟合到固定几张图上。加权损失更精细把CrossEntropyLoss的weight参数设置为各类别样本数的倒数这样可以避免扩增数据带来的额外存储。阈值迁移则是训练完模型后在推理端做的操作——不改变模型只调判决边界。import torch.nn as nn class_counts torch.tensor([900, 60, 40], dtypetorch.float32) # good / scratch / stain weights 1.0 / class_counts weights weights / weights.sum() * len(class_counts) # 归一化到类别数 criterion nn.CrossEntropyLoss(weightweights.to(device))这段计算逻辑说明weights归一化后每个类的加权系数乘上当前 batch 的 loss使得样本量少的类别获得更大梯度贡献。需要说明的是加权损失在 epochs 较大时可能让模型对少数类过拟合所以它的生效前提是配合早停机制。三个方法叠加使用时的经验顺序是先加权损失 早停再看验证集每个类别的召回率最后用阈值迁移补齐最后几个百分点的漏检。4.3 early stopping 与检查点选择别把最后一个 epoch 当最优训练脚本里最容易犯的错是把最后一个 epoch 的权重当作最终模型。工业数据显示训练 Loss 在下降、验证准确率却在波动是很常见的——模型在训练集上越来越熟但对光照波动和微小形变开始过度敏感。正确做法是每个 epoch 结束后在验证集上算准确率和每个类别的召回率并保存验证集指标最好的那个权重。best_f1 0.0 patience 5 no_improve 0 for epoch in range(30): # 训练代码略 val_loss, val_f1 evaluate(model, val_loader) if val_f1 best_f1: torch.save(model.state_dict(), best_model.pth) best_f1 val_f1 no_improve 0 else: no_improve 1 if no_improve patience: print(fEarly stop at epoch {epoch1}) break参数说明patience5在 30 个 epoch 的配置下给了模型足够的缓冲不会因为一次验证集波动就停止。保存模型的评估指标选用 F1 而不是准确率是因为缺陷类别样本少准确率会被良品主导。这里建议把best_model.pth单独保存别覆盖到训练循环里用的权重文件上避免中途调试时把最优模型搞丢。4.4 置信度阈值漏检率与误检率的唯一旋钮模型训练完成后阈值调优是投入产出比最高的环节。产线需求的本质不是“准确率 98%”而是“漏检率低于 0.5%、误检率低于 3%”这样的具体数字。这两者在分类模型里是同一个旋钮的两端——把阈值调高更多低置信度的预测被判为可疑漏检率下降但需要人工复检的良品数量上升把阈值调低误检率下降但漏检风险上升。操作上我一般会跑一次完整的验证集预测把每张图的置信度保存下来然后画一条阈值-误检率/漏检率的曲线。选阈值时先满足漏检率上限再尽量压低误检率。工业客户对漏检比对误检敏感得多所以阈值通常往保守方向调宁可不达标率 5%也不能把缺陷品放出去。这个价值观要在一开始就跟业务方确认清楚否则后面拉锯无穷无尽。5. 五个血泪坑从数据集污染到部署差异新手最容易翻车的点5.1 按图切分导致指标虚高验证集和训练集“同源”现象训练时验证集准确率 99%一上产线实测直接掉到 85%怎么查都找不到原因。原因切分时按“图”而不是按“工件”或“批次”切分。工业相机连拍同一工件时相邻几张图的背景、光照几乎一致如果其中一张进了训练集、另一张进了验证集模型本质上记住了背景而不是缺陷。上产线后换了角度和光强立刻失效。解决回到第 3 章的切分脚本按文件名前缀或时间戳把同一批次的图归组组级别切分。另一个补充操作是把同一批次的图片做哈希去重避免显式重复数据污染。5.2 模型学到的是光源不是缺陷打光波动的隐蔽后果现象训练集里所有缺陷图都来自左侧光源模型在产线右侧光源环境下把阴影识别成缺陷误检率飙升。原因数据采集时没有控制光源方向的一致性。工业现场的相机和光源安装角度基本固定但不同批次的数据可能是不同工位甚至不同时间采集的光照方向、光源色温都不同。模型找捷径直接学“左侧有暗影就是缺陷”而不是学“纹理断痕才是缺陷”。解决数据采集阶段记录每批图的拍摄工位和光源参数训练前按光源方向分组确保训练集和验证集都覆盖多个光源方向。如果做不到就在增强里加入亮度扰动和对比度扰动压缩模型对光源特征的依赖。5.3 缺陷类别标注不干净Loss 震荡的隐藏原因现象训练 Loss 曲线一直抖验证准确率卡在某个值上不去调学习率和 batch size 都没用。原因缺陷样本标注时不同质检员对“轻微划伤算不算缺陷”的判断不一致。同一张图在数据里被标成 scratch 和 good 各一次模型对这两个类别的特征边界完全混乱。解决最直接的办法是找人复核标注把所有边界案例集中起来重新打标。如果复核成本太高退而求其次可以把争议样本单独建一个“弱缺陷”类别虽然分得粗但至少让模型不用把矛盾样本强行塞进两个互斥类别里。从项目管理的角度看这是数据质量成本带来的取舍算法层面没有后悔药。5.4 正负样本极端失衡准确率 95% 但你今天什么都没干现象模型打印出的验证集准确率 95%你以为已经达标了。但看类别召回率缺陷类的召回率只有 30%所有缺陷几乎都漏掉了。原因准确率是全局指标90% 良品 10% 缺陷的数据分布下全预测为良品就稳拿 90%。没有看混淆矩阵之前准确率完全会骗人。解决训练过程里每轮打印每个类别的召回率和精确率而不是只打印总体准确率。验收标准里必须写清楚每个缺陷类别的召回率下限。这是整个项目里最值得盯的一次输出。5.5 部署端预处理不一致训练时 512×512部署端 416×416现象模型导出 ONNX 后在产线工控机上跑准确率比验证集低五六个百分点反复查代码找不到问题。原因部署脚本里用的预处理和训练脚本不一致。最常见的是分辨率不一致训练用 512 但部署端为了帧率改成了 416其次是归一化参数不一致、Resize 的插值方式不一致。这些在单测时看不出来产线连续跑几小时问题才暴露。解决把预处理封装成一个独立函数训练脚本和推理脚本共用同一个文件在部署代码里写一条断言检查输入分辨率和归一化参数是否符合配置表。有条件的话直接用训练时的验证集图片跑一遍部署端的推理脚本对比两边的预测置信度差异超过 0.01 就说明预处理有偏差。6. 从实验台搬到产线的三个阶段把分类模型用稳的习惯6.1 先做粗筛不要试图一步到位我每次都会建议现场先用分类模型做两级质检的第一级后续再接人工复检或更高精度的专用模型。因为分类模型在工业现场的定位是“把明显缺陷和绝对良品分开”而把模棱两可的边界样本全部推到阈值线以下。这样做的好处是模型的压力小即使精度波动也不会直接影响出货产线对这个系统的信任度会慢慢建立起来。6.2 每天盯特征分布的漂移模型上线后我习惯每天保存产线实测图片的嵌入特征和训练集特征分布做一个简单的距离对比。常用的做法是取模型的倒数第二层输出做一个降维散点图。如果发现新的缺陷形态导致特征分布偏移明显就说明模型需要增量训练了而不是等到准确率报表出问题再被动响应。这个监控习惯的成本很低但能避开最尴尬的场面——客户先发现问题。6.3 把验收指标写进交付单最后一个阶段是验收。我不建议只用整体准确率验收至少要列每个缺陷类别的召回率、良品的误检率、阈值线下的人工复检率。这三个数值同时达标模型才算真正具备上产线的资格。另外要把阈值、预处理参数、模型版本一起写进交付单方便以后排查。这个项目方向值得投入因为它把工业视觉里最常用、落地路径最短的一个方案完整走通了。视频讲解和代码帮你省掉的是那些没人告诉你、只能在产线上熬出来的细节。从分数据开始到部署端预处理一致每一步都按这套思路来基本不会翻大车。最后希望本文能帮你在自己的缺陷检测项目里少踩几个坑顺利把第一个模型送上线。本文还有配套的精品资源点击获取
返回列表