こんにちは、リコーの @3215 です。
先日RICOH THETA プラグインストアがオープンし、開発したTHETAプラグインをこのストアから発信できるようになりました。更に、いくつかのプラグインはソースコードも公開されています。
今回はその中のAutomatic Face Blur BETA (以降、自動顔ぼかしβ版)のソースコードから、撮影して何かしらの画像処理をかけて保存する、というプラグインを開発する場合に必要な作法を読み解いてみます。
自動顔ぼかしβ版の顔認識精度を改善したい・ぼかし方を変えたいという場合はもちろんですが、顔ぼかしに限らず画像処理を含むプラグインを作成したいという場合は、このプラグインをベースにするのが作りやすいと思います。
THETAプラグインをご存じない方はこちらをご覧ください。
プラグインを開発してみたいという方はまずは以下の記事をご覧ください。
THETAプラグインSDKの使い方は以下の記事を参考にしてください。
興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もよろしくおねがいします。
プラグインの構成
まずはプラグインがどのような構成になっているか確認します。
自動顔ぼかしβ版のプロジェクトをAndroid Studioにインポートしてみると、プロジェクトはapp
とpluginlibrary
の2つで構成されています。
プラグインとしての処理はapp
で実装しています。
pluginlibrary
はTHETAプラグインのSDKで、基本的には開発者が手を入れる必要はないものです。
以下、app
の方を見ていきます。
一般的なAndroidアプリと同じく、MainActivity
が入り口です。
ただし、AppCompatActivity
ではなくTHETAプラグインのSDKで提供されているPluginActivity
を継承しています。
MainActivity
ではプラグインのライフサイクルやTHETAのボタン操作に対応する処理の呼び出しをして、実際の処理はサブパッケージのクラスで行っています。
撮影から保存までの部分に関してはこのMainActivity
と、task
パッケージのTakePictureTask
、ImageProcessorTask
あたりを見ればほぼ把握できそうです。
その他の大半はWebUIに関係するソースコードですが、今回は解説の対象外とします。
WebUIとはブラウザからアクセスしてプラグインの設定などができる機能で、自動顔ぼかしβ版では以下のようにプレビュー、撮影、撮影モード設定などができます。
撮影の実行
単に撮影を実行するだけであれば、既にSDKのTakePictureTask
で最低限の実装がされています。
MainActivity
のonKeyDown()
で、シャッターボタンが押された時の処理が書かれています。
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
のコールバックで呼ばれるmTakePictureTaskCallback
のonPictureGenerated()
で、撮影後に画像処理を行う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://...)になっているので、ImageProcessorTask
のdoInBackground()
の中でローカルファイルのパスに変換しています。
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
顔ぼかし処理
ファイルパスを取得した後は、ImageProcessorTask
のblurInputFile()
以下のシーケンスで顔ぼかし処理を行っています。
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をコピーしてやる必要があります。
ImageProcessorTask
のdoInBackground()
でExif.copyMetadata()
を使って、元画像のExifを顔ぼかし後の画像にコピーしています。
File blurredFile = new File(blurredFileUrl);
File file = new File(fileUrl);
if (Exif.copyMetadata(fileUrl, blurredFileUrl)) {
.....
データベースの更新
API ReferenceのUpdating the Databaseの説明にあるように、新しく画像ファイルを作成したらTHETA内で画像を管理しているデータベースに反映させる必要があります。
MainActivity
のImageProcessorTask
のコールバックで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についてはメーカーサポート対象外になりますので、ご注意ください。