Tensorflow.jsを使ってブラウザで任意の物体検出がしたい2022【Tensorflow2.9.1でSavedModel作成編】

# はじめに
Tensorflow.jsを使って、ブラウザで任意の物体検出がしたい！
見事大ハマリしたので、同じエラーで苦しんでいる人を解決方向に導けたら。
後の自分への備忘録も兼ねて。

# 概要
Tensorflow.jsを使って最終的に有名巨大像(牛久大仏、鎌倉大仏、奈良大仏、高崎観音、自由の女神)をブラウザでリアルタイム検出するモデルを作成した。開発環境はMacBookPro, Anaconda。
その過程を2回に分けて記事にする。
大きくTensorflowのObject Detection APIを使ってモデルを学習する部分と、そのモデルをTensorflow.jsで使えるように変換、ブラウザで読み取れるようにする2ステップだ。
今回は前編の **【Tensorflow2.9.1でSavedModel作成編】** である。
<img width="600" alt="スクリーンショット 2022-07-12 17.17.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616335/4c20089a-dd6a-511e-26f5-e2c1e2e3caf4.png">

# 準備
#### tfrecord形式のアノテーションデータ
まず、画像に物体の位置情報を付与した自作アノテーションデータを用意する。
アノテーションデータを作るにはVoTTが簡単でおすすめ。元画像のサイズはバラバラでも問題ない。
使い方は割愛するが、以下の記事が参考になる。書き出し形式が色々あるのでtfrecord形式を選ぶこと。
指定したフォルダに、大量のJSONと共に`tfrecord`が入ったフォルダが書き出される。

https://qiita.com/IchiLab/items/8ad0cdb9c2006f416c3b

#### Google Colaboratoryが使える環境
今回WindowsでGPUを使おうとしたところ上手くいかなかったので、Google Colaboratory(以下Google Colab)でGPUを使って学習をすることにした。クラウドなのでGoogle Colab自体はWindows/Macどちらも対応。Google DriveにGoogle Colab用のフォルダを作っておく。
Google Colaboratoryの使い方はこちらがおすすめ。

https://aiacademy.jp/media/?p=1037

# 手順
## 0. Google Colaboratoryのファイルをダウンロード
Google Colaboratoryのファイルを作ったのでこちらを元に進めていく。
(以下のGithubには手順を進めやすいようGoogle Colaboratoryのファイルである`Tensorflow_ODA_2022.ipynb`と、これから手順1で作成する`Tensorflow_ODA_DriveDir`ディレクトリのほぼ完成形を入れてある。)

https://github.com/manae-work/Tensorflow_ODA

## 1. Googleドライブ上フォルダを作成
Google Colaboratoryでは自分のGoogleドライブをマウントして中のファイルを読み込める。
そのためまずは、先程作ったアノテーションデータなどの読み取るデータと、後で出力するモデルデータを置くための基準となるフォルダをマイドライブ上に作成する。名前は任意だが`Tensorflow_ODA_DriveDir`としておく。
フォルダ構造は以下の通り。

```
Tensorflow_ODA_DriveDir
├── model
│   └── ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8
├── tfrecord
│   ├── train
│   │   ├── 000000.tfrecord
│   │   └── …
│   └── val
│       ├── 000000.tfrecord
│       └── …
├── trained
├── tf_label_map.pbtxt
└── pipeline.config
```
`Tensorflow_ODA_DriveDir`内を解説する。
- **model**
転移学習に使うモデルを入れるフォルダ。
転移学習とは既にあるモデルを流用することで学習の精度や速度を早める学習のさせ方。
以下からダウンロードする。
[TensorFlow 2 Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md)
モデルは色々あるのだが、今回は`SSD MobileNet V2 FPNLite 640x640`を使う。
<details><summary>ここでの大ハマリ(クリックで展開)</summary>

1. **モデルがダウンロードできない**
そんなまさかと思うが、最初モデルが全くダウンロードできなかった。
何度も、どんなやり方でダウンロードしても、`tar.gz`が壊れる。
しかも、いくら調べても有力な手がかりが得られず。
途方に暮れていたところ、会社のセキュリティにブロックされていたことが発覚。
家のWi-Fiでするっとダウンロードできて涙ちょちょぎれ。。。

2. **EfficientDetを選択**
EfficientDetは優秀なモデルだがここでは選んではいけない。
後編で使うTensorflow.jsはEfficientDetに対応しておらず、ブラウザで読み取る段階でエラーが出てしまう。
これも原因がなかなか分からず苦労させられた。

</details>

- **tfrecord - train / val**
アノテーションデータ(`tfrecord`)を入れるフォルダ。
`train`に学習用、`val`に検証用のデータを入れる。
大体 **`train` : `val` = 7 : 3** くらいになるよう`tfrecord`を分割して入れる。

