TensorflowをTHETA Vで動作させる

# はじめに
TensorflowにはAndroidで動作する[サンプル](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android)があり、以下のようなサイトでも紹介されている。
https://qiita.com/icchi_h/items/a1df9f27569714edfc5e
ここでは、Androidが搭載されている全天球カメラTHETA VでTensorflowを動作させる。

(2018/8/18: 前後編に分かれていた記事を統合して追記。[こちら](https://www.slideshare.net/RICOHTHETAPluginDevloperCommunity/ricoh-theta-120818517)に2018/10/20に行われたイベントでの解説資料(PPT)があります。)

# 開発環境
* RICOH THETA V, firmware ver.2.40.2 (Android 7.1.1)
* Moto G5 Plus, Android 7.0
* Android Studio 3.1.4
* Build #AI-173.4907809, built on July 24, 2018
* JRE: 1.8.0_152-release-1024-b01 x86_64
* JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
* Mac OS X 10.13.6

# 手順
## ソースコード取得(Tensorflow, PluginSDK)

githubから[tensorflow](https://github.com/tensorflow/tensorflow)を取得して、ソースコードを準備する。ここでは、
https://github.com/tensorflow/tensorflow （コミット：26353f9b51091312e7097143aee9c2d05e2011fd）をforkしたものを、https://github.com/mktshhr/tensorflow-theta に置き、THETA V向けに更新した。タグ"Qiita20180818"が本記事に対応したソースコードである。中でも https://github.com/mktshhr/tensorflow-theta/tree/Qiita20180818/tensorflow/examples/android が変更範囲となる。
ソースコードを取得した上で、Android Studioで /tensorflow-theta/examples/android を開いて試すことができる。以下で説明する変更は既に適用されている。

```
git clone https://github.com/mktshhr/tensorflow-theta.git 
cd ./tensorflow-theta/examples/android
```

## Android Studioで開く
androidフォルダをAndroid Studioで開く。
"Add Google Maven repository and sync project"をクリックするなどして画面に従う。
build.gradle の45行目を"bazel"から"none"に書き換え。

```
// set to 'bazel', 'cmake', 'makefile', 'none'
def nativeBuildSystem = 'none'
```

同じくbuild.gradle の194行目を"compile"から"implementation"に書き換える。

```
dependencies {
    if (nativeBuildSystem == 'cmake' || nativeBuildSystem == 'none') {
        implementation 'org.tensorflow:tensorflow-android:+'
    }
}
```

"Sync Now"して更新。

## Androidスマホで動かす
ここまでの変更でAndroidスマホ（Moto G5 Plus, Android 7.0）上では動作する。
"Run"-"Debug 'TensorflowAndroid'"を選択してビルドし、Androidスマホで動作させることができる。（THETA Vの場合はピピピピピといってカメラがシャットダウンしてしまう。）

TensorFlow Demoは、TF Classify、TF Detect、TF Stylize、TF Speechの４つのアクティビティ（アプリ）からできている。

* TF Classify: 1000カテゴリへの分類。
* TF Detect: YOLOなどを使った物体認識。
* TF Stylize: 絵画調などのスタイル変換。
* TF Speech: 音声認識。

## THETA V向けの変更
### Camera共通部分 (TF Classify, TF Detect, TF Stylize)の修正
* CameraActivity.javaを修正

CameraActivityクラスのonCreateで"com.theta360.plugin.ACTION_MAIN_CAMERA_CLOSE"をブロードキャストするように変更。THETAのカメラリソースを解放させて、Tensorflowアプリで利用できるようにする。

```java
  @Override
  protected void onCreate(final Bundle savedInstanceState) {
    LOGGER.d("onCreate " + this);
    sendBroadcast(new Intent("com.theta360.plugin.ACTION_MAIN_CAMERA_CLOSE"));
    super.onCreate(null);
```

ビルドするために、以下もCameraActivity.javaファイルの頭に追記。

```java
import android.content.Intent;
```

124行目付近のonPreviewSizeChosen引数を修正。回転無しにした。

```java
onPreviewSizeChosen(new Size(previewSize.width, previewSize.height), 0);
```

* LegacyCameraConnectionFragment.java を修正
 
99行目付近を以下のように変更。ここでは RicMoviePreview1024 にしたが、 RicMoviePreview3840などの設定も可能。

```java
            //camera.setDisplayOrientation(90);
            parameters.set("RIC_SHOOTING_MODE", "RicMoviePreview1024");
```

109行目を以下のように変更。幅と高さを入れ替え。

```java
          camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.width, s.height)]);
          textureView.setAspectRatio(s.width, s.height);
```

以上の変更で、Vysor上でTF Classify, TF Detectの動作を確認できた。
表示が縦長画像となったため修正したが、Androidスマホ（Moto G5 Plus)では新しいCamera2 APIが使えたのに対して、THETAではレガシーなCamera APIを使うため、スマホとTHETAで動作が異なっていたのかもしれない。

TF Detectは物体検出にデフォルトでTF_OD_API(Tensorflow Object Detection API)が使われるようになっている。

### TF Stylizeの修正

一旦画像を1:1の正方画像にしてStyle適用したのち、2:1の正距円筒画像に戻すように修正する。

* Style変換用に 2:1 -> 1:1の画像を作る

StylizeActivity.javaの495行目付近のprocessImage()でframeToCropTransform変換行列を作るときにアスペクト比を保存しないように設定する。これで、Style変換用に 2:1 -> 1:1の画像ができるようになる。

```java
  @Override
  protected void processImage() {
    if (desiredSize != initializedSize) {
      LOGGER.i(
          "Initializing at size preview size %dx%d, stylize size %d",
          previewWidth, previewHeight, desiredSize);

      rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
      croppedBitmap = Bitmap.createBitmap(desiredSize, desiredSize, Config.ARGB_8888);
      frameToCropTransform = ImageUtils.getTransformationMatrix(
          previewWidth, previewHeight,
          desiredSize, desiredSize,
          sensorOrientation, false);
```

* Style変換後の画像を 1:1 -> 2:1に戻す

StylizeActivity.javaの520行目付近のRunnable()内で、Canvasを使ってリサイズし、stylizeImage()でスタイル変更したcroppedBitmap画像を1:1から2:1にする。スタイル変更した2:1のtextureCopyBitmap画像ができる。

```java
    runInBackground(
        new Runnable() {
          @Override
          public void run() {
            cropCopyBitmap = Bitmap.createBitmap(croppedBitmap);
            final long startTime = SystemClock.uptimeMillis();
            stylizeImage(croppedBitmap);
            lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
            textureCopyBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);

            final Paint paint = new Paint();
            paint.setFilterBitmap(true);
            final Canvas canvas = new Canvas(textureCopyBitmap);
            canvas.drawBitmap(croppedBitmap, cropToFrameTransform, paint);

            if (SAVE_PREVIEW_BITMAP) {
              ImageUtils.saveBitmap(textureCopyBitmap, "stylizeImage.png");
            }
            requestRender();
            readyForNextImage();
          }
        });
```

ここまででTF Stylizeが動作するようになる。
<img src="https://qiita-image-store.s3.amazonaws.com/0/123458/e4c27643-db4b-790a-eed3-ad55a11720b1.jpeg" width=40%>


### TF Speechの修正

* SpeechActivity.java に AudioManagerの設定を追加。

```java
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Set up the UI.
    super.onCreate(savedInstanceState);
    
    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); // for THETA
    am.setParameters("RicUseBFormat=false"); // for THETA
    
    setContentView(R.layout.activity_speech);
```

* MODIFY_AUDIO_SETTINGS権限を追加。

AndroidManifest.xmlで、android.permission.MODIFY_AUDIO_SETTINGS権限を追加（以下は26行目付近）。TF Speechのサンプリングレートのデフォルトは16kHzであり、THETAのデフォルトは44.1kHzであった。44.1kHzではTF Speechのフィルタがうまく動作しないようなので16kHzで動作させたが、この設定で動作させるために権限追加が必要だったと思われる。

```java
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```

以上の変更で、スマホ程度に音声認識できるようになった。

# まとめ
TensorflowのAndroidサンプルをTHETA上で動作させて、画像認識と音声認識を行った。TF Classifyは物体の識別器だが、全天球画像では超広角なため物体を誤判定しやすい。TF Detectは画像内の物体を探す物体検出器である。TF Stylizeはうまく2:1画像に対してスタイル適用できなかったため一旦1:1画像でスタイル適用することで回避した。TF Speech はMODIFY_AUDIO_SETTINGS権限を追加するなどして動作させることができた。全天球向けの認識精度向上など工夫の余地はまだ多くあると考えられる。
鍵となるTensorFlowInferenceInterfaceクラスは assetsの下にある学習済みモデル(Protocol Buffers(.pb)形式のファイル)をパラメータとして利用している。同様に配置して読み込むことで様々な学習モデルを試すことができる。

# 参考
https://codelabs.developers.google.com/codelabs/tensorflow-style-transfer-android/index.html
https://api.ricoh/docs/theta-plugin-reference/camera-api/
http://iti.hatenablog.jp/entry/2017/05/25/093328