Unity Sentisを使ってYolov5を動かしてみる

Unity SentisでONNXを使ってUnity上で各種AIモデルを実行できるという話が出ましたね。
最近ONNXというAIモデル形式を知ったばかりのAI超初心者が、どうにかこうにかUnity上で物体認識モデルYOLOv5を動かそうと奮闘したので、その記録をここに残します。

# 時間ない人向けまとめ
* YOLOv5の学習済みモデルがぱっと見配布されてなかった
* GoogleColaboratoryでYOLOv5読み込み・学習済みの重み情報を読み込ませて、ONNXへエクスポート
* 生成されたONNXファイルをダウンロードしてSentisで読み込んで使った

# 全体の経緯
## まずUnityにSentisを導入
PackageManagerからSentisを導入。
こちらは公式のSentisページに詳しいやり方が載ってます。
https://discussions.unity.com/t/about-sentis-beta/260899

## そもそもONNXってどこから取って来れば？
Model Zooというのがあるらしい、なるほど・・・
[ONNX MODEL](https://github.com/onnx/models) とか
[PINTO MODEL ZOO](https://github.com/PINTO0309/PINTO_model_zoo) とか
から取って来ればいいんだな？
Yolov3とかはやってみたの見たことあるから、v5を使ってみよう。
v5はPINTO MODEL ZOOから取って来ればいいんだな（←後の悲劇フラグ）

## PINTO MODEL ZOOから取ってきたけど・・・
download.shを実行したらそれらしき.onnxファイルが出てきた。これを使えばいいのかな・・・？ とmodel_float32.onnxファイルをUnityにドラッグ＆ドロップ。
一応Sentisで読み込めるように変換できた模様。
Outputsが(?,?,?)になってるけどエラー出てないし多分大丈夫・・・

## モデルの情報を調べる
Netronというソフトを使えば、onnxモデルのInputとOutputの形式を見ることができる。
内部で使ってるオペレーターも見れるけどよくわからない初心者なので飛ばす。
https://developer.mamezou-tech.com/blogs/2023/02/06/ml-model-visualizer-netron/

input が`(1,640,640,3)`
output が`(1,25200,85)`、名前が`tf.identity`
であることを確認

## Sentisで実行するようにソースコード実装
とりあえず、「入力したら出力される」が確認したいので、640*640の画像をSentisのTensor変換してモデルに読み込みさせる。
※ modelをLoadしてengineで実行できる形にする部分は省略
```
// 3はチャンネル数
inputTensor = TextureConverter.toTensor(texture2dData, width, height, 3);

// モデルに入力する
engine.Execute(inputTensor);

// 出力を取得する
var output = engine.PeekOutput("tf.identity");
output.MakeReadable(); // アウトプット情報をCPUで使うために必要らしい
Debug.Log("output.Shape: " + output.shape); // 出力されたデータ形式を見る
```
これで`output.Shape: (1,25200,85)`のログが出力されたので、モデルはちゃんと動いていそう

## この出力データをどうすれば・・・？
そもそもYoloが物体検出できるってことだけ知ってた初心者。
出力された値をどうこねくり回せば画像内の物体検出結果をいいかんじの形にできるのかわからず。
ここからYolo関係の記事を探す旅に・・・

## YOLOの使い方を知る旅から帰還
色々記事見ましたが、使い方とかアウトプットをどう解釈すれば良いのかとかは、使ってみたDemoコードを読み解くのが一番早かった気がします。（もっといい学習先があれば教えてほしい）

旅から戻った初心者の理解としては、出力された85がキモ部分。
0~3までの数値はバウンディングボックスのx, y, width, heightの四角形情報
4はその四角形がどの程度信頼できるかの0~1の値
5~85はYoloが分類できる80のクラス（人とか車とか犬とか猫）それぞれの確率

ここからより確かと思われる信頼度が高い四角形の、より確率が高いクラスを取ってくることで「この範囲はこの物体だ」という認識になってる、はず。

だから25200個分の85個の各数値を取ってきて、ある程度信頼できる四角形情報だけ残してその四角形の高い確率のクラスを取ってきて表示すればいい。

なお、80個の分類クラス内訳はYolov5公式Githubの`data/coco.yaml` にあったのでこれのはず。
[YOLOv5公式Github](https://github.com/ultralytics/yolov5)

## 改めて信頼度0.5以上の情報リストアップ
outputデータを数値見れる形にして・・・
信頼度（添字4の数値）が0.5以上だったらBOXクラス生成してリストに入れて・・・
```
var outputData = output as TensorFloat;
var length = outputData.shape[1];
var list = new List<BOX>();

for(var i = 0; i < length; i++) {
    var objectConfidence = outputData[0, i, 4];
    if (objectConfidence > 0.5f) {
        // BOXは自分で定義したクラス
        list.Add(new BOX(outputData, i));
    }
}
```
結果：ゼロ。
えっそんな馬鹿な・・・数値の解釈間違った？信頼度0.5だと高すぎた？
と信頼度部分の数値だけログに出してみたけど、軒並み0.00001以下ということが判明。
なぜ・・・

## 学習済みモデルじゃなかったことが判明
どおりで・・・PINTO MODEL ZOOでYOLO取ってきてから改めて自分で学習するような記事しか見つからないと思った・・・どおりで・・・
学習済みモデルは？ 重みファイル.ptだけ公式で配布してる？ そっかぁ・・・

なんと、最初に調べてたMODEL ZOO で、ONNX MODEL ZOO の方は学習済みモデルを配布してたことを後に知って２度がっかり

## 学習済みONNXモデルを作る
我らの強い味方・GoogleColaboratoryさんにかかれば、YOLOv5の環境を構築して重みファイル読み込み・ONNXファイル作成までお手のものなのだ・・・！

コードセルに以下記載するとYOLOv5の環境構築ができちゃいます。
```
!git clone https://github.com/ultralytics/yolov5

%cd yolov5
!pip install -r requirements.txt
```
参考：

https://qiita.com/shinya_sun_sun/items/61205f83ea4873c0993d

重みファイル.ptは公式のYOLOv5GithubページからDLしてきて、Colaboのファイルスペースにアップロードしておく。
exportの仕方はexport.py内にコメントで記載されてるので、それに従って以下コマンドを実行
```
!python export.py --weights yolov5s.pt --include onnx
```
yolov5sのところは使いたい重みに合わせる。
これでonnxファイルが生成されているので、ファイルスペースから右クリックDLすれば学習済みONNXが手に入った！

## Unityに持ってきてSentisで読み込む
outputの名前が変わってるのでそこだけ気をつければ、きちんと信頼できる検出結果が取ってこれます！
あとは結果をもとに、認識範囲に合わせたボックスを生成するなりすればpythonで実行したときと同じような検出結果が出せるし、WebCameraと連携すればリアルタイム検出も可能です。

## まとめ
SentisはONNXモデルを使える便利なツールですが、そもそもモデル自体への理解がないとちゃんと使えないので、使いたいモデルがあったらどう使えば良いか勉強してから導入しましょう！
あと、GoogleColaboratory使えば簡単に動作環境作ってONNXモデル生成までできたの感動したのでおすすめです！