【2022年AIモデル紹介】外観検査AIモデル PatchCore の紹介！

:::note warn
この記事は2023年4月17日に弊社ブログに公開された記事の移植記事となります。
:::

こんにちは、@2626です。

本記事では、外観検査AIモデル PatchCore を紹介します。最初に、PatchCore の特徴や実際のアルゴリズムを簡単に説明し、実際のAIモデル学習の結果も記載します。最後の部分では、アルゴリズムの理論的な説明に入るので、少し難しくなるかもしれませんが、最後まで読んでもらえると嬉しいです！


# そもそも外観検査とは？
外観検査とは、部品や製品の表面を検査することです。この検査は製造業を含む多くの工場で行われており、傷・凹み・歪み・異物・汚れなどの有無を確認します。検査された部品や製品は、業界や企業内で定められた基準に準拠しているかどうかが評価されます。従来の検査方法は目視に依存していましたが、以下のようなデメリットがあることが問題でした。


### 目視による外観検査のデメリット
1. **正常・異常判定の評価基準のばらつき**
    ベテラン作業員など、その製品の正常・異常判定に精通している人が必要であり、属人化することがある。
1. **ヒューマンエラー**
    見逃しや長時間の作業による集中力の低下によって、異常な製品が見逃されてしまう。
1. **人件費等のコストの増大**

機械学習の手法は様々ですが、教師あり学習と教師なし学習の2種類に分類されます。教師あり学習とは、学習データに正解を与えた状態で学習させる手法です。一方、教師なし学習とは学習データに正解を与えずに学習させる手法です。従来の外観検査の自動化では主に教師あり学習が使われていましたが、その手法には以下のようなデメリットがあります。


### 教師あり学習による外観検査のデメリット
- **正常な画像、異常な画像に正解（ラベル）を与える必要がある**
    - 画像1枚ごとにラベル付けが必要であり、作業コストがかかってしまう
- **多くの異常画像が必要である**
    - 実際の製造業の現場では、不良品はめったに発生しないことから画像を集めることが難しい
- **過去にラベル付けしていない異常画像を判定できない**
    - 傷と凹みというラベル付けをしたとしても、実際の運用場面で歪みの異常が発生した場合、正しいラベル付けができない

そこで、近年では教師なし学習を用いた外観検査アルゴリズムが開発されています。今回紹介する PatchCore も教師なし学習アルゴリズムの1つです。


# PatchCoreの特徴
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/95c759d6-8f00-5076-2549-09e4e911b9a3.png)
> Karsten Roth, Latha Pemula, Joaquin Zepeda, Bernhard Schölkopf, Thomas Brox, Peter Gehler, University of Tübingen, Amazon AWS, "Towards Total Recall in Industrial Anomaly Detection", 2021, Figure 2. より引用

PatchCoreは、過去の教師なし学習の外観検査モデル [SPADE](https://arxiv.org/abs/2005.02357) や [PaDiM](https://arxiv.org/abs/2011.08785) を踏襲した新しいアルゴリズムです。2022年にCVPR2022[^1]で採択され、[MVTecAD Dataset](https://www.mvtec.com/company/research/datasets/mvtec-ad)[^2] で外観検査におけるSOTA[^3]を達成しました。

PatchCore では学習データの特徴量を保存しておき、推論時に学習時に保存しておいた特徴量と比較することで、各部品画像の異常度を測る手法を用いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/a1a14fcb-4f5d-7a8f-8065-6f65dc6638dc.png)

また、上図の特徴量抽出の部分には、深層学習の学習済みモデルが使われています。この学習済みモデルは、特徴量の計算のみに使用されるため、追加学習やファインチューニングは必要ありません。そのため、勾配計算などが不要になる点も大きな特徴の1つになっています。

次に、PatchCore の大まかなアルゴリズムについて説明していきます。


## アルゴリズム（学習時）
PatchCoreの学習手順は大きく2つに分けられます。


### 特徴量抽出
学習データと学習済みモデル[^4]を用いて、中間層の特徴量を抽出します。このとき、複数の中間層の値を用いることで、画像の位置情報を含めた正常・異常の判定が可能になります。これによって、画像データの製品そのものの正常・異常だけでなく、異常箇所も同時に特定することができるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/85aa5286-9e22-6b3f-9927-8f52baf866dc.png)


