【Unity】Barracudaで物体検出してみる


# はじめに
物体検出の記事をこれまで書いてきましたが、最近Unityを触る機会があったので、Unityで物体検出する方法を調べて、まとめてみました。
本記事では、自前のdarknet形式のYOLOv3-tinyモデルを使ったUnity上での物体検出を行います。

以下の順序で記載します。
1. [Barracudaの導入](#barracudaの導入)
1. [YOLOv3-tiny用の実装](#yolov3-tiny用の実装)
2. [darknet形式のYOLOv3-tinyモデルからONNX形式に変換](#darknet形式のyolov3-tinyモデルからonnx形式に変換)
3. [自作のYOLOv3-tinyモデルを使って物体検出](#自作のyolov3-tinyモデルを使って物体検出)

# 開発環境
* Unity Editor：2021.3.9f1
* Barracuda：Version 3.0.0


# Barracudaの導入
[Barracuda](https://docs.unity3d.com/Packages/com.unity.barracuda@3.0/manual/index.html)はUnityの公式パッケージとして提供されているニューラルネットワーク推論ライブラリです。GPUとCPUの両方で推論をサポートしています。

Unity Editorでプロジェクトを起動し、以下の手順でパッケージを導入します。
1. Window - Package Manager
2. +マーク - Add package from git URL
3. `https://github.com/Unity-Technologies/barracuda-release.git` と入力して、Add

2022年11月現在、Version 3.0.0が追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/990690c4-3489-076c-4363-069dda4b86a8.png)


# YOLOv3-tiny用の実装
YOLOv3-tinyモデルの検出結果を元に描画する実装を一から行うのは大変なので、[Unity-Technologies/barracuda-starter-kit](https://github.com/Unity-Technologies/barracuda-starter-kit) の中に含まれてるYOLOv3-tinyの実装を流用します。

barracuda-starter-kitには3つのサンプルが含まれています。
* 01-StaticImageRecognition-MobileNetV2：MobileNet
* 02-FaceTracking-BlazeFace：MediaPipe BlazeFace model
* 03-MultiObjectStaticImageRecognition-YoloV3Tiny ： YOLOv3-tiny

まずは動作を確認してみましょう。
`03-MultiObjectStaticImageRecognition-YoloV3Tiny`のSceneを選択し、再生すると以下のような画面が表示されます。公式の説明はありませんが、COCOデータセット(クラス数は80)の検出モデルが搭載されているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/ad64849d-aade-8eff-e8aa-248ec3bfd201.png)

:::note info
左下のBackendでは、Workerの種別を指定できます。[Worker](https://docs.unity3d.com/Packages/com.unity.barracuda@3.0/manual/Worker.html)はモデルを実行可能なタスクに分解し、GPUまたはCPUにスケジュールします。
* CSharpBurst：Burstでコンパイルされた高効率なジョブ化・並列化されたCPUコード
* ComputePrecompiled：すべてのオーバーヘッドコードが取り除かれ、ワーカーにプリコンパイルされた高効率なGPUコード
* PixelShader：Pixel Shaderの実装
::: 

今回は、barracuda-starter-kitの`03-MultiObjectStaticImageRecognition-YoloV3Tiny`以下をUIも含めて自分で作成したプロジェクトにコピーして使用しました。
※ 画像の選択部分やBackendのプルダウンのUIが一部壊れるので手動で修正が必要になります。



# darknet形式のYOLOv3-tinyモデルからONNX形式に変換
Barracudaは[ONNX(Open Neural Network Exchange)](https://onnx.ai/)形式の学習モデルをサポートしています。TensorflowやPytorchなどで作成した学習モデルは変換が必要になります。

darknet形式からONNX形式への変換方法は、Kerasを経由したり、tensorflowを経由したり、複数の方法がありそうなのですが私の環境では失敗する方法が多かったです。
いろいろ試した結果、[tensorrt_demos](https://github.com/jkjung-avt/tensorrt_demos/tree/3fb15c908b155d5edc1bf098c6b8c31886cd8e8d) を利用することで成功しました。ONNXのVer1.4.1を利用する古いバージョンを使用しています。ONNXのVer1.9.0を利用する最新版では、変換には成功しますが、Unityのアタッチで警告が出ました。

## 準備
Python3.6で環境を構築し、必要なライブラリを入れます。
``` bash
cd tensorrt_demos/yolo/
pip install -r requirements.txt
```

## 変換手順
まずは、正常に変換できることの確認するために、[darknetの作者のサイト：YOLO: Real-Time Object Detection](https://pjreddie.com/darknet/yolo/)で配布されているCOCOデータセット(クラス数は80)の学習済みモデルの重みとconfigファイルを用いて、ONNX形式のモデルへの変換を試します。

1. 学習済みモデルの重みのファイル名を「yolov3-tiny-[学習時の画像サイズ].weight」に変更する。
1. configファイルのファイル名を「yolov3-tiny-[学習時の画像サイズ].cfg」に変更する。
2. tensorrt_demos/yolo/に、重みとconfigファイルを配置する
3. `python yolo_to_onnx.py --model yolov3-tiny-[学習時の画像サイズ]`

これでONNX形式のモデルが作成できました。

## Unityへの取り込み
1. ONNX形式ファイルのUnityへの取り込みはProjectウィンドウにDrag&Dropするだけです。正常に取り込めた場合は、以下のような表示になります。
![スクリーンショット 2022-10-15 Coco_onnx1.4.1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/0f1a5bc8-67e1-dad9-303b-227f205c8301.jpeg)
モデルに異常がある場合は、以下のようにWarningやErrorが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/4c129758-2f06-2113-d2c8-192a9eae523f.png)
2. Hierarchyウィンドウで「Controler」を選択し、Inspectorウィンドウの「Src Model」に取り込んだONNX形式のファイルをタッチします。
![スクリーンショット 2022-10-15 Cocoモデル.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/fe9c5be2-f310-5629-f1a0-690d1637bc6e.jpeg)
3. 再生します。
![スクリーンショット 2022-10-15 Coco.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/8548b64c-2a79-fdab-d263-915a04391ab8.jpeg)
モデル置き換え前と同様に検出できているので、正常に変換できていることが分かりました。


# 自作のYOLOv3-tinyモデルを使って物体検出
動作確認ができたので、自作のYOLOv3-tinyモデルを使って、物体検出しています。
yolov3-tinyの自作モデルは過去に [https://qiita.com/SwitchBlade/items/11253433bbf6c34772ac](https://qiita.com/SwitchBlade/items/11253433bbf6c34772ac)で作成した飼い猫の検出モデルを用います。クラス数は4のモデルになります。

ONNX形式への変換手順は先ほどと同様です。

## Unityへの取り込み
1. ONNX形式ファイルをProjectウィンドウにDrag&Dropします。
2. タグ名称が記載されたテキストファイル(darknetモデルの作成時に「obj.names」のようなファイル名で作ったものです)をProjectウィンドウにDrag&Dropします。
3. Hierarchyウィンドウで「Controler」を選択し、Inspectorウィンドウの「Src Model」に取り込んだONNX形式のファイルをタッチします。また、「Labels Asset」にタグ名称が記載されたテキストファイルをアタッチします。
![スクリーンショット 2022-10-15 101409.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/a5787cf3-d99a-c248-6058-1a3ddbc9a4ae.jpeg)
4. クラス数が変わるので一部ソースの修正が必要になります。
``` RunInferenceYOLO.cs
private const int amountOfClasses = 4;
//private const int amountOfClasses = 80;

private const int anchorBatchSize = amountOfClasses + 5;
//private const int anchorBatchSize = 85;
```

## 動作確認
検出対象の画像をUnityに取り込みます。
`RunInferenceYOLO.cs`で640×640の画像以外はエラーになるようにしているので、画像のサイズを変更するか、ソースを修正する必要があります。今回は、画像のサイズを640×640に変換して使用しました。

1. 画像ファイルをProjectウィンドウにDrag&Dropします。
2. 取り込んだ画像を選択し、Inspectorウィンドウで[Non-Power of 2]を[None]に変更します。また、[Format]を[RGBA 32bit]に変更します。
3. Hierarchyウィンドウで「Controler」を選択し、Inspectorウィンドウの[Input Image]のいずれかのElementの画像と置き換えます。
4. 再生します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/6c266a13-f94b-8c2b-0acc-7cf17f747c8b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/e694f94b-385e-e740-e573-1fa0780b7654.png)
自前の検出モデルで検出できました！！

# まとめ
今回はUnityを使って自前モデルで物体検出することができました。
今後は物体検出とARと組み合わせたアプリの作成や、物体検出以外のモデルの利用も試してみたいと思います。

# 参考にしたサイト
[Unity Learning Materials/Unity Barracudaを使用したONNXニューラルネットワークモデルのマルチプラットフォーム運用](https://learning.unity3d.jp/7557/)
[Unityの推論エンジン『Barracuda』を試してみたのでそのメモ](https://edom18.hateblo.jp/entry/2020/07/25/094400)
[Barracuda 1.0.0 - Unity用の軽量でクロスプラットフォームなニューラルネットワーク推論ライブラリ](https://note.com/npaka/n/nbb37d193da7f)
[ONNX変換・確認ライブラリ、アプリケーションまとめ](https://qiita.com/tfukumori/items/3f438294e722bc29fe16#213-%E5%AF%BE%E5%BF%9C%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF)