モザイクカメラを作ろう(1)
では次に、顔検出APIを利用してモザイクカメラを作ってみましょう。認識した顔情報を元に、撮影した画像にモザイク加工を施します。
カメラAPI
今回のアプリでは、前回と異なりカメラの制御も自前で行うことにします。カメラの制御は、Android SDKに含まれているCamera API(android.hardware.Cameraパッケージ)を利用します。このAPIは、Android 5.0(APIレベル21)では非推奨となり、camera2(android.hardware.camera2)が公開されています。ただ新しいAPIは、Android 5.0でしか動作しませんので、本稿では、対応機種の多い従来のCamera APIを使うアプリとしています。
カメラのプレビューを表示する
SDKとともに配布されているサンプルプログラムに、カメラからの入力映像を全画面でプレビュー表示するプログラムがあります。APIレベル19のSDKであれば、インストールしたSDKのsamplesフォルダ以下の\android-19\legacy\ApiDemosというフォルダに納められています。
\samples\android-19\legacy\ApiDemos\src\com\example\android\apis\graphics\CameraPreview.java
今回のアプリは、このサンプルプログラムを元に、必要なコードを追加していくことにします。なお、紙面に限りがありますので、追加したコードを抜粋して解説します。アプリ全体は、別途ダウンロードして参照してみてください。
Manifestファイルの設定
最初に、パーミッションの設定をしておきましょう。カメラ機能の利用と、外部ディスクへのデータ保存、そして、Web APIのためのインターネット接続許可の設定を追加します。
また、今回のアプリは、screenOrientation属性に、横向き(landscape)専用の設定を追加しています。これは、元のサンプルプログラムでは、縦向きの場合に、プレビュー画面のアスペクト比が少しおかしくなるためです。
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/> <application> <activity android:screenOrientation="landscape"></activity> </application>
主な処理の流れ
元のプレビュープログラムは、カメラの入力データを、リアルタイムに全画面のSurfaceViewに描画するものです。SurfaceViewは、通常のViewとは異なり、メインスレッド以外のスレッドからも高速に描画可能なため、カメラ画像や動きの速いゲーム画面に適しています。
サンプルプログラムは、単にSurfaceViewに描画するだけなので、プレビューを保存する処理を追加します。本アプリでは、プレビュー画面をタッチしたときに、オートフォーカスを行い、その画像を加工、保存します。
主な処理の流れは、次のようになります。
- カメラ画像をSurfaceViewに描画する
- 画面をタッチすると、OnTouchListenerが呼ばれる
- オートフォーカス(autoFocus)を実行する
- オートフォーカスの実行後、AutoFocusCallbackが呼ばれる
- 撮影(takePicture)する
- 撮影後、画像のJPEGデータができたら、PictureCallbackが呼ばれる
- 画像データから、顔検出APIを使って顔の位置座標を検出する
- 検出結果の座標を元に、モザイク処理を行う
- 加工した画像を保存する
public class MainActivity extends Activity { ~中略~ // 二重起動防止フラグ private boolean mIsTake = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ~中略~ // 画面をタッチすると、OnTouchListenerが呼ばれる(1) mPreview.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if (!mIsTake && event.getAction() == MotionEvent.ACTION_DOWN) { mIsTake = true; // 二重起動防止フラグON // オートフォーカスを実行する(2) mCamera.autoFocus(mAutoFocusListener); } return true; } }); } // オートフォーカス動作後に呼ばれる(3) private Camera.AutoFocusCallback mAutoFocusListener = new Camera.AutoFocusCallback() { public void onAutoFocus(boolean success, Camera camera) { // 撮影する(4) mCamera.takePicture(null, null, mPicJpgListener); } }; // JPEGイメージ生成後に呼ばれる(5) private Camera.PictureCallback mPicJpgListener = new Camera.PictureCallback() { public void onPictureTaken(final byte[] data, Camera camera) { if (data == null) return; // 画像データから顔を検出する(6) new ImageRecognitionClient().faceDetection(data, new JsonHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, JSONObject response) { saveMosaic(data, response); // モザイク処理(7、8) mCamera.startPreview(); // プレビュー表示の再開 mIsTake = false; // 二重起動防止フラグ解除 Toast.makeText(MainActivity.this, "保存しました", Toast.LENGTH_LONG).show(); } }); } }; ~後略~ }
元のMainActivityクラスに、画面をタッチした際のハンドラであるOnTouchListener(1)と、オートフォーカスした際のハンドラのmAutoFocusListener(3)、撮影後のハンドラのmPicJpgListener(5)を追加しています。
実際の撮影は、Camera APIのtakePictureメソッドで行います。このメソッドの引数にコールバックを指定すると、撮影した画像データを受け取ることができます。ここで指定している第3引数のコールバックでは(4)、JPEG形式の画像データが渡されます。
なお、一度タッチされると、一連の処理が終わるまでは、再度実行されないように、二重起動防止フラグ(mIsTake)で制御しています。また、オートフォーカスや撮影を実行すると、プレビュー表示がいったん停止しますので、startPreviewメソッドで、再開するようにしています。
画像データから顔を検出する
顔検出APIの呼び出しは、ImageRecognitionClientというクラスを作り、その中のfaceDetectionメソッドで行っています。
public class ImageRecognitionClient { // 顔検出API private final String APIURL = "https://api.apigw.smt.docomo.ne.jp/puxImageRecognition/v1/faceDetection"; private final String APIKEY = "XXXXXX"; private AsyncHttpClient client = new AsyncHttpClient(); public void faceDetection(byte[] data, AsyncHttpResponseHandler responseHandler) { RequestParams params = new RequestParams(); params.put("response", "json"); // 画像データからBase64変換した文字列を設定する(1) params.put("inputBase64", Base64.encodeToString(data, Base64.DEFAULT)); // POSTメソッドでAPIを呼び出す(2) this.client.post(APIURL + "?APIKEY=" + APIKEY, params, responseHandler); } }
実際のHTTP通信は、Android Asynchronous Http Clientというオープンソースのライブラリを使っています。サイトからライブラリ本体(執筆時点では、android-async-http-1.4.6.jar)をダウンロードして、プロジェクトのlibsフォルダにコピーしておきます。このライブラリのおかげで、HTTP通信部分もメソッドを呼び出すだけのシンプルな実装になっています。
ImageRecognitionClientクラスのfaceDetectionメソッドのdataパラメータには、撮影した画像のビットマップデータが渡されますので、Base64の文字列に変換してパラメータに設定しています(1)。そして、顔検出APIを、AsyncHttpClientクラスのpostメソッドで呼び出します(2)。
APIが実行されてレスポンスが返されると、responseHandlerが呼ばれます。レスポンスデータの処理は、AsyncHttpResponseHandlerオブジェクト(responseHandler)のonSuccessメソッドに記述します。MainActivityクラスを参照してください。