2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[2025年7月版] Anomalib v2.0.0 を GPU + Windows で動かす (WSL2を使用)

2
Last updated at Posted at 2025-07-02

趣旨

表題のままです。anomalib 1.x から微妙に変わっているうえに、変更点について特にドキュメントとかないとか酷いんですけど、動かすことはできたのでメモしておきます。

ポイント

  • Python はバージョン 3.11 にする

変更点

  • テスト用のデータセット名が MVTec ではなく MVTecAD になった。
  • python の API が微妙に変わってる(これがやっかい!)

手順

Windwis 11 Home + WSL2 + RTX 3060Ti, 4060Ti では動きました。Anaconda を使ってますがそこは必須ではないです。バージョンについては、2025.7 の時点で動く最新のセットになっていますが、時間が経過したら変わっているかもしれません。

  • NVIDIA の Driver を最新版にする
  • Windows 11 に Ubuntu 22.04LTS (WSL2) を入れる
  • Ubuntu で cuda 12.6 を入れる
  • Anaconda をインストールする
  • 環境を作って python 3.11 をインストールする
  • Pytorch をインストールする(CUDA 12.6 版を入れること!)
  • anomalib v2.0.0 をインストールする

インストールするツールやライブラリのバージョンが違うだけで、手順は下記に書いてある内容と同じです。そのうち描き直します(適当)

なお、cuda 12.8 (+ pytorch 12.8 対応版) をインストールしても学習とテストが動くことは確認しました。

anomalib v2.0.0 をインストールする

下記のとおりです。

$ pip install anomalib
$ anomalib install

WSL2 を使わず Windows 11 側の anaconda で install すると、あいかわらず途中でシンボリックリンクが作れないといわれてエラーで落ちます。

training

MVTecAD のデータを使って学習させてみます。実行すると、1回目だけ MVTecAD のデータが 5GB ほどダウンロードされます。

$ anomalib train --model Patchcore --data anomalib.data.MVTecAD 
... 中略
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃        Test metric        ┃       DataLoader 0        ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│        image_AUROC        │            1.0            │
│       image_F1Score       │    0.9919999837875366     │
│        pixel_AUROC        │    0.9814900159835815     │
│       pixel_F1Score       │    0.7311215400695801     │
└───────────────────────────┴───────────────────────────┘

上のようなテスト結果の表が出たらインストールと実行は成功です。

カスタムデータによる学習

正常なデータだけを学習させる(異常データの学習はしない)方法だけ示します。ソースではデータに MVTecAD の bottle を使っていますが、指定しているパスを変更すればカスタムデータで学習できます。

datasets/MVTecAD/bottle/
├── test        # テスト用データを配置(必要に応じて)
├── ground_truth # テスト用のデータの正解(マスク)画像を配置(必要に応じて)
└── train
    ├── good    # 学習用正常データを配置
    └── bad     # 学習用異常データを配置(必要に応じて)

データは上記のように配置されています(配置します)。

train.py
from anomalib.models import Patchcore
from anomalib.engine import Engine
from anomalib.data.utils import TestSplitMode, ValSplitMode

from anomalib.data import Folder
import anomalib

from torchvision.transforms.v2 import Compose, CenterCrop

transform_size = 224

datamodule = Folder(
  name="bottle",
  root="./datasets/MVTecAD/bottle",
  normal_dir="train/good",
  test_split_mode=TestSplitMode.NONE,      # テストしないとき NONE
  val_split_mode=ValSplitMode.SYNTHETIC,   # NONE にすると失敗する
  val_split_ratio=0.5,
  #test_split_mode=TestSplitMode.SYNTHETIC,
  #mask_dir="ground_truth",
  train_augmentations=Compose(             # transform を使わない場合はコメントアウト
    [
        CenterCrop( transform_size ),
   ],
  )
)
datamodule.setup()

model = Patchcore()
engine = Engine()

# 学習
engine.fit(datamodule=datamodule, model=model)

# 推論テスト
predictions = engine.predict(
    data_path="./datasets/MVTecAD/bottle/test/good",
    model=model,
)

コード中の nameroot を変更することで、指定するデータセットを変更できます。

異常検知画像をヒートマップ画像にする

下記との違いだけ書きます。

from anomalib.models import Patchcore
from anomalib.engine import Engine
import matplotlib.pyplot as plt

import numpy as np
import os
from datetime import datetime
import torchvision.transforms as transforms

# 推論処理
predictions = Engine().predict(
    # MVTec のデータが datasets 以下に配置されている場合
    data_path="datasets/MVTecAD/bottle/test/broken_large",
    model=Patchcore(),
    # 学習済みモデルへのパスを指定する
    ckpt_path="./results/Patchcore/bottle/v0/weights/lightning/model.ckpt",
)