### 新しい特徴量集合の作成
上記の計算で得られた全ての画像の特徴量データは1つにまとめられます。しかし、学習する画像データが多ければ多いほど、特徴量データの数も増えてしまい、推論時にはメモリ使用量が増えたり、時間がかかるといったのデメリットが生じます。

そこで、特徴量データの量を適切に減らすために、[貪欲法](https://ja.wikipedia.org/wiki/%E8%B2%AA%E6%AC%B2%E6%B3%95)[^5]という方法を用いて似た特徴量データを1つにまとめます。残った特徴量データの集合は Memory Bank と呼ばれ、PatchCore における学習データになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/c632b123-f64a-69f2-52c8-1405157fba50.png)


## アルゴリズム（推論時）
上記の方法によって、Memory Bank と呼ばれる特徴量データの集合が作成されました。これを用いて推論する、PatchCore では以下の手順が進められます。


### 特徴量抽出
アルゴリズム（学習時）と同じように計算することによって、特徴量を抽出します。

### 特徴量と Memory Bank との距離計算
Memory Bank の各特徴量と推論したい画像の特徴量を比較し、推論したい画像の特徴量から最も近い Memory Bank の特徴量までの距離を計算します。この計算には厳密にはさらに複雑な手法が必要ですが、補足に記載しておきます。Memory Bank にはあらかじめ正常な画像の特徴量データが保存されています。このとき、下記のようなことが分かります。

- **推論したい画像が正常な場合**
    学習した画像データと類似しているはずなので、同じような特徴量データが生成されるはずです。よって、推論したい画像の特徴量データは、Memory Bank から近い位置に生成されると考えられます。

- **推論したい画像が異常な場合**
    学習データに存在しない特徴量が抽出されるはずです。よって、推論したい画像の特徴量データは、Memory Bank の特徴量データから遠い位置に生成されると考えられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/0705da38-8731-4ba1-f776-31294308c304.png)

以上が PatchCore のアルゴリズムについての説明でした。それでは実際に、PatchCore の実装を使った学習および推論結果を確認していきましょう。


# 実行環境
### PC周りの環境
環境は以下の通りです。
- Windows10 Pro 22H2 (64bit)
- WSL2(Ubuntu-20.04.6 LTS)
- Docker Desktop for Windows (Ver.4.16.3)
- NVIDIA グラフィックボードとドライバ
    - GeForce RTX 3060
    - NVIDIA グラフィックスドライバー 528.79


### ハイパーパラメータ
PatchCoreを実行する場合、パラメータの設定が必要になります。実際の運用環境では、設定によって精度や推論時間が変化するため、環境によって最適なパラメータを設定する必要があります。本検証では、下記のパラメータで実行しています。
- 使用した学習済みモデル：WideResnet-50×2
- Memory Bank の取得割合：25%
- 取得する近傍数：9個


### ソースコード
PatchCoreのソースコードは[こちら](https://github.com/amazon-science/patchcore-inspection)を一部改変の上、使用しています。


### 使用画像データ
画像は弊社で撮影した綿棒を使用しています。一部の画像を添付しておりますので、確認してみてください。
1. **データ加工**
    画像データをどのように加工するのかも PatchCore におけるパラメータの1つとされています。今回は下記のようなパラメータで実行しています。
    - 画像の切り取り：なし
    - 画像のリサイズ：224×224
    - ImageNet[^6]の正規化[^7]：あり
    <br></br>

2. **学習画像データ**
    - 良品の綿棒×16枚（縦320×横320）
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/32ab5dfb-1b7b-1552-c466-6aff3afd82ed.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/9a71c6e0-b0be-bef2-d809-ee2b4b10e904.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/f3145f05-7386-ab87-7995-383306d9162b.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/8625895a-4e04-fef4-9c44-b5b6d0557e29.png", width=128></td>
    </tr>
    </table>
    <br></br>

3. **推論データ**
    - 良品の綿棒×8枚（縦320×横320）
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/e299a8d6-f58f-3bf1-e855-6ccc4808f2cc.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/58bdb5b7-1764-4f77-c5ac-226a3f1e1999.png", width=128></td>
    </tr>
    </table>
    <br></br>

    - 不良品の綿棒×20枚（縦320×横320）
    異物を取り付ける、折る、色付けなど様々な異常を作成しています。
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/c871d3f8-adf0-51c4-dce7-15af5ab6e4c5.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/805e82af-8910-f57d-f0d9-899da71fb95b.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/5412d95d-61c4-4171-a632-bc5efe4eba4c.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/e28bf56e-1606-66d4-1eb6-4acc99ce66ab.png", width=128></td>
    </tr>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/5d8cfaf9-ea53-fe24-969f-df903ce6a76b.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/957f0b4d-96aa-491f-776e-9b9bdcc2a273.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/229bc381-a40d-994e-9d7c-32195e03c5df.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/82e01069-93ac-d09a-e85e-dc6ab7967ab1.png", width=128></td>
    </tr>
    </table>

# 実行結果
上記の推論用の画像データ（良品×8枚、不良品×20枚）を用いた結果は、下記の通りです。
1. **全体的な結果**
    1枚のみ不良品画像を良品と判断したが、概ね正しい識別ができていることが分かります。なお、True label の 0 は「良品の綿棒の画像」、1 は「不良品の綿棒の画像」を表しています。またPredicted label の 0 は「良品である判定した画像」、1 は「不良品であると判定した画像」を表しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/79d17d37-5787-333c-6485-c37cc0041725.png)
