Android Thingsにおけるカメラ＋MLKitサンプルプロジェクトの動作状況

この記事はAndroid Advent Calendar 11日目の記事になります。

## はじめに

本記事はAndroid ThingsにおけるカメラとMLKitの動作検証として、Googleが提供しているカメラやMLKitに関するサンプルプロジェクトを動作させてみた結果を共有する記事になります。

### 検証内容

- そもそもカメラが動くか
  - カメラプレビューが画面に表示されるか
- （ついでに）MLKitは動くかどうか

## ハードウェア、OS情報

|項目|内容|
|---|----|
| ハードウェア | Raspberry Pi3 Model B |
| OS | AndroidThings1.0.5(Android 8.1.0) - API27 |
| カメラ（モジュール） | [http://amzn.asia/d/1hAkIjn](http://amzn.asia/d/1hAkIjn) |
| カメラ（webカメラ） | [ロジクール C270](http://amzn.asia/d/hHBZ3ap) |
| ディスプレイ | [Quimat 3.5インチ](http://amzn.asia/d/4v8bela)を1280x720で利用 |

ちなみに文中に出てくる比較用のスマートフォンは、Pixel 3になります。

### ハードウェアの補足情報

Raspberry Pi3 Model B+は2018年12月10日現在、Android Thingsがサポートされていません。そのため、（個人が手軽に用意できる中で）RP3 ModelBが現状でのAndroid Thingsの高スペック端末になります。

ちなみに、[Model B+サポートを要望している人たちのIssueTrackerが存在します](https://issuetracker.google.com/issues/76155541)。あまりにも+1というレスが多すぎて、自分も+1したじゃん…と思われる人が「+1だけの通知がくる！お前らやめろ！」と怒り出すなど、大変反響です。


## Android ThingsにおけるWebカメラについて

Android Things上でカメラの動作検証をするためには、当然ながら端末にカメラが接続されていることが必須です。Android Things 1.0はAndroid 8.1がベースであるため、Raspberry Piの専用コネクタに接続して使うカメラモジュールでしか動かないと思われている方がいるかもしれません。

私自身もそう思っていましたが、何気なくC270を接続したところ普通に認識しました。Developer Previewではまだ対応していなかった様ですし、調べてもリリースノートでは特に触れられておらず、さらに1.0リリース以降でもWebカメラが認識されないという記事が出てきたりするので、特定のカメラ限定かもしれません。

カメラ自体の動作チェックは、何もアプリを動作させていないAndroid Things上での「Peripherals ＞ Camera」で確認することができます。
Camera自体の項目が出てきていない場合は、１台も認識されていないことを表しています。

![screenshot-1544404851893.jpg](https://qiita-image-store.s3.amazonaws.com/0/4560/7ac626da-c966-a848-e549-09b7db421b83.jpeg)

![screenshot-1544404874392.jpg](https://qiita-image-store.s3.amazonaws.com/0/4560/d6389609-0ee3-8f2e-57df-ec5cfba2797c.jpeg)

![screenshot-1544404928883.jpg](https://qiita-image-store.s3.amazonaws.com/0/4560/c48f8cfd-6e25-a89a-f5a3-1317f391354d.jpeg)

プレビュー画面では、接続されたカメラのプレビュー表示だけでなく、そのカメラの対応するフォーマットが一覧表示されています。このプレビュー画面では複数台のカメラを接続しても、いわゆる0番目に認識されたカメラしか表示されず、カメラの切り替えはできないようです。

カメラモジュールに関してはまったく問題なく動作します。ちなみにカメラモジュールとC270を接続した状態だと、C270の方が優先で認識されていました。

なお、コード上ではカメラモジュールやWebカメラかどうかは関係なく、全てCameraDevicesの中にまとめられます。

## 試したサンプルプロジェクトと動作検証結果

試したサンプルプロジェクトは、次の3つになります

- [googlesamples/android-Camera2Basic](https://github.com/googlesamples/android-Camera2Basic)
- [firebase/quickstart-android(MLKit)](https://github.com/firebase/quickstart-android/tree/master/mlkit)
- [androidthings/experiment-expression-flower(code部分を使用)](https://github.com/androidthings/experiment-expression-flower)

### googlesamples/android-Camera2Basic

**動きません**。

まずそのままのコードで実行しようとすると、 `CameraDevice.createCaptureSession()` で設定がうまくできず、 `CameraCaptureSession.StateCallback` の `onConfigureFailed()` が呼ばれてしまいます。ちなみにスマートフォンでは問題なく動作します。

このプロジェクトはカメラのプレビューをTextureView流し込む形で作られています。考えられるconfigエラーでいうと、TextureのBufferSizeや、ImageReaderの設定サイズなどが考えられますが、どちらもAndroid Thingsのカメラ情報ページに出てくるフォーマットに基づいた解像度でした。

念の為、後述する動作確認のとれたフォーマットで指定しても結果は変わりませんでした。さらにこのプロジェクト自体はAndroidManifestでハードウェアアクセラレータをtrueにしています。当然falseにしても動きません。

ちなみに、

```java
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
        new CameraCaptureSession.StateCallback() {
```

となっている部分を

```java
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface),
        new CameraCaptureSession.StateCallback() {
```

としてImageReaderを使わせないことで、 `onConfigureFailed()` 行きを防ぐことができましたが、その後のmCaptureSessionへのcameraCaptureSessionの代入で何故か画面が落ちる（Exceptionでキャッチ不可）状態になってしまい、解決に至りませんでした。ギブアップです。


### firebase/quickstart-android(MLKit)

そもそも複数画面を想定した設計のため、MLKitのActivityを直接立ち上げるようにして動作しています。
が、なんにせよ **動きません**。そもそもこちらはCamera2ではなく1のAPIが用いられているため、あまり利用はオススメできない印象があります。

まずこのプロジェクトはカメラを切り替えられる機能を実装しています。デフォルトは背面カメラを利用するようにしていて、この処理は `CameraInfo.facing` によって判断しています。

ここでC270に関しては `CameraInfo.facing` の中身が `CameraInfo.CAMERA_FACING_FRONT` 
 、つまり前面カメラとして返ってくるため、初回に選択できるカメラがなくアプリが落ちてしまいます。

かといってこのfacing部分を修正しても、今度はプレビュー画面の設定処理のひとつ（`getSupportedPreviewFpsRange()`）がnullを返すのでアプリが落ちます。FPSが無い＝プレビューできないということでしょうか？

この辺りでCamera2 APIじゃないこともあって、あらゆるAPIにdeprecated由来の取り消し線が引かれることにモチベーションをどんどん奪われてしったので、ギブアップしました。

### androidthings/experiment-expression-flower

元々がAndroid Thinhgs上で動くプロジェクトのため、 **しっかり動きます**。

このプロジェクトが上記２つと大きく違うポイントは、プレビュー処理にTextureViewやSurfaceViewを使わず、毎フレーム？画像が取得できたタイミングで呼び出される `onImageAvailable()` 内で取得したimage（YUV形式）を都度加工してImageViewに流し込む形で実装されています。

```java

    /*
    // フォーマット設定（320x240, MAXIMAGES=20)
    mImageReader =
            ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT, ImageFormat.YUV_420_888, MAX_IMAGES);
    */

    // Preview部分に関するところを抜粋
    @Override
    public synchronized void onImageAvailable(ImageReader reader) {
        // CaptureRequestは、CameraDevice.TEMPLATE_PREVIEW)を選択しています
        try {
            Image image = reader.acquireLatestImage();
            if (image == null) {
                return;
            }
            // ここでimage（YUV）をBitmapに変換
            imageToBitmap(image, displayBitmap);
            image.close();
        } catch (IllegalStateException e) {
            Log.e(TAG, "Too many images queued to be saved. Dropping this one.", e);
            return;
        }
                                        
        canvas.drawBitmap(displayBitmap, 0, 0, null);

        synchronized (buffer) {
            bufferCanvas.drawBitmap(displayBitmap, 0, 0, null);            
        }

        frameCallback.onAnnotatedFrame(buffer);
    }
```

TextureViewやSurfaceViewを使ったプレビューに比べると、圧倒的に昔ながらの力技感がありますが、現状での安牌なカメラプレビューの実装方法かもしれません。途中でBitmapを生成するので、MLKitでも扱いやすいですし。

どうしてもプレビューそのもののラグ、特にMLKitでの追従の遅れが目立ちますが、ハードウェア上ではそこまでの負荷がかかっていないようです。

<img width="427" alt="スクリーンショット 2018-12-10 17.37.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/4560/9ccf1fd8-c3f6-7c08-9829-de0067613040.png">

試しにカメラのサイズを320x240から640x480にあげてみましたが、負荷としては実用範囲内な気がします。ただMLKitの追従速度が明らかに遅れているので、ここからなにか開発する上で考慮すべき点になると思います。

<img width="422" alt="スクリーンショット 2018-12-10 17.39.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/4560/54002f3a-d46b-b8b6-d5bf-34f38333f2ba.png">

## 結論

個人的に「Android Things上のカメラ処理はTextureViewやSurfaceViewをサポートしていないのでは？（TextureView自体のサポートはDP6あたりのリリースで公式が明言している）」と言う気がしていますが、確定となる情報や処理を見つけられていないので断言できません。

ですが、個人的には今後は最後の逐次Bitmap化というやり方を使って行かざるを得ないかなあと思います。

## 最後に

こうした「Android Thingsでカメラを動かす」というだけのノウハウですら、国内外問わずネット上にほとんど転がっていない状況なので、どんどん共有化されて欲しいなと思いました。特にMLKitはAndroidとiOS上でしか現状は動かないので、これがRaspbian等と比較したAndroid Thingsの優位点だと思っているので、まだAndroid Thingsには価値があると感じています。

## おまけ

動作に成功した androidthings/experiment-expression-flower のカメラプレビューとMLKitの顔認証が実際にAndroid Thingsで動く様を、本日開催される [Otemachi.apk #01](https://connpass.com/event/109177/) のLT枠で紹介予定です。