Unity x 機械学習 で遊んでみた【Unity Barracuda】

![anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/052a65f2-7c33-a4dc-0a31-531e8b1453f2.gif)

⊂二二二（ ＾ω＾）二⊃ﾌﾞｰﾝ


この記事ではBarracudaについてざっくり理解してから、上のように遊んでみます
Unityはある程度触れるけどBarracudaは知らない人向けの内容です

# Barracudaとは

- Unityの機械学習パッケージの1つ
    - Gitリポジトリ : https://github.com/Unity-Technologies/barracuda-release
    - 公式ドキュメント : https://docs.unity3d.com/Packages/com.unity.barracuda@latest
- Unity上で動作するONNXランタイム
    - ONNX : 機械学習モデルの共通ファイルフォーマット `.onnx`
        - 生成した学習モデルをONNX形式で出力することで、様々なランタイムで使いまわせる
        - Homeページ : https://onnx.ai/
- 機械学習のプロセスでいう「推論」の部分を担う (下図参照)

![image_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/2bd3fe08-c30d-c7cc-2c87-2942664754a3.png)

- NETORONに対応しており、UnityにインポートしたONNXファイルをダブルクリックするとNETORONで開ける
    - NETORON : ニューラルネットの視覚化ツール
        - Gitリポジトリ : https://github.com/lutzroeder/netron

# Hello World

こちらを利用して、MNISTによる手書き数字の推論をやってみる
https://github.com/keijiro/MnistBarracuda

![image_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/b755b187-9c6c-7c35-6f5f-c97a1a3a6f37.png)

## MNISTとは

- 手書き数字画像の大規模なデータベース
- 手書き数字がどの数字か推論するための学習モデルとして使える
- Wikipedia : https://ja.wikipedia.org/wiki/MNIST

## 手順

1. クローンする `$git clone git@github.com:keijiro/MnistBarracuda.git`
2. Unityで開く
3. MnistTestシーンを開く `Assets/MnistTest.unity`
4. シーンを実行すると、推論結果が表示される

<img width="600" alt="image_03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/b4f937c0-17bb-b954-20af-fbbf81bac8c8.png">

## コードを読む

https://github.com/keijiro/MnistBarracuda/blob/main/Assets/Script/MnistTest.cs

```cs
        // Convert the input image into a 1x28x28x1 tensor.
        using var input = new Tensor(1, 28, 28, 1);

        for (var y = 0; y < 28; y++)
        {
            for (var x = 0; x < 28; x++)
            {
                var tx = x * _image.width  / 28;
                var ty = y * _image.height / 28;
                input[0, 27 - y, x, 0] = _image.GetPixel(tx, ty).grayscale;
            }
        }
```

- 入力画像`_image`をテンソル`Tensor`に変換する処理
    - テンソル = 多次元配列
- 機械学習の分野ではy軸方向が上から下、UnityのUV座標のV軸方向は下から上なので、yを反転させている
- モデルに入力するテンソルの形式はONNXファイルのインスペクタで確認可能 (下の画像参照)
    - n:バッチサイズ, h:高さ, w:幅, c:チャネル数
    - チャネル数1 = グレースケール、チャネル数3 = RGB画像

<img width="600" alt="image_04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/37e0ea09-b9d5-a2db-63a0-9379382fe3f1.png">

```cs
        // Run the MNIST model.
        using var worker =
          ModelLoader.Load(_model).CreateWorker(WorkerFactory.Device.CPU);

        worker.Execute(input);
```

- MNISTの学習モデルを元に推論を行う
- `worker`は`IDisposable`
- `CreateWorker`メソッドの第一引数でCPU/GPU実行を指定可能　ここではCPUを指定

```cs
        // Inspect the output tensor.
        var output = worker.PeekOutput();

        var scores = Enumerable.Range(0, 10).
                     Select(i => output[0, 0, 0, i]).SoftMax().ToArray();
```

