Unity Barracuda による画像分析

Unity Barracuda を理解を深めるため、入力と出力の取り扱い、特に出力のTensorの取り扱い学んだのでそのメモです。

# はじめに
## Unity Barracuda を触ったことがない人はこちら
こちら（[Unity Barracuda に入門しやすいイメージ変換までの流れ](https://qiita.com/matchyy/items/8a242806cfbb2d82c8fe)）で、Unity Baraccuda の紹介、インストールを含め、
とっつきがいいWebCamearaのイメージ変換を紹介しています。

## 作業環境
* Mac M1 Big Sur
* Unity 2020.3.2f1
* Barracuda v2.1.0-preview
* UniTask v2.2.5
* WebCamera （を使っていますが、RenderTexture でも代用できます）

## 対象者
* Unityを触ったことがあり、プログラムを書いて普通に実行できる人。
* UniTask 使っていますが、async/await 知らなくても大丈夫です。メインでは使っていません。
* [Unity Barracuda に入門しやすいイメージ変換までの流れ](https://qiita.com/matchyy/items/8a242806cfbb2d82c8fe)の内容を理解していることを前提としています。

# 入力画像分析
## 1. ONNX モデルを準備します
1. モデルは[こちら](https://github.com/Syn-McJ/TFClassify-Unity-Barracuda/tree/master/Assets/Resources)からダウンロード。
1. Unity にドラッグアンドドロップで取り込みます。

## 2. プログラムの準備
1. GitHubの Scripts 配下に、TFClassify.cs、Converter.cs、WebCamera.cs があるので、それぞれ作成してください。
2. TFClassify.cs が重要なので、一部を抜粋してコメントを入れて説明の代わりとさせてください。
処理的には WebCamera を入力とし、modelAsset に設定したモデルを利用し、得られた結果のトップ3をテキスト表示するだけです。

```C#
    private string[] labels;
    
    void Start()
    {
        // 入力画像を類推した結果、分類させる結果の種類群です（例：goldfish, kite, library など）
        labels = Regex.Split(labelsFile.text, "\n|\r|\r\n")
            .Where(s => !string.IsNullOrEmpty(s))
            .ToArray();
    }

    async void Update()
    {
        var input = await Converter.ProcessImage(webCamera.Texture, IMAGE_SIZE);
        // 類推した結果のトップ3を取り出して、結果表示用に変換
        var results = Predict(input)
            .Take(3)
            .Select(x => $"{x.Key}: {x.Value:0.000}%")
            .ToList();
        // 結果を改行コードで連結
        output.text = string.Join(Environment.NewLine, results);
    }

    private IEnumerable<KeyValuePair<string, float>> Predict(Tensor input)
    {
        // 入力が複数になる場合はDictionaryで指定する。今回は一つのみ。
        var inputs = new Dictionary<string, Tensor> {{"input", input}};
        worker.Execute(inputs);
        // worker.Execute(input); // 入力が一つの場合、どちらでもOK = イメージ変換時

        // アウトプットノードの名前を指定することで、そのノードの結果を取り出すことができる。
        var output = worker.PeekOutput("MobilenetV2/Predictions/Reshape_1");
        // var output = worker.PeekOutput(); // 出力が一つの場合、どちらでもOK = イメージ変換時

        // Debug.Log($"{output.batch}, {output.height}, {output.width}, {output.channels}");
        // 今回の結果の Tensor をデバッグログに出力すると
        // -> 1, 1, 1, 1001
        // という結果になる。
        // channels に類推した結果が詰まっている。

        // Tensorを、類推したラベルと、類推したラベルだと推測される確率の、キーバリューのペアにして返す。
        // 合わせて扱いやすように、確率をパーセント表示用に100倍し、確率の高い順にソート。
        return Enumerable.Range(0, output.channels)
            .Select(i => new KeyValuePair<string, float>(labels[i], output[i] * 100))
            .OrderByDescending(r => r.Value);
    }
}

```

## 3. Hierarchyの設定
* [Unity Barracuda に入門しやすいイメージ変換までの流れ](https://qiita.com/matchyy/items/8a242806cfbb2d82c8fe)と同じようにやっていきます。
* 違いは入力にラベル用のファイル設定があることと、出力がテキストになっていることぐらいです。

## 4. 画像分析〜後は実行するだけ〜
実行して正常に動作すればこのような感じで結果が表示されるはずです。
<img width=540 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/12a2bc88-2983-1e95-47db-6dc869f48493.png" >
ここでは本棚を library、bookcase、comic book と類推しているようです。
概ねあっているのではないかなと思っています。

# その他
## 参考リンク集
1. [Unity Barracuda 入門 / 画像分類](https://note.com/npaka/n/nedd82a5fd9bd)  
今回はこちらのサイトを参考に実施させていただきました。  
Tensor の扱いも[こちら](https://note.com/npaka/n/nf7f76871af8f#sPJz3)で説明があります。