<br></br>

2. **ヒートマップ**
    - 良品画像
    下図のように綿棒の綿部分が赤くなっており、異常箇所と判定されているように見えます。これは全ピクセルの異常値の相対評価によってピクセル表示しているためです。後述のように、閾値を適切に設定することで、異常箇所の表示・非表示を制御できます。
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/3ab7b922-1889-f9ad-eeb8-69c0760040c2.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/097afd69-e613-d392-6d7f-5bf1abade17b.png", width=128></td>
    </tr>
    </table>
    <br></br>

    - 不良品画像
    異物、汚れ、欠けなど、あらゆる不良箇所に対応できていることが分かる。
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/b6bb8cda-74b5-8d47-d093-70c0b76e3d6e.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/79c8960c-cb10-a8c3-3679-b146bf59541c.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/2ef54411-fd40-8c97-1ff9-45caa76ccc81.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/9e33caff-a030-317d-979b-46f5c222368a.png", width=128></td>
    </tr>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/d88c70f6-1bd2-f205-0d38-78a9f61835ea.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/92c99784-89be-9b38-7bfa-c0c2c25d888f.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/cdfaa88c-46f5-4f26-dd96-2e4de4227253.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/a859087f-8455-8d6a-d46a-515509fcb565.png", width=128></td>
    </tr>
    </table>
    <br></br>

3. **異常箇所に境界線を引いた画像**
    - 異常箇所に境界線を引いた画像
    良品画像の検証の結果、境界線が無いことが分かる。これは、画像に異常箇所がないことを表している。

    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/0e1aef7e-630d-7518-b524-a37986b41c53.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/ebe07f07-ee54-9dde-9bb6-db65ab44e2fa.png", width=128></td>
    </tr>
    </table>
    <br></br>

    - 不良品画像
    適切に閾値を設定することによって、不良箇所に境界線が引かれていることが分かる。
    <table>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/81e06db6-9712-8164-dceb-88ccc9fa364e.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/a852da58-b671-2e25-8325-6f7f9f66dad1.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/92ef175e-ee85-4d05-4107-ad07af9a120b.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/6b4890c3-f274-5802-a155-426a42490c4a.png", width=128></td>
    </tr>
    <tr>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/4517f61e-2120-1d5d-051f-8a9d3f13fabd.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/3c2eff93-97fa-3a8d-39cf-9ed0b394572e.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/9846137c-d46a-bb3d-b392-3d2a01157953.png", width=128></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904801/c3cba67a-9dc4-228b-b9c7-92723aeb72a9.png", width=128></td>
    </tr>
    </table>
    <br></br>


# まとめ
ここまで、外観検査AIモデル PatchCore を紹介していきました。PatchCore は2022年に外観検査で SOTA を達成しただけあって、異常箇所をかなり精度よく検出していることが分かります。ただし、良品の画像のみでモデルを作成することはできますが、正常・異常を分けるための閾値を設定するには、不良品の画像が必要になります。よって、実際の現場では、モデルの導入後であっても不良品の異常値に応じて適切に閾値を設定し、更新していく必要があります。

また、上記の実行結果でも記載したように、不良品画像を良品と判断した例がありました。一般的に、100%正しい判断ができるAIモデルはありません。しかし、このようなAIモデルを用いることによって、ヒューマンエラーを減らし、人件費やコストの削減を図ることが可能です。


