Android
Theta
THETAPlugin
0

画像処理を含むTHETAプラグインの実装方法【THETAプラグイン開発】

こんにちは、リコーの @3215 です。

先日RICOH THETA プラグインストアがオープンし、開発したTHETAプラグインをこのストアから発信できるようになりました。更に、いくつかのプラグインはソースコードも公開されています。

今回はその中のAutomatic Face Blur BETA (以降、自動顔ぼかしβ版)のソースコードから、撮影して何かしらの画像処理をかけて保存する、というプラグインを開発する場合に必要な作法を読み解いてみます。
自動顔ぼかしβ版の顔認識精度を改善したい・ぼかし方を変えたいという場合はもちろんですが、顔ぼかしに限らず画像処理を含むプラグインを作成したいという場合は、このプラグインをベースにするのが作りやすいと思います。

プラグインを開発してみたいという方はまずは以下の記事をご覧ください。

THETAプラグインSDKの使い方は以下の記事を参考にしてください。

また、こちらのtwitterアカウントでTHETAプラグインに関する情報を発信していますので、フォローお願いします!

プラグインの構成

まずはプラグインがどのような構成になっているか確認します。
自動顔ぼかしβ版のプロジェクトをAndroid Studioにインポートしてみると、プロジェクトはapppluginlibraryの2つで構成されています。

プラグインとしての処理はappで実装しています。
pluginlibraryTHETAプラグインのSDKで、基本的には開発者が手を入れる必要はないものです。

project_contents.png

以下、appの方を見ていきます。

一般的なAndroidアプリと同じく、MainActivityが入り口です。
ただし、AppCompatActivityではなくTHETAプラグインのSDKで提供されているPluginActivityを継承しています。

MainActivityではプラグインのライフサイクルやTHETAのボタン操作に対応する処理の呼び出しをして、実際の処理はサブパッケージのクラスで行っています。
撮影から保存までの部分に関してはこのMainActivityと、taskパッケージのTakePictureTaskImageProcessorTaskあたりを見ればほぼ把握できそうです。

その他の大半はWebUIに関係するソースコードですが、今回は解説の対象外とします。
WebUIとはブラウザからアクセスしてプラグインの設定などができる機能で、自動顔ぼかしβ版では以下のようにプレビュー、撮影、撮影モード設定などができます。

web_ui.png

撮影の実行

単に撮影を実行するだけであれば、既にSDKのTakePictureTaskで最低限の実装がされています。
MainActivityonKeyDown()で、シャッターボタンが押された時の処理が書かれています。

public void onKeyDown(int keyCode, KeyEvent keyEvent) {
    if (keyCode == KeyReceiver.KEYCODE_CAMERA) {
        if (mTakePictureTask == null && mImageProcessorTask == null) { // 撮影中や画像処理中でないことを確認
            if (mUpdatePreviewTask != null) {
                mUpdatePreviewTask.cancel(false);   // WebUIのプレビュー更新をcancel
            }
            mTakePictureTask = new TakePictureTask(mTakePictureTaskCallback, null,
                    null);
            mTakePictureTask.execute(); // 撮影タスクを実行
        }
    }
}

自動顔ぼかしβ版ではTakePictureTaskのコンストラクタに引数が追加されていますが、これはWebUIからの撮影もサポートするためです。
ここではシャッターボタン押下の場合なので追加の引数はnullとし、コールバックにはMainActivity内に定義しているmTakePictureTaskCallbackを指定しています。

撮影した画像ファイルへのアクセス

TakePictureTaskのコールバックで呼ばれるmTakePictureTaskCallbackonPictureGenerated()で、撮影後に画像処理を行うImageProcesssorTaskを呼び出しています。

public void onPictureGenerated(String fileUrl) {
    if (!TextUtils.isEmpty(fileUrl)) {
        notificationAudioOpen();
        notificationLedBlink(LedTarget.LED4, LedColor.BLUE, 1000);
        mImageProcessorTask = new ImageProcessorTask(mImageProcessorTaskCallback);
        mImageProcessorTask.execute(fileUrl);   // 画像処理タスクを実行
    } else {
        notificationError(getResources().getString(R.string.take_picture_error));
    }
    mTakePictureTask = null;
}

