首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Anomalib自定义数据集构建指南:工业异常检测从采集到训练全流程
📅 2026/9/19 8:12:44
✍️ 爱科研究院
👁 阅读 3,247
做异常检测这几年我越来越觉得真正决定模型上限的往往不是模型本身而是数据集。Anomalib 是 Intel 开源的一套基于 PyTorch 的异常检测工具库里面集成了 PatchCore、EfficientAD、STFPM、FastFlow 这些主流工业异常检测算法官方 repo 的 demo 跑起来很容易但很多朋友试用后反馈官方示例在公开数据集上效果不错一换到自己的产品数据就完全不对劲。原因大多不是模型选错了而是自定义数据集没有按规范构建。这篇文章我就以 Anomalib 为核心完整走一遍“从零开始构建自定义异常检测数据集”的流程。内容覆盖数据采集策略、目录结构组织、像素级 mask 标注、训练配置、常见坑点排查。不管你是刚接触工业视觉的新手还是已经在用 OpenCV 做规则判断、想切到深度学习方案的开发者这篇都能给你一套可以直接照做的落地方案。1. 先搞清楚Anomalib 到底在解决什么问题1.1 它和普通分类任务的本质区别大多数计算机视觉任务比如目标检测、语义分割核心思路是“教会模型认识每一类东西”。异常检测不一样它的目标是“教会模型识别没见过的状况”。在工业场景里缺陷种类往往是无限的划痕可以有几十种角度、深度、长度脏污、凹陷、毛刺也千变万化。你不可能把所有缺陷都标注一遍这既不现实也没必要。Anomalib 的思路非常聪明它只让模型见“正常样本”然后通过重构误差、特征距离、密度估计等手段把“偏离正常分布”的样本判为异常。这解决了工业落地中的两个核心痛点正常样本容易收集异常样本难得尤其产线初期良率很高时缺陷样本屈指可数。异常类型无法穷举传统分类模型面对没见过的缺陷形态很容易误判而异常检测模型天然对“没见过的东西”敏感。所以我平时和别人聊都会先确认一件事你手里是“有大量缺陷样本、需要给缺陷分类”还是“缺陷样本少、想判断产品是否异常”。前者该去做分类后者才适合用 Anomalib。1.2 一个典型的落地场景假设你在做手机中框外观检测。产线上每天产出几千片其中绝大多数是良品偶尔出现划伤、压伤、氧化色差。你不可能让每片都过人工目检于是想用工业相机抓拍后交给算法判断。这时候 Anomalib 的用法就很清晰用几百张正常中框图像训练模型记住“正常纹理和轮廓长什么样”。上线后每抓拍一张新图模型计算它和正常分布的偏离程度超过阈值就报警。只有报警样本才需要人工复判甚至可以做二次分类。这样即使你没积累几天甚至几个月的缺陷图也能先把“异常报警”这件事跑起来。这也是 Anomalib 这类算法在工业现场最受欢迎的原因模型不需要见过缺陷就能把缺陷找出来。2. 构建自定义数据集之前先定好训练策略很多人一上来就拍脑袋拍照、标注结果数据收集了一周训练时才发现策略不对。这里我建议你先回答三个问题再动手。2.1 你要做图像级检测还是像素级检测图像级异常检测模型只输出“正常/异常”一个结论告诉你这片产品有没有问题。像素级异常检测也叫分割级模型还会额外输出一张热力图标出哪个区域异常。这个选择直接决定你要不要做 mask 标注工作量差别很大。如果只是产线分拣图像级已经够用。但如果你希望算法帮 QC 定位缺陷位置、辅助复判或者后续还要接机械臂打磨、剔除那就必须做像素级。我建议起步阶段先做图像级跑通流程确认模型能稳定区分异常后再补标注做像素级不然项目周期会被标注拖死。2.2 正常样本多还是异常样本多Anomalib 为代表的单类模型one-class训练时只用正常样本异常样本全部留到测试集。这个特性带来一个好处你可以把大量未标注的产线图像当作“疑似正常”数据直接参与训练前提是你要确保这些图像里没有重大缺陷。这里有个分寸要拿捏。如果训练集里混入外观明显的异常图像模型就会把异常也当成正常上线后漏报率会很高。我的做法是第一批训练先用人工严格确认的纯净正常图等模型能跑了再从中高置信度判为正常的产线数据里筛一部分增量训练这样风险小很多。2.3 你打算在什么硬件上推理Anomalib 各算法对算力的要求差别很大。PatchCore 效果好但把训练集所有正常特征都存进 memory bank显存和内存占用不小边缘设备部署时要小心。EfficientAD 是专门为工业场景设计的轻量模型推理速度快普通工控机也能跑。STFPM 更轻但几种算法各自适合不同缺陷类型。我不建议在项目初期纠结最优算法先用默认配置跑通 PatchCore 拿到一个基线结果然后再尝试 EfficientAD、STFPM 做对比。数据集如果构建得干净算法之间差异远比你想象的少真正决定天花板的是数据质量和标注一致性。3. 从零动手数据采集、整理与目录搭建3.1 拍照环境一致性比数量更重要很多人在实验室里随便拍了几十张就开始训练结果模型把“背景反光”当成正常特征一到产线就崩。工业异常检测对图像采集的一致性要求极高哪怕你后续用再强的增强也没用我踩过最大的坑就是光照不一致。拍照时要注意的几件事固定相机位姿、固定产品摆放角度尽量让每张图的背景保持一致。光源要稳定最好用低角度环形光或同轴光避免环境光干扰。同一个工位拍照不要在上午拍一批、下午换了个窗户位置再拍另一批。产品自身允许有轻微旋转或位移但不要太大否则模型会学“位置变化”而不是“纹理正常”。我曾经试过用网上爬来的同类产品图片补充数据集结果训练集只有 80 张但包含五六种不同背景模型评估时 image-level AUC 只有 0.75后来重新统一光源拍摄同样 80 张AUC 直接到了 0.96。背景、光照的变化对模型影响之巨大远超很多人的直觉。3.2 一个 MVTec 风格的自定义数据目录Anomalib 数据加载器对目录结构有约定。虽然你可以写自定义 Dataset 类但项目初期最简单的方式是直接套用官方的 MVTec 目录格式这样配置文件几乎不用改代码这也是最快能跑通的方式。推荐的目录结构如下任意根目录/ └── product_a/ ├── train/ │ └── good/ │ ├── normal_001.png │ ├── normal_002.png │ └── ... ├── test/ │ ├── good/ │ │ ├── good_001.png │ │ └── ... │ └── scratch/ │ ├── scratch_001.png │ └── ... └── ground_truth/ └── scratch/ ├── scratch_001_mask.png └── ...train 下只有 good 文件夹这是 Anomalib 约定俗成的“正常样本专用目录”。所有异常样本按缺陷类型分别放在 test 下的不同子文件夹中比如 scratch、dent、stain。如果你只做图像级检测ground_truth 目录可以不建做像素级检测时则需要在 ground_truth 下建立和 test 一一对应的子目录每个异常图配一张同名的 mask 图。这里要特别提醒不要在 train 目录里搞一个“anomaly”文件夹想混着训练Anomalib 单类算法不会读它即便有的教程让你这样放也只会让训练入口报错。3.3 各类型数据量该给多少正常样本越多越好但不同算法对数量的敏感度不一样。PatchCore 这类基于特征存储的算法训练样本太少会导致正常类别的特征覆盖不全测试时大量正常图被误判为异常也就是假阳性偏高。我的经验值是最少 100 张200 到 500 张是性价比最高的区间超过 1000 张边际收益就很有限了。异常样本则不需要太多。因为异常样本只参与测试和评估每种缺陷有 20 到 50 张就够计算召回率了。你更应该把精力花在收集不同类型的异常上而不是堆同一种缺陷的重复样本。很多项目里我反而是花了两三周攒齐缺陷图正常图一天就能拍完这个比例很正常。4. 像素级标注与 mask 数据制作4.1 mask 的原理与文件要求像素级异常检测需要 ground truth mask用来评估模型预测的像素级热力图准不准。mask 是一张和原图尺寸完全相同的单通道 PNG 图背景为纯黑像素值 0异常区域为纯白像素值 255。在 Anomalib 的评估流程里它会把模型输出的热力图和 mask 逐像素比对然后算 pixel-level AUROC。我见过不少人在这上面翻车。最常见的问题是用 JPEG 格式保存 mask导致白色区域边缘出现压缩伪影像素值不再是严格的 255变成 237、245、250 这类灰色值。虽然 Anomalib 会做二值化但边缘不干净会影响评估准确性所以记住一句话mask 一律用 PNG别用 JPEG。4.2 手工标注工具与操作流程标注工具有很多我平时用得比较多的是 LabelMe 和 CVAT。LabelMe 适合小规模数据单机操作简单CVAT 更适合团队协作但搭建要 Docker略微重。对于只有几百张图的项目我更推荐先用 LabelMe因为它能直接导出 JSON方便写脚本转成 mask。操作流程大概这样用 LabelMe 打开异常图沿着缺陷边缘画多边形标注。给每个多边形命名比如叫“defect”保存 JSON。写一个 Python 脚本读取 JSON 中的多边形点坐标在空白画布上画一个填充值为 255 的多边形生成同尺寸 PNG。这里有个很容易忽略的点LabelMe 默认保存的坐标是相对于原图的如果你的脚本里有 resize 步骤一定要先把坐标缩放再画 mask否则 mask 对不上图。我早期犯过这个错用 resize 后的图片配原图坐标的 mask模型训练没问题评估指标惨不忍睹。4.3 用脚本快速生成 mask 的示例如果你对代码不陌生下面这段脚本可以帮你快速把 LabelMe JSON 转成 mask。这里只列核心逻辑实际用的时候根据文件路径调整一下。import json import numpy as np import cv2 from pathlib import Path json_path Path(label/scratch_001.json) img_path Path(images/scratch_001.png) mask_path Path(masks/scratch_001_mask.png) # 读取原图尺寸严格按原图大小生成 mask img cv2.imread(str(img_path)) h, w img.shape[:2] mask np.zeros((h, w), dtypenp.uint8) with open(json_path, r, encodingutf-8) as f: data json.load(f) for shape in data[shapes]: # shape[points] 是多边形的顶点列表每个顶点是 [x, y] pts np.array(shape[points], dtypenp.int32).reshape(-1, 2) cv2.fillPoly(mask, [pts], 255) cv2.imwrite(str(mask_path), mask) print(fmask saved to {mask_path}, size{mask.shape})这个脚本适用于大多数常见标注工具导出的几何标注。如果你要标注的是边缘不规则的缺陷建议多边形点给密一点但也不要密密麻麻到上万点否则人工成本太高。实际项目里我一般建议每个缺陷用 20 到 200 个点包围出来足够精确即可。4.4 图像级检测可以跳过 mask前面提到如果你只是做“有无异常”的图像级判断完全不需要标注 masktest 目录里的异常图和正常图就够训练评估了。Anomalib 在配置里把任务类型设置成 classification 即可它会自动忽略 ground_truth 目录。但我要给个建议哪怕你现阶段不打算做像素级最好也在异常图像采集时顺手用画框软件把缺陷位置标一下。这样万一后续模型要升级做定位数据不用重拍只需补 mask。别问我为什么这么推因为我有很多客户都是模型跑通后又回头补标注这时候原来的图片要重新找出来整理非常痛苦。5. 配置文件与训练实战5.1 Anomalib 版本差异先对齐Anomalib 更新很快不同版本的训练命令和配置写法有差异。以目前主流的 1.x 版本为例训练入口通常是anomalib train命令行配置文件使用 YAML 格式指定模型、数据、训练器等参数。如果你还在用 0.x 老版本入口则是python tools/train.py配置字段也会略有不同。我建议直接装新版本旧版很多 API 已经不维护了。安装这块我就提一句涉及具体机器环境差异很大。常规做法是创建一个 Python 3.10 的虚拟环境然后pip install anomalib它会顺带把 PyTorch、lightning、openvino 等依赖装好。如果你要跑 ONNX 导出或 OpenVINO 推理再额外装对应组件。装完之后可以用anomalib install补全可选依赖具体以官方文档为准。5.2 一个可直接参考的训练配置示例下面这个 YAML 配置是我在自定义数据集上常用的骨架以 PatchCore 为例。注意这是示例具体字段要以你安装的 Anomalib 版本说明为准但核心思路是一致的。model: name: patchcore backbone: wide_resnet50_2 layers: - layer2 - layer3 coreset_sampling_ratio: 0.1 normalize: true dataset: name: my_product format: mvtec path: ./datasets/product_a category: product_a image_size: 256 train_batch_size: 16 eval_batch_size: 32 num_workers: 8 task: segmentation trainer: max_epochs: 1 accelerator: auto devices: 1 default_root_dir: ./results逐个解释一下关键配置的含义model.name指定算法PatchCore 是“无训练”的记忆型方法训练阶段只是提取并存储正常特征所以max_epochs设为 1 足够。backbone是特征提取网络wide_resnet50_2精度高但速度偏慢。如果数据简单、希望更快可以换成resnet18。coreset_sampling_ratio是核心采样比例0.1 表示从全部正常特征中抽出 10% 作为核心集用来控制内存占用。数据量大或显存不够时调成 0.05 或 0.02 可以明显减负。dataset.path指向数据集根目录category指向数据集根目录下的具体类别目录两者组合后加载器会自动寻找train/good、test/...等结构。task设成segmentation时会做像素级评估设成classification则只做图像级评估。如果你没准备 mask一定记得改这个字段否则评估阶段会因为找不到 ground truth 报错。5.3 开始训练与结果解读配置写好后执行训练命令anomalib train --config config.yaml训练过程中可以打开 TensorBoard 看 loss 曲线和样本可视化结果。PatchCore 本身没有梯度更新过程它是在跑特征提取和存储所以 loss 曲线参考意义不大重点看 validation 阶段的 anomaly map 可视化输出是否能把真实缺陷区域标亮。训练结束后Anomalib 会在./results目录按数据集、模型、时间戳生成结果文件夹里面有模型权重、指标文件、测试集的可视化图。我最关心的几个指标是image-level AUROC衡量“整张图有没有异常”的区分能力越接近 1 越好工业场景下 0.95 以上才比较安心。pixel-level AUROC衡量热力图定位能力一般会比 image-level 低一些0.9 左右已经很不错。F1-max用不同阈值算出来的最高 F1实际布署时会参考它来确定报警阈值。实测经验是如果 image-level AUROC 只有 0.8 左右先不要急着换模型先去看检测错误的样本大概率是正常样本形态变化太大或者光照不一致先把数据问题解决AUC 往往能涨一大截。5.4 快速验证异常发生的检测效果训练完之后最直观的验证方式是把一批新拍的产品图像丢给模型做推理。Anomalib 1.x 的推理命令很简单anomalib predict --model patchcore --weight ./results/patchcore/product_a/weights/model.pt --input ./inference_images模型会对每张输入图输出一个异常分数同时保存可视化热力图。你要做的是把异常分数阈值定下来用测试集里的正常图算出分数分布取一个能保证“正常品不报警”的阈值再拿异常图验证这个阈值下缺陷能召回多少。这个阈值就是以后产线报警用的可以在配置文件或后处理里固化下来。需要提醒一点很多模型在测试集上指标很好一到现场就开始乱报。原因往往是现场图像和训练集分布有偏移。所以我强烈建议训练完成后专门去现场采集一小批“没见过的正常图”做盲测如果假阳性过高就把这些图加入训练集做增量训练这个步骤比调参重要得多。6. 常见问题与排查技巧实录6.1 训练很快结束但评估分数很低很多第一次用 PatchCore 的人看到训练几十秒就结束会觉得不靠谱。其实这是正常的PatchCore 训练阶段只做特征提取不做反向传播几十秒到几分钟都算正常。评估分数低我看下来主要是两类原因正常样本太少了模型没看过足够多样的正常纹理导致任何一点正常波动都被当成异常。解决方法加到 200 张以上并确保包含产线正常状态下各种允许的波动。测试集正常图与训练集正常图分布不一致比如测试集里产品角度、光照发生了变化。解决方法严格统一采集条件或者把这些变化纳入训练集。6.2 所有异常图都报“异常”但正常图也误报如果正常图也开始大面积误报大概率是正常样本覆盖不够。工业产品即使良品也会存在纹理差异、轻微位置偏移、边缘反光变化如果训练集里只包含了其中一种形态模型就会把其他形态判为异常。我的处理方式是把正常样本按“形态”分组不同工位、不同批次、不同表面状态分别拍一批混合进训练集。这样模型学到的“正常”才是一个带容差的范围而不是一张模板。这是短期内提升效果最有效的手段比换任何算法都有用。6.3 mask 评估报错或指标异常像素级评估时如果报错说找不到 mask先检查task是否配成segmentation以及ground_truth目录下的子目录名是否和test下的异常子目录名完全一致。比如 test 下是scratchground_truth 下也得是scratchmask 文件名要和异常图文件名一模一样。如果指标异常低比如 pixel-level AUROC 只有 0.5 到 0.6大概率是 mask 和原图尺寸不一致或者 mask 里异常区域用灰色而非纯白色。可以用脚本统计一下 mask 的像素值分布正常情况应该是 0 和 255 两种值中间值越少越干净。6.4 显存或内存不够PatchCore 对资源的要求偏高特征存储量会随训练图数量和分辨率增长。如果显存报 OOM优先降image_size从 256 降到 224 或者 192 往往就能解决精度损失通常可以接受。其次是调低coreset_sampling_ratio比如从0.1降到0.05memory bank 会明显变小。还有一个容易被忽略的点num_workers 太高也会吃内存训练数据量大时把num_workers降到 4 或 8可以缓解内存压力。如果还是不够换 EfficientAD 或 STFPM它们的内存占用比 PatchCore 小很多。6.5 疑难问题速查问题现象大概率原因处理建议训练找不到数据集path / category 配置不对检查目录是否包含 train/good路径是否为绝对路径或相对当前工作目录正确评估时找不到 ground truthtask 配置错误检查 task 是否应为 classification或 ground_truth 目录是否缺失正常图误报率高正常样本多样性不足增加不同产线状态下的正常图到训练集异常图漏报率高异常特征和正常特征太接近提高图像分辨率或尝试 EfficientAD / FastFlowmask 错位坐标未随 resize 缩放确保脚本先缩放坐标再填充 mask部署推理慢backbone 太大换轻量 backbone或导出 OpenVINO / ONNX 加速6.6 最后分享一点我的个人习惯我从接触 Anomalib 到现在最大的体会有两条。第一条是“数据洁癖”很重要每张图从采集到入训练集都要记录拍摄条件、产品型号、缺陷类型这样后期排查问题才能快速定位。第二条是“第一次跑通流程先不过度优化模型”用一个默认配置跑出基线结果再根据错误样本决定是加数据、调预处理还是换模型让数据驱动决策而不是靠感觉。另外我习惯在训练完成后把测试集里所有误判样本单独导出到一个文件夹按正常误报、缺陷漏报分类。每隔一段时间翻一翻你会发现很多规律比如某种纹理总是被误报、某种光照下缺陷总是漏检这些发现比单纯调参带来的收益大得多。Anomalib 的完整工作流还支持 OpenVINO 导出、API 服务化部署等你自定义数据集跑通之后可以再往这些方向扩展。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/19 8:07:43
CANN ops-transformer 中 aclnnMhcPreV2 算子接口全解析:MHC 架构 H 投影矩阵与 hIn 计算实战指南
2026/9/19 8:07:43
从 nodejs.org 周报档案解读 io.js 2015-02-06 更新:贡献量新高、1.1.0 发布与生态迁移实录
2026/9/19 8:07:43
Arthas pwd 命令全解析:查看目标 JVM 应用工作目录的原理与实战
2026/9/19 9:12:47
GN算法边介数计算优化:从O(nm²)到并行增量式实现
2026/9/19 9:12:47
Flutter折叠屏适配实战:窗口布局、双栏路由与状态连续性
2026/9/19 9:12:47
大模型认知机制解析:Prompt工程与思维链技术
2026/9/19 9:12:47
Matlab实现气象塔风资源数据完整分析流程
2026/9/19 9:12:47
PSO算法优化机器人路径规划:减少转角与平滑处理
2026/9/19 9:07:47
Homebrew可视化工具BrewUI:直观管理依赖、升级与缓存
2026/9/19 0:02:13
PixiJS v8 遮罩(Masking)完全指南:AlphaMask、StencilMask、ScissorMask 与 ColorMask
2026/9/19 0:02:13
GLM 5.3 Flash 被 Artificial Analysis 收录:用 TaoToken 复现同一把 Key
2026/9/19 0:02:13
分布式雷达多维度干扰建模与抗干扰算法实现
2026/9/18 16:05:49
拯救者Y7000黑屏故障排查与维修实战指南
2026/9/18 3:56:12
AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
2026/9/18 13:25:13
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化