# 補足

ここからは推論時における PatchCore のアルゴリズムを説明していきます。数式を用いて説明しているため、難しい方は読み飛ばしていただいで構いません。また、本論文と一部記号の定義内容が異なる箇所がありますが、ご了承ください。

下記の文字を導入します。
- Memory Bank : ${\mathcal M}$
- 学習時に実行した特徴量変換 : ${\mathcal P}$
- 推論画像データ : ${\mathcal D}$

正常・異常を判定したい画像データを $x_{\rm test} \in {\mathcal D}$ とする。また、
```math
{\mathcal M}_{\rm test} = {\mathcal P}(x_{\rm test})
```
とする。このとき、画像単位の異常スコアは下記のように計算される。

1. ${\mathcal M}\_{\rm test}$ の各点に対して、${\mathcal M}$ の一番近い点を求める。ここではユークリッド距離が使われる。
    ```math
    {\mathcal M}^*_{\rm test} = \left\{
     (m, m_{\rm test}) \in {\mathcal M} \times {\mathcal M}_{\rm test}
     \ | \ 
     \underset{m \in {\mathcal M}}{\rm argmin}
     \|m_{\rm test} - m \|_2
    \right\}
    ```

2. $(m, m_{\rm test})$ のうち、一番距離が大きいペアを $(m^*, m^{\*}_{\rm test})$ とする。

3. $s^\* =\|m^\*_{\rm test} - m^* \|_2$ とすると、画像単位の異常スコアは
    ```math
    s = \left(
     1 
     - \frac{
     \exp \|m^*_{\rm test} - m^* \|_2
     }{
     \displaystyle 
     \sum_{m \in {\mathcal N}_b(m^*_{\rm test})}
     \exp \|m^*_{\rm test} - m \|_2
     }
    \right) \cdot s^*
    ```
    で求められる。ここで、$b \in {\mathbb N}$ に対し ${\mathcal N}\_b(m^\*\_{\rm test}) \subset {\mathcal M}$ は、$m^\*_{\rm test}$ から最も近い ${\mathcal M}$ の $b$ 個の点の集合である。（蛇足だが、$m^\* \in {\mathcal N}_b(m^\*\_{\rm test})$ である。）

もし不良品画像であれば、$m^\*\_{\rm test}$ は異常箇所のピクセルの値が選ばれ、$m^\*\_{\rm test}$ と ${\mathcal M}$ の各点の距離は遠くなるはずである。よって、$s$ と $s^*$ が近い値を取るようになり、異常スコアが大きくなる。

ピクセル単位の異常スコアは下記のように計算される。

1. パッチ単位の異常スコアを計算する。下記の式で求められる。
    ```math
    {\mathcal S} = \left\{
     \|m_{\rm test} - m\|_2
     \ | \ 
     (m, m_{\rm test}) \in {\mathcal M}^*_{\rm test}
    \right\}
    ```

2. パッチの異常スコアは元画像のサイズより小さいので、ガウシアンフィルタによりアップスケーリングして元画像のサイズと同じ大きさにする。一般的には $\sigma = 4$ が用いられる。


# 参考文献
- 【CVPR2022】画像異常検知 PatchCoreの実装解説
    https://zenn.dev/kwashizzz/articles/ml-anomaly-det-patchcore
- 【2022年最新AI論文】画像異常検知AIの世界最先端手法「PatchCore」の論文を解説【CVPR 2022】
    https://qiita.com/umapyoi/items/7c3e9b42388d576057b1
- 【ML Tech RPT.】第25回 k-center クラスタリングを学ぶ
    https://buildersbox.corp-sansan.com/entry/2021/07/12/110000#fn-1d4d1e66


[^1]: 毎年アメリカで開催され、コンピュータビジョンに関する世界的な学会
[^2]: MVTec Software社が提供する外観検査向けデータセット
[^3]: State-of-the-Artのこと。ある分野のあるスコアで最高の性能を達成していること
[^4]: 主に Resnet18 や WideResnet50×2 が使用されている
[^5]: Greedy アルゴリズムとも言われる
[^6]: 1400万枚以上もあるほど大規模な、カラー写真の教師ラベル付き画像データベース
[^7]: ResNet18 などの深層学習モデルは ImageNet の画像を用いて学習されることが多く、その際に正規化という処理が前処理として使われている