この引数のfileUrlには、WebAPIの撮影コマンドを叩いた時のresponseに入っているURLが入っています。
このURLはHTTPアクセス用のもの(http://...)になっているので、ImageProcessorTaskdoInBackground()の中でローカルファイルのパスに変換しています。

protected Map<String, String> doInBackground(String... params) {
    Matcher matcher = Pattern.compile("/\\d{3}RICOH.*").matcher(params[0]);
    if (matcher.find()) {
        String fileUrl = DCIM + matcher.group();
        .....

一般的なデジタルカメラの画像ファイルは「DCIM」という名前のディレクトリの下の、数字3桁+アルファベット5文字のサブディレクトリの下に保存されます。
DCIMへのパスはAndroidのEnvironmentクラスを使って取得できます。自動顔ぼかしβ版ではMainActivityの冒頭で定義しています。

public static final String DCIM = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath();

デバッグしてみたところ、具体的には以下のように文字列が変換されていました。
- 変換前:http://127.0.0.1:8080/files/150100525831424d42075b6ace1cc300/100RICOH/R0010063.JPG
- 変換後:/storage/emulated/0/DCIM/100RICOH/R0010063.JPG

顔ぼかし処理

ファイルパスを取得した後は、ImageProcessorTaskblurInputFile()以下のシーケンスで顔ぼかし処理を行っています。

private Bitmap blurInputFile(@NonNull String fileUrl) throws IOException {
    long start = System.currentTimeMillis();
    inputFile(fileUrl);
    long now = System.currentTimeMillis();
    Timber.d("inputFile : %d", now - start);
    blurFaces();
    now = System.currentTimeMillis();
    Timber.d("blurFaceInEqui : %d", now - start);
    blurFacesOnSides();
    now = System.currentTimeMillis();
    Timber.d("blurFaceEquiTwoEdges : %d", now - start);
    return mBitmapToBlur;
}

ここでのポイントは、ファイルがEquirectangular形式の全天球画像なので、左右の両端部分はつなぎ合わせて処理を行う必要があるということです。blurFaceOnSides()が画像両端部分に対する処理です。

顔認識はblurFaces()及びcalculateCoordinateOfEyes()で行っていて、顔認識ライブラリにはAndroid標準のFaceDetectorを使っています。
また、顔認識できた領域に対するぼかし処理はblur()で行っています。

これらのメソッドを修正することで、顔認識の精度を改善したり、ぼかし方を変えることができます。
このプラグインは最も簡易的な実装になっていますので、これをベースにライブラリを差し替えたりロジックを工夫したりして、自分好みの顔ぼかしにチューニングしてみてください。

Exifのコピー

このままでは顔ぼかし後の画像ファイルのExifが空になってしまうので、元画像のExifをコピーしてやる必要があります。
ImageProcessorTaskdoInBackground()Exif.copyMetadata()を使って、元画像のExifを顔ぼかし後の画像にコピーしています。

File blurredFile = new File(blurredFileUrl);
File file = new File(fileUrl);
if (Exif.copyMetadata(fileUrl, blurredFileUrl)) {
.....

データベースの更新

API ReferenceのUpdating the Databaseの説明にあるように、新しく画像ファイルを作成したらTHETA内で画像を管理しているデータベースに反映させる必要があります。
MainActivityImageProcessorTaskのコールバックでnotificationDatabaseUpdate()を呼んでいます。

Matcher blurredMatcher = Pattern.compile("/DCIM.*")
        .matcher(fileUrlMap.get(ImageProcessorTask.BLURRED_FILE_KEY));
if (blurredMatcher.find()) {
    String formattedFileUrl = blurredMatcher.group();
    Timber.d(formattedFileUrl);
    Timber.d(fileUrl);
    String[] fileUrls = new String[]{formattedFileUrl, fileUrl};
    notificationDatabaseUpdate(fileUrls);   // データベースを更新
}

これで顔ぼかし処理をした画像がTHETAの画像一覧に登録されました。

まとめ

自動顔ぼかしβ版のソースコードから、画像処理を含むTHETAプラグインの実装方法を読み解いてみました。
ぜひ公開されているソースコードを参考にして、自分なりのプラグインを作ってみてください。

THETAプラグイン開発に興味を持たれた方はぜひパートナープログラムにご登録ください!
なお、登録時に申請したシリアルナンバーのTHETAについてはメーカーサポート対象外になりますので、ご注意ください。