ONNX体験

# 概要
とある事情でONNX（Open Neural Network eXchange）を勉強することになったので、Windowsで環境を作って画像分類をするところまでを一通り体験。
いろいろなサイトを参考にしたので、ポチポチできるだけの内容にまとめてメモ。

# 実践
## 環境
* Windows（WSL2）
* Ubuntu 22.04
* VSCode

環境構築は下記のサイトを参考に実施
https://qiita.com/zaburo/items/27b5b819fae2bde97a3b

Ubuntu実行時に下記のようなエラーが出たらカーネル更新が必要
```
WslRegisterDistribution failed with error: 0x800701bc
```
方法はいくつかあるが、私は`wsl.exe --update`をたたくと動くようになった
参考: https://hachimoto12.com/wsl2_error_0x800701bc.html

## 事前準備
ONNXは事前に学習済みモデルをハードウェア等に関係ない状態で定義されており、実行環境に合わせたチューニングが不要。
また公開されているモデルもあり、推論だけであれば自身で学習せずにインターネットから取得するだけで済む。

### モデル
今回はVGGモデルを使ってみる。
モデルはGithubに公開されているので、ダウンロード。
```bash
$ mkdir -p models
$ wget -P models https://github.com/onnx/models/raw/main/vision/classification/vgg/model/vgg19-7.onnx
```

ほかにも様々なモデルが公開されているので、試してもよいかも
https://github.com/onnx/models/tree/main/vision/classification

### Python環境
ONNXとOpenCVを動かすためのパッケージをインストール
```bash
# pip3が入っていない場合
$ sudo apt update && sudo apt install -y python3-pip

$ pip3 install onnxruntime opencv-python
```

OpenCVを動かす際に下記のエラーが出る場合があるので、事前に確認。
```bash
$ python3 -c "import cv2"
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
```

もしエラーが出たら必要なパッケージをインストール。
```bash
$ sudo apt update && sudo apt install -y libgl1-mesa-dev
or
$ sudo apt update && sudo apt install -y libglib2.0-0 libsm6 libxrender1 libxext6
```

## 実践

まず、Pythonを起動してONNXのセッションを作成。
今回の環境ではGPUを積んでいないため、CPUを指定している。
```python
>>> import onnxruntime
>>> import numpy as np
>>> onnx_model_path = "model/vgg19-7.onnx"
>>> sess = onnxruntime.InferenceSession(onnx_model_path, providers=['CPUExecutionProvider'])
# GPU(CUDA)を使う場合
>>> sess = onnxruntime.InferenceSession(onnx_model_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
```

使用しているモデルの入力と出力の情報を確認。
```python
>>> input_name = sess.get_inputs()[0].name
>>> input_shape = sess.get_inputs()[0].shape
>>> output_name = sess.get_outputs()[0].name
>>> output_shape = sess.get_outputs()[0].shape
>>> print("Input name  :", input_name)
Input name  : data
>>> print("Input shape :", input_shape)
Input shape : [1, 3, 224, 224]
>>> print("Output name  :", output_name)  
Output name  : vgg0_dense2_fwd
>>> print("Output shape :", output_shape)
Output shape : [1, 1000]
```

次に推論するための画像を読み込む。
事前に実行環境に準備する必要があるが、VSCodeの場合はドラッグ&ドロップで転送できる。
```python
>>> import cv2
>>> image = cv2.cvtColor(cv2.imread(filename="penguin.jpg"), code=cv2.COLOR_BGR2RGB)
```

今回使う画像はこちら
（正面向いてないけど大丈夫かな。。。？）
![penguin.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342406/327bd642-c9eb-de7d-050a-d61304f4896e.jpeg)

入力画像をモデルの形に合わせてリサイズ
```python
>>> resized_image = cv2.resize(src=image, dsize=(224, 224))
>>> input_data = np.expand_dims(np.transpose(resized_image, (2, 0, 1)), 0).astype(np.float32)
```

推論実行!
```Python
>>> out = sess.run([output_name], {input_name: input_data})
>>> result_index = np.argmax(out[0])
>>> print("Index :", result_index)
Index : 145
```

推論結果はインデックスで取れるため、何に当てはまるか確認。
まずインデックス一覧のファイルをダウンロード。
```bash
wget https://raw.githubusercontent.com/onnx/models/main/vision/classification/synset.txt
```

取得したファイルから推論結果を取得する。
```python
>>> imagenet_classes = open("synset.txt").read().splitlines()
>>> imagenet_classes[result_index]
'n02056570 king penguin, Aptenodytes patagonica'
```
おーペンギンと認識してくれた。

# まとめ
学習済みのONNXモデルを用意できれば、時間とコストのかかる学習を行わずに気軽に推論を使うことができた。
ほかのモデルも公開されているので、そちらを使ったりPyTouchなどのモデルからONNXに変換することもできるので試してみたい。