ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Anomalib自定义异常检测数据集构建实战:从零搭建工业视觉正常样本训练流程

Anomalib自定义异常检测数据集构建实战:从零搭建工业视觉正常样本训练流程 如果你做过工业视觉项目大概率遇到过这种尴尬桌上的缺陷样本少得可怜客户却要求检测的异常类型五花八门。标注几百张缺陷图既费人力又费时间而且换个产品线旧模型又得重新训。Anomalib这套异常检测库就是冲着这个痛点来的它最大的特点是只需要正常样本就能完成训练不需要你提前框出每个缺陷的位置。我最初接触Anomalib时最纠结的不是选哪个模型而是怎么把手上零散的图片整理成它能识别的数据集。这篇文章就从数据集构建入手把从零搭一个自定义异常检测数据集的全过程讲透包括目录结构、配置修改、训练测试和推理出图最后再聊聊我踩过的坑。1. 开始之前为什么异常检测要用Anomalib以及它能干什么异常检测和常规的目标检测、图像分类不是一回事。常规目标检测需要你框出每个目标的坐标分类需要你准备每个类别的正负样本。而异常检测做的事情是在一堆正常样本上建模之后凡是偏离正常分布的输入都会被标记为异常。这在工业场景里极其刚需因为绝大多数产线上“正常”图像很容易拍而“异常”图像往往一年也攒不出几张。具体到Anomalib它是Intel开源的一个异常检测库基于PyTorch Lightning封装里面集成了不少主流的高质量异常检测算法包括PADIM、PatchCore、STFPM、CFA、FastFlow、DRAEM、EfficientAD等。它有两大特点很对我胃口一是复现成本低官方提供了统一的数据组织方式和模型配置配好数据集之后不同模型之间只需要改个名字就能来回切换二是可视化做得全训练、测试、推理阶段都能直接输出异常热力图方便你快速看效果。1.1 只用正常样本训练为什么可行很多刚接触异常检测的朋友会问训练时一张缺陷图都不给模型怎么知道哪些是异常这里的关键在于“特征分布”。以工业视觉里常用的PatchCore为例它用预训练的卷积网络从正常图像中提取大量特征把这些分布密集的特征点保存下来相当于建立了一张“正常外观地图”。推理时模型同样提取特征然后去地图里找最近的邻居如果输入图像的特征离地图太远说明它长得很不像正常样本异常得分就高。如果还是抽象可以把它想象成一个小区的门禁管理员只需要知道小区里住的人长什么样不需要提前收集每个陌生人的照片。当一个从没见过的面孔出现在门口时系统自然就能察觉不对劲。异常检测也是这个逻辑把“正常”定义清楚剩下的就都值得怀疑。1.2 常见模型初步认识Anomalib内置的模型各有侧重做自定义数据集之前最好先心里有个数。我用一个表格简单整理一下以我个人实测经验为准模型训练速度显存占用精度倾向适用场景PADIM快低中高轻量部署、工业在线检测PatchCore中等较高高精度优先、离线检测STFPM快低中需要快速迭代的场景CFA慢高高小样本高精度场景FastFlow中等中等中高需要像素级定位时EfficientAD快低高边缘设备实时检测如果你第一次跑我建议先用PatchCore或PADIM原因是这两个模型不依赖额外的mask标注自定义数据集准备起来最轻松。后面如果要追求像素级分割或者要求实时推理再往别的模型迁移。2. 构建自定义数据集前先把Anomalib的数据组织方式吃透用Anomalib最忌讳的是拿到图就直接塞进训练脚本。它会按一套固定的目录约定来找数据如果你不按约定来跑起来立刻报错比如“Tried to infer the category”之类。所以动手之前先花十分钟把数据组织方式搞清楚后面会省很多事。2.1 为什么默认格式是MVTec格式MVTec是工业异常检测领域最常用的公开数据集里面的文件夹组织方式已经成为事实标准每个类别一个文件夹类别下有train和test两个目录train下只有good子目录test下有good子目录和若干异常类别子目录。Anomalib的数据加载器默认就按这套结构读取数据。这样做最大的好处是“对模型透明”。同一套目录结构换数据不需要改代码逻辑同一套目录结构换模型也不需要重新组织数据。2.2 Folder与MVTec两种组织方式Anomalib支持两种数据格式名称上也有区别组织方式适合情况目录结构要求folder自定义数据集、没有官方标注结构train/goodtest/good、test/缺陷名mvtec使用MVTec数据集或按MVTec结构整理的数据更复杂含ground_truth标注目录对大多数自己采集的图像直接用folder格式就够了。它不强制要求你提供缺陷mask只需要把正常样本和异常样本按目录区分开。如果项目需要做像素级定位也可以把mask目录加上用Anomalib的分割任务配置加载。2.3 图像采集与预处理建议做自定义数据集的时候图像质量直接影响最终效果。我在实际项目中有一条经验异常检测对“背景一致性”的要求远高于对“图像数量”的要求。第一尽量保持拍摄角度、打光、相机参数一致。同一产线上拍出来的图如果一会儿亮一会儿暗模型会把光照变化当成异常误报率会居高不下。第二所有图像最好统一分辨率。Anomalib内部会自动resize但如果你输入的图片本身尺寸参差不齐能保留的细节就少缺陷也可能在resize时被抹掉。建议先统一分辨率比如256x256或更高再灌给模型。第三正常样本要有代表性。别只拍同一件产品要多拍几个批次、不同装配位置让“正常”这个概念的覆盖范围足够完整。如果正常样本本身就天南地北模型会记住一个很松的分布异常检测能力会被稀释。3. 手把手构建自定义数据集以“金属表面划痕”为例这部分我拿一个常见的场景来说金属表面划痕检测。假设你已经拿到了一批金属零件的图片其中有表面完好的也有带划痕的现在要把它们整理成Anomalib能吃下的数据集。3.1 准备正常的训练集正常图像是训练的基础建议至少准备几百张。在Anomalib的无监督训练模式下训练集只放正常样本也就是train/good目录。注意一个容易踩的坑千万不要把外观差异特别大的“正常”混在一起比如有些是拉丝面有些是抛光面最好单独拆成两个类别分两套数据集训练。否则模型为了覆盖两类外观会把自己变成“只要跟拉丝、抛光各沾点边都算正常”真正的缺陷反而漏掉。3.2 准备测试集测试集的作用是验证模型能不能区分正常与异常。在test目录下正常图像放到test/good异常图像按缺陷类型分别建目录比如test/scratch、test/dent。如果只有一种缺陷建一个test/bad也可以。测试集中正常和异常的比例没有严格规定但建议异常类型尽量覆盖真实生产中会出现的形态。比如划痕有细长的、有团状的、有弯曲的每种都放一些模型评估出来的指标才可信。测试图像不要在训练集中出现过否则指标虚高部署到现场会被现实教育。3.3 目录落盘实测下面我把整个目录结构列出来这就是Anomalib文件夹格式的最终形态datasets/ └── metal_surface/ ├── train/ │ └── good/ │ ├── 001.jpg │ ├── 002.jpg │ └── ... └── test/ ├── good/ │ ├── 101.jpg │ └── 102.jpg └── scratch/ ├── 201.jpg └── 202.jpg如果你手上图片很多手动建目录会疯掉。我用一个简单的bash命令批量整理过流程是先把所有正常图扔到normal_imgs目录所有划痕图扔到scratch_imgs目录再执行mkdir -p datasets/metal_surface/train/good mkdir -p datasets/metal_surface/test/good mkdir -p datasets/metal_surface/test/scratch cp normal_imgs/*.jpg datasets/metal_surface/train/good/ # 从normal_imgs中留出20%给测试 ls normal_imgs | head -n 80 | xargs -I {} cp normal_imgs/{} datasets/metal_surface/train/good/ ls normal_imgs | tail -n 20 | xargs -I {} cp normal_imgs/{} datasets/metal_surface/test/good/ cp scratch_imgs/*.jpg datasets/metal_surface/test/scratch/这个脚本很粗暴但能跑。如果是正式项目我更推荐用Python脚本按比例随机划分避免前80张都是同一个批次导致数据分布不均。随机划分时注意固定随机种子保证可复现。4. 让Anomalib认识你的数据配置与启动训练目录准备好之后下一步是让Anomalib按我们的数据集配置来训练。这部分涉及配置文件也是新手最容易卡住的地方。4.1 修改数据集配置Anomalib 1.x版本的典型做法是写一个YAML配置文件。下面是我经常用的一组关键配置以PatchCore为例dataset: name: metal_surface format: folder path: ./datasets/metal_surface category: scratch task: classification train_batch_size: 16 eval_batch_size: 16 num_workers: 4 image_size: [256, 256] test_split_mode: from_dir test_split_ratio: 0.2 val_split_mode: same_as_test val_split_ratio: 0.2 model: name: patchcore backbone: wide_resnet50_2 layer: layer3 normalization_method: min_max threshold: image_default: 0 pixel_default: 0先解释几个容易理解的参数format: folder告诉数据加载器我们用的是文件夹格式path和category拼接起来就成了./datasets/metal_surfacetask如果是classification只做图像级判断如果需要输出像素级定位图可以改成segmentationtest_split_mode: from_dir指测试集直接从你放好的test目录读取而不是从训练集里切一部分。你需要按自己的实际路径和类别名调整。如果Anomalib内部封装的Folder类默认路径结构和你不一样最笨但也最保险的办法是让目录结构严格对齐上面的示例。4.2 训练命令与日志输出配置文件改好之后在Anomalib工程根目录下执行训练命令这里以1.x的tools为例python tools/train.py --model Patchcore --config configs/custom_dataset.yaml训练启动后会看到PyTorch Lightning风格的日志里面有当前epoch、loss、验证集AUROC等信息。PatchCore这类方法不按传统方式反向传播权重训练过程看起来更像是在“构建记忆库”有时loss显示为0也很正常不用慌。如果你用的是Anomalib 2.0及以上版本命令行入口有调整但数据组织方式几乎没变。换版本时最需要改的是API层数据准备这一套逻辑是通用的。4.3 训练过程中看什么训练过程中我重点关注两条一是异常分数分布二是图像级AUROC。Anomalib的验证集默认会输出一个AUROC指标表示“随机挑一个正常样本和一个异常样本模型把异常样本排在前面”的概率。AUROC在0.9以上算是可用的模型如果只有0.7左右就需要先检查数据、再看模型参数。另外如果训练时出现大量“normalization_method”相关的警告一般是数据mean/std计算时机的问题不会直接报错但会影响指标。建议保持默认值跑通一次再逐项调优。5. 测试与推理验证自定义数据集效果训练完一个模型紧接着要做两件事先在保留的测试集上获取正式指标再做推理可视化看模型输出的异常热力图到底长什么样。5.1 在测试集上跑指标测试命令同样很简单python tools/test.py --model Patchcore --config configs/custom_dataset.yaml --ckpt_path results/metal_surface/patchcore/run/weights/model.pt它会遍历test目录下的good和scratch图像输出图像级AUROC、像素级AUROC如果启用了segmentation、F1-max、PRO-AUC等指标。翻译成人话就是图像级AUROC看的是“整张图是否异常”像素级AUROC看的是“异常区域定位得准不准”。如果只关心有没有缺陷看图像级就好如果还要给下游机械臂做定位像素级才有参考价值。第一次跑测试如果发现指标低于预期先别急着调模型。我通常先打开测试集里的图片确认没有混入训练图、没有标签颠倒、没有异常图长得和正常图几乎没区别。5.2 推理出图与热力图Anomalib的推理接口可以针对单张或多张图片生成可视化结果python tools/inference.py --model Patchcore --config configs/custom_dataset.yaml --ckpt_path results/metal_surface/patchcore/run/weights/model.pt --input datasets/metal_surface/test/scratch/201.jpg输出目录里一般会有一个叠加了热力图的图像以及一个原始异常图anomaly map。热力图上越红的位置异常得分越高用肉眼就能快速判断模型是否真的捕捉到了划痕。我习惯把这个可视化结果直接发给现场工程师看比盲目看指标值直观得多。这里补充一个经验Anomalib推理输出的是16位浮点图直接用看图软件打开可能是一团黑。别慌用代码或工具做一次归一化后再保存效果就正常了。这个坑几乎每个用过Anomalib的人都会遇到。6. 自定义数据集的常见坑与排查实录这部分写我自己踩过或者帮同事踩过的坑挑典型的记录下来方便你遇到时快速定位。6.1 数据集路径和类别匹配报错Anomalib启动时会去找path/category组合如果你把category写错或者目录层级多套了一层就很容易出现找不到数据集的错误。建议先在终端里ls一下配置里的路径确认路径真实存在。有时候是Windows和Linux路径分隔符的问题统一用os.path.join风格的相对路径更稳。6.2 测试集显示为空如果配置文件里test_split_mode设成了from_dir但test目录下没有good子目录或者图片后缀不在支持的列表里比如.png和.jpg混用但扩展名写错都可能加载出空数据集。我的排查思路是先看日志中dataset实例化后输出了多少张图片数量为0就检查图片扩展名和目录名。6.3 显存溢出OOMPatchCore在利用预训练特征时每个正常样本都会保留特征向量图多、分辨率高、backbone宽显存很容易爆。解决办法有三个降低train_batch_size、降低image_size、换轻量backbone。如果只是验证一个思路用resnet18级别的backbone足够。6.4 模型输出“全绿”无异常训练集和测试集都正常但推理时所有图都显示正常一个都不报警。这种情况大概率不是因为模型好而是因为数据有问题。最常见的原因是测试集中的“异常”其实非常轻微肉眼都难分辨模型没有充分抓取到可区分的异常模式。另一个原因是分割任务配置下threshold设置太高把所有区域都判定为正常。可以把threshold调低再看结果。6.5 图片后缀和读取问题Anomalib底层用OpenCV或Pillow读图遇到损坏的图片不会直接跳过有时会卡在中间。正式项目里我建议先跑一遍完整性检查把无法读取的图片列出来只保留能读的。另外图像通道顺序要注意拍摄设备偶尔会输出BGRA格式直接喂给模型可能导致颜色偏移影响效果。下面我把上面提到的坑整理成一张速查表现象常见原因排查顺序数据集找不到path/category写错核对路径、查看日志测试集为空test目录缺good检查目录结构和扩展名显存OOM批次太大/图太大降低batch、resize、换backbone全绿不报警异常过于轻微或阈值太高看样本、调threshold热力图全黑输出float图未归一化归一化后保存6.6 合成异常的使用技巧如果你手上的异常样本实在不够Anomalib还提供了synthetic模式的合成异常切分。也就是在训练集内部通过cutpaste、random等策略生成伪异常用于构造测试集或辅助训练。这对只有几张缺陷图的场景帮助很大。但合成异常和真实物理缺陷还是有差距用它做验证可以做最终验收前一定要回归到真实数据上。7. 选型建议与后续扩展数据集构建完成、模型能跑通后很难忍住不去调优。这里讲几种我常用的优化方向以及模型选型上的建议。7.1 如何按场景选模型如果是产线边缘侧在线检测设备算力有限优先PADIM或EfficientAD如果是离线抽检可以放心上PatchCore效果通常最扎实。如果同时要看像素级定位FastFlow和CFA值得尝试但这两个对数据和显存的要求相对高。实在拿不准就在你自己的自定义数据集上把PADIM和PatchCore各训一版比较图像级AUROC和高误报率下的漏检率选更贴合业务的那个。7.2 提升效果的小技巧第一训练时控制数据增强强度。Anomalib默认会做随机翻转和裁剪但在工业场景中过强的增强反而可能破坏“背景固定”这个有利条件。我一般只保留轻微旋转和水平翻转。第二正确划分验证集。验证集最好和测试集来源一致避免模型在一组图像上过拟合换一组就崩。第三手动检查阈值。Anomalib会为每个类别自动计算最佳阈值但自动阈值不一定满足你的业务需求。如果产线上对“漏检”和“误报”的容忍度不一样就手动调阈值把安全方向放在漏检一侧。7.3 从单类到多类的扩展如果一条产线上有多个产品品类不要直接把所有品类混进一个数据集。正确做法是每个品类建一个独立category目录分别训练模型推理时先做品类识别再进对应的异常检测模型。虽然多部署几个模型但每个模型都“脑子清醒”线上表现反而更稳。如果有能力还可以给高质量缺陷样本做mask标注走分割任务路线。这样模型对缺陷区域的定位会更精准也方便后续自动剔除或机械臂处理。从分类到分割数据集结构变化不大只是多了一个ground_truth目录Anomalib的mvtec格式已经为你预留好了位置。最后分享一个我个人的真实体会。异常检测项目上线后最容易被忽视的不是模型精度而是数据回流。现场每天都会产生大量新图片其中可能有新的缺陷形态也可能有新的正常形态。如果没有一套持续更新数据集的机制模型会慢慢老化。我现在的做法是把现场反馈的误报、漏检图片定期归档按正常和缺陷重新跑一遍本文的整理流程每两周左右增量训练一次。这样模型和产线始终是同步的。异常检测模型的迭代速度很大程度取决于数据流程能不能标准化。把数据准备脚本和训练配置一起放进版本管理绝对是值得投入的一件事。
返回列表