- `worker`から推論結果`output`を取得し、0~9のそれぞれのスコアを配列`scores`に格納
- `SoftMax()`メソッドは受け取ったIEnumerableの全要素を0~1に正規化した値に変換

```SoftMax.cs
    public static IEnumerable<float> SoftMax(this IEnumerable<float> source)
    {
        var exp = source.Select(x => Mathf.Exp(x)).ToArray();
        var sum = exp.Sum();
        return exp.Select(x => x / sum);
    }
```

## 補足など

- Mnistのテンソルは(1,28,28,1)と小さいためCPU側で作成しても問題ないが、基本的にはComputeShaderを利用してGPU側で処理した方がいい
- BarracudaとONNXファイルを利用すれば、ユーザーがするのはモデルの入出力データの整形くらい
    - ONNXファイルはいろいろなものが公開されてるので探してみるといい
        - "model zoo" で検索するとたくさんでてくる
    - 実際にはそのままでは動作させられないONNXの方が多いらしい
        - Barracuda未対応の機能を利用している場合、ONNXの修正で動作させられる場合もある

# 遊んでみた

Barracudaについてざっくり理解したので、次は遊んでみます
こちらを利用 (またしてもkeijiroさん)
https://github.com/keijiro/UltraFaceBarracuda

## 手順

1. クローンする `$git clone git@github.com:keijiro/UltraFaceBarracuda.git`
2. Unityで開く
3. [こちら](https://4.bp.blogspot.com/-39SnY6czv-g/VaMOH6tlptI/AAAAAAAAviY/Zhta2pOuE5Y/s800/boy_07.png)の画像をDLしてプロジェクトにimport
4. `Assets/Marker/SunglassesMarker.prefab`を複製し、パラメータを変更  
   RectTransform > Scale : (2,2,2)  
   RawImage > Texture : 先ほどimportした画像
5. Visualizerシーンを開く `Assets/Visualizer.unity`
6. シーン上の`Webcam Test`オブジェクトを有効にし、パラメータを変更  
   Visualizer > Marker Prefab : 先ほど作成したPrefab  
   WebcamInput > DeviceName : 右の`Select`を押し、プルダウンから入力デバイスを選択
7. シーンを実行

![anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269069/052a65f2-7c33-a4dc-0a31-531e8b1453f2.gif)

## 補足など

- 先ほどの後Mnistと異なり、UltraFaceBarracudaではGPU(ComputeShader)でモデルの入出力データを整形している
    - ONNXファイル : `Packages/UltraFace/ONNX`
    - ComputeShader : `Packages/UltraFace/Shader`
- 各クラスについてざっくり
    - Visualizer : FaceDetectorの作成/更新
    - FaceDetector : ComputeBufferの作成/破棄、モデル実行と前後処理のコンピュートシェーダ実行
    - ResourceSet : FaceDetectorで実行するONNXファイルと、その入出力データに対して実行するGPU処理(コンピュートシェーダ)の参照を持つ
    - Detection : UltraFaceによる推論結果情報をもつ構造体
    - SunglassesUpdater : Detectionに応じた位置にサングラスを表示する更新処理

# 最後に

簡単に Barracuda x 機械学習 で遊ぶことができました
機械学習を知らなくてもゲームは作れるし、なんとなく難しそうだったので敬遠してきましたが、今回触れてみて一気に身近に感じるようになりました
ゲーム x 機械学習 でどんなことができるか、考えてみようと思います

# 参考

keijiroさんによるBarracuda紹介動画
ぶっちゃけこの動画見た方がはy (ry

- [Unity Barracudaを使用したONNXニューラルネットワークモデルのマルチプラットフォーム運用 - CEDEC2021](https://www.youtube.com/watch?v=dMgm4ZYfaUI)
- [ニューラルネットで何でもできちゃう！？Unity Barracudaで遊ぼう！] (https://learning.unity3d.jp/6932/)