- **trained**
学習後のチェックポイントデータを保存するフォルダ。今は空。(gitで管理するため.gitkeepは入れてある。)

- **tf_label_map.pbtxt**
アノテーションしたときのタグの名前やIDが書いてあるファイル。
`tfrecord`を生成した場所に一緒に生成されているので`Tensorflow_ODA_DriveDir`直下にそのまま入れる。

- **pipeline.config**
学習に使う設定ファイル。
ダウンロードした`ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8`に入っている`pipeline.config`を書き直して`Tensorflow_ODA_DriveDir`直下に保存する。
以下書き直す箇所。

◎ 3行目
アノテーションのタグ種類数、この記事の場合は5。
```diff:pipeline.config
- num_classes: 90
+ num_classes: 認識させたい物体の種類数
```

◎ 135行目
余裕がありそうなら16くらいでも良いが、自分の場合メモリ足りないエラーが出たので8に減らした。
```diff:pipeline.config
- batch_size: 128
+ batch_size: 8
```

◎ 165行目
転移学習に使うモデルのチェックポイントのパス。
```diff:pipeline.config
- fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED"
+ fine_tune_checkpoint: "/content/drive/MyDrive/Tensorflow_ODA_DriveDir/model/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8/checkpoint/ckpt-0"
```

◎ 171行目
画像認識から物体検出モードに。
```diff:pipeline.config
- fine_tune_checkpoint_type: "classification"
+ fine_tune_checkpoint_type: "detection"
```

◎ 175, 185行目
`tf_label_map.pbtxt`のパス。
```diff:pipeline.config
- label_map_path: "PATH_TO_BE_CONFIGURED"
+ label_map_path: "/content/drive/MyDrive/Tensorflow_ODA_DriveDir/tfrecord/tf_label_map.pbtxt"
```

◎ 177行目
学習用tfrecordの場所。?はワイルドカードで、`??????.tfrecord`とすることですべての`数字6桁.tfrecord`ファイルを読み込める。
```diff:pipeline.config
- input_path: "PATH_TO_BE_CONFIGURED"
+ input_path: "/content/drive/MyDrive/Tensorflow_ODA_DriveDir/tfrecord/train/??????.tfrecord"
```

◎ 189行目
検証用tfrecordの場所。?は上と同じ。
```diff:pipeline.config
- input_path: "PATH_TO_BE_CONFIGURED"
+ input_path: "/content/drive/MyDrive/Tensorflow_ODA_DriveDir/tfrecord/val/??????.tfrecord"
```

長くなってしまったが、これで基準となるフォルダは完成した。

## 2. Google Colaboratory上で学習
学習の流れはColaboratory上に書いたため、上記のフォルダを作成した後手順0.でダウンロードしたipynbを順に実行していけば無事にSavedModelが作成できると思う。
そこで、ここでは私がつまづいたポイントをメインに解説していく。
気になる人はGoogle Colaboratoryと見比べながら読んでほしい。