# 画像を出力するルートフォルダ
outpath = './output'

# 出力パスが上書きされないように日付と時刻名でフォルダを作成する
now = datetime.now()
formatted_datetime = now.strftime("%Y%m%d_%H%M%S")
result_path = os.path.join( outpath, formatted_datetime)

# 出力フォルダを作成
os.makedirs( result_path, exist_ok=True)

i = 0
for res in predictions:
  print( res )
  #exit()
  # 出力画像のファイル名を作成
  filename = res.image_path[0]
  basename = os.path.basename(filename)
  file, _ = os.path.splitext(basename)

  anomaly_file = os.path.join(result_path, file + "_ano_" + str(i) + ".png")

  # 推論に使用した画像の出力(変形の適用後)
  to_pil_image = transforms.ToPILImage()

  # 異常部分のみを取り出した画像(グレー画像。白に近いほど異常があると推論)
  image_array = res.anomaly_map.to('cpu').squeeze(0)
  
  pil_image = to_pil_image(image_array)
  #pil_image.save(anomaly_file)

  # グレー画像をヒートマップに変換
  gray_array = np.array(pil_image)  # PIL画像をnumpy配列に変換
  plt.figure(figsize=(6, 6))
  plt.axis('off')  # 軸を非表示にする
  plt.imshow(gray_array, cmap='jet')  # ヒートマップカラーマップを適用 ('jet' は一般的なヒートマップ)
  #plt.colorbar()  # カラーバーを追加(オプション)
  plt.savefig(anomaly_file, bbox_inches='tight', pad_inches=0)  # ヒートマップ画像を保存
  plt.close()
  print( f"Results: {anomaly_file=}" )
  i += 1

res['image_path']res.image_path に、res['anomaly_maps'].res.anomaly_map に変更となったようです。

image.png

出力結果は、たとえば上のようになります。

複数閾値で異なるマスク画像を出力する

from anomalib.models import Patchcore
from anomalib.engine import Engine

import numpy as np
import os
from datetime import datetime
import torchvision.transforms as transforms

# 推論処理
predictions = Engine().predict(
    # MVTec のデータが datasets 以下に配置されている場合
    data_path="datasets/MVTecAD/bottle/test/broken_large",
    model=Patchcore(),
    # 学習済みモデルへのパスを指定する
    ckpt_path="./results/Patchcore/MVTecAD/bottle/v0/weights/lightning/model.ckpt",
)

# 画像を出力するルートフォルダ
outpath = './output'

# 出力パスが上書きされないように日付と時刻名でフォルダを作成する
now = datetime.now()
formatted_datetime = now.strftime("%Y%m%d_%H%M%S")
result_path = os.path.join( outpath, formatted_datetime)

# 出力フォルダを作成
os.makedirs( result_path, exist_ok=True)

i = 0
for res in predictions:
  # 出力画像のファイル名を作成
  filename = res.image_path[0]
  basename = os.path.basename(filename)
  file, _ = os.path.splitext(basename)

  anomaly_file = os.path.join(result_path, file + "_ano_" + str(i) + ".png")

  # 推論に使用した画像の出力(変形の適用後)
  to_pil_image = transforms.ToPILImage()

  # 異常部分のみを取り出した画像(グレー画像。白に近いほど異常があると推論)
  image_array = res.anomaly_map.to('cpu').squeeze(0)
  pil_image = to_pil_image(image_array)
  pil_image.save(anomaly_file)

  # 画像データのしきい値以上の領域を白く塗りつぶす(マスク)処理
  # 閾値は 0 < th < 1 の範囲で指定
  threshoulds = [0.4, 0.5, 0.6]
  for th in threshoulds:
    mask_file = os.path.join(result_path, file + "_mask_" + str(i) + "_" + str(th) + ".png")

    # image_array を取得
    image_array = res.anomaly_map.to('cpu').squeeze(0).numpy() 

    # チャンネル次元を増やす
    image_array = image_array[:, :, np.newaxis]

    # RGB値の判定と更新
    binary_image = np.where(np.all(image_array <= th, axis=-1, keepdims=True),
                            [0, 0, 0],
                            [255, 255, 255]).astype(np.uint8)

    pil_image = to_pil_image(binary_image)
    # マスク画像の出力
    pil_image.save(mask_file)

    print( f"Results: {anomaly_file=}, {mask_file=}" )
  i += 1

res.anomaly_map がカラーのチャンネル次元を持たなくなったようなので、そのあたり少しコード変えてます。

0.4 0.5 0.6
image.png image.png image.png

閾値でマスクのサイズが変わってることが分かります。

結論

バージョンアップで変更したらちゃんとドキュメントかいてよー

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?