### Protobufのバージョン
TensorflowではProtocol Buffers(以下Protobuf)というフォーマットが使われている。そのため、Protobufライブラリをダウンロードしてコンパイルする必要がある。
[チュートリアル](https://tensorflow-object-detection-api-tutorial.readthedocs.io/en/latest/install.html)では最新バージョンをダウンロードするよう指南されているが、最新バージョンをダウンロードすると途中`model_builder_tf2_test.py`でインストールが上手く行ったか確認する際以下のようなエラーが出る。
```
ImportError: cannot import name 'builder' from 'google.protobuf.internal' (/usr/local/lib/python3.7/dist-packages/google/protobuf/internal/__init__.py)
```
これは**Object Detection APIに必要なパッケージのインストールをsetup.pyでする際にv3.19.4のProtobufがインストールされてしまう**ため「先にコンパイルした最新のProtobuf」と「setup.pyでダウンロードされたv3.19.4のProtobuf」のバージョンの齟齬でエラーが発生している。そのため、今回は最初から**v3.19.4をインストール・コンパイル**することで対処することにした。

### Tensorflow2.9に合ったCuda, cuDNNのバージョン
恐らくここが1番のハマりポイントである。
Protobufと同じく、setup.pyを動かすと**Tensorflowの最新バージョン(2022年7月現在：2.9.1)が上書きインストールされてしまう。** そのまま学習に進むと、`DNN library is not found.`のエラーが発生する。
これはGPUを使うためのCuda、cuDNNのバージョンがTensorflow2.9と噛み合ってないため起こる。
CudaとcuDNNは既にGoogle Colaboratoryにインストールされているのだが、以下の表の通りcuDNNのバージョンがTensorflow2.9の動作条件に足りていない。
|                     | Cuda      | cuDNN                           |
|:-------------------:|:---------:|:-------------------------------:|
| Google Colaboratory | 11.1      | 8.05                            |
| Tensorflow2.9       | 11.0~11.7 | <font color="Red">8.1以上</font> |

Tensorflow2.7にバージョンを下げることでも動くようになるが、今回はCudaとcuDNNのバージョンを上げることでTensorflow2.9を使えるように対処することにした。

### opencv-pythonのエラー
```
AttributeError: module 'cv2' has no attribute 'gapi_wip_gst_GStreamerPipeline'
```
という謎エラー。opencv-pythonをアップグレードすれば解決。

これらを解決するのにかなりの時間を費やしてしまった。

## 3. SavedModelの保存
学習を終えたらSavedModelを保存する。
Google Colaboratoryを全て実行すると、手順1.で作ったフォルダの中に`output`というフォルダが新たにできている。
その中の`saved_model`フォルダを次の記事で使うので、ローカルにダウンロードしておく。

ちなみに`saved_model`自体はローカルアプリでならそのまま使える。

# まとめ
Tensorflow.jsでブラウザを使う前に、TensorflowのObject Detection APIがかなりの曲者だった。
元々インストールしていたTensorflowが途中で最新版に塗り替えられていることに気づいた時は目眩がした。その後もGPU関連で首を絞められるような思いをしつつも、何とか自作モデルを完成させられたのでバンザイ！！！
次はWindowsデスクトップでGPU使って学習できるといいな…
後編もTensorflow.jsで血を吐きます。お楽しみに。

→ [後編はこちら](https://qiita.com/manae/items/73ad91884f91e81157d0)

# 参考記事・サイト
この他にも数多くの記事やサイトを参考にしましたが、主に参考にした代表的なものを以下に紹介します。
過去の知恵には頭が上がりません。
### Google Colaboratoryのベース
まず最初に、この記事がなければGPUを使用しての学習は諦めていたと思います。
MITライセンスで配布されていたものをベースに今回のGoogle Colabファイルを作らせていただきました。
ライセンスにも記載しております。この場をもって感謝申し上げます。ありがとうございました。

・[【Tensorflow2 Object Detection API】オリジナルデータ学習ハンズオンのColab公開](https://zenn.dev/kazuhito/articles/7f4391167968a9)

### Object Detection API全般
・ [TensorFlow 2 Object Detection API tutorial](https://tensorflow-object-detection-api-tutorial.readthedocs.io/en/latest/index.html)
・ [「Object Detection API」で物体検出の自前データを学習する方法（TensorFlow 2.x版)](https://qiita.com/karaage0703/items/8567cc192e151bac3e50#%E5%AD%A6%E7%BF%92)

### Protobuf周り
・ [ImportError: cannot import name 'builder' from 'google.protobuf.internal'](https://stackoverflow.com/questions/71759248/importerror-cannot-import-name-builder-from-google-protobuf-internal)

### Tensorflow2.9 と Cuda, cuDNN周り
・ [Trained failed when training SSD mobilenet on colab GPU](https://github.com/tensorflow/models/issues/10495)
・ [DNN library is not found ssd_mobile_net_v2 in Colab](https://stackoverflow.com/questions/70998639/dnn-library-is-not-found-ssd-mobile-net-v2-in-colab#answer-72404540)
・ [TensorFlow 2.9.1 （GPU 対応可能）のインストール（Windows 上）](https://www.kkaneko.jp/tools/win/tensorflow2.html#S2)
・ [NVIDIA ドライバ，NVIDIA CUDA ツールキット 11.5，NVIDIA cuDNN 8 のインストール（Ubuntu 上）](https://www.kkaneko.jp/tools/ubuntu/ubuntu_cudnn.html)
・ [NVIDIA CUDNN DOCUMENTATION](https://docs.nvidia.com/deeplearning/cudnn/support-matrix/index.html)
・ [Index of /compute/cuda/repos/ubuntu2004/x86_64](https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/)
・ [Installing Tensorflow 2.9 with GPU Support](https://spltech.co.uk/installing-tensorflow-2-9-with-gpu-support/)
・ [google colaboratory の cuda version, cudnn version](https://qiita.com/ikeyasu/items/cdae3562fffdca235d69)

### opencv-python周り
・ [AttributeError: partially initialized module 'cv2' has no attribute 'gapi_wip_gst_GStreamerPipeline' (most likely due to a circular import)](https://stackoverflow.com/questions/72706073/attributeerror-partially-initialized-module-cv2-has-no-attribute-gapi-wip-gs)