はじめに
第3回目の本稿は、「docomo Developer support」で公開予定となっている、PUX社の技術を用いた顔・オブジェクト認識APIを使って、顔がモザイクになって写るモザイクカメラアプリを作ってみます。「docomo Developer support」では、このAPIのように、NTTドコモ以外が開発したAPIも公開されており、今後もさまざまなAPIが増える予定になっています(本稿は、2015年1月29日時点の最新情報に基づいております)。
対象読者
JavaとEclipseを用いたAndroidアプリの開発で、基本的な知識がある方を対象とします。
顔・オブジェクト認識API
顔・オブジェクト認識とは、写真や動画などの画像から、特定の人物やもの(オブジェクト)を認識する技術です。最初に、どのような機能があるかを紹介しておきます。
提供されるAPIの概要
提供されるAPIは、次の4つです。
(1)顔検出
写真などの画像から、人間の顔を見つけることができます。一度のアクセスで、最大20人までの顔を認識することができます。また、顔の情報として、目や鼻などのパーツの位置や性別、年齢までも判定できます。
(2)顔認証
画像の中から、事前に登録した顔画像と似た顔を見つけられます。結果の情報には「どの程度似ているか」というレベルの数値が含まれています。
(3)オブジェクト認識
任意の画像を登録し、登録したものを別の画像から探し出すことができます。ブレていたり傾いていたり、大きさの違うものでも認識可能です。
(4)手書き文字認識
画面上にペンなどで書いた文字を、独自のパターン認識技術で、文字として特定できます。画像データのような事前に書かれた文字の認識ではなく、リアルタイムに入力される筆跡の認識が可能です。
ブラウザでAPIを試してみよう
これらのAPIは、Web APIとして提供されますので、まずはブラウザを使って、具体的にどのような機能なのかを見ていきましょう。
顔検出(faceDetection)
検出元の画像は、インターネット上のURLを指定するか、画像データをアップロードします。画像データをアップロードする場合は、HTMLフォームを使ってファイル送信するか、または画像データを、バイナリのままかBase64文字列に変換して、POSTメソッドで送信します。ここでは、フリー素材アイドルの画像(フリー素材アイドルの顔画像)を使って検出してみます。
ブラウザで、以下のURLを開いてみましょう。なお、APIKEYのパラメータには、「docomo Developer support」で取得したキーを設定してください。
https://api.apigw.smt.docomo.ne.jp/puxImageRecognition/v1/faceDetection?APIKEY=XXXXX&imageURL=http://takae.sakura.ne.jp/wings/image1.jpg
APIのベースのURLは、https://api.apigw.smt.docomo.ne.jp/puxImageRecognition/v1/で、その後に、機能名(ここではfaceDetection)を付加します。
パラメータは、APIKEY以外では、顔画像のURL(imageURL)を指定するだけで認識可能です。レスポンスは、デフォルトではXML文字列ですが、オプションのresponseパラメータにjsonを指定すれば、JSON文字列になります。
レスポンスデータは、顔検出結果情報(faceRecognition)をルートとして、顔情報(detectionFaceInfo)、顔座標(faceCoordinates)、顔部品座標(facePartsCoordinates)、各判定結果(~Judge)です。
各判定は、デフォルトで、瞬き、年齢、性別、顔の向き、笑顔度の結果が含まれます。オプションでは、動物顔、どや顔、困り顔なども判定可能です。
そのほか、詳細については、サイトの仕様書を参照してください。
顔認証(faceRecognition)
今度は、特定の顔で認証してみましょう。まず、顔の画像を登録します。
顔の登録は、faceRecognitionの後に、modeパラメータとして、registerを指定します。
https://api.apigw.smt.docomo.ne.jp/puxImageRecognition/v1/faceRecognition?APIKEY=XXXXXX&mode=register&imageURL=http://takae.sakura.ne.jp/wings/image1.jpg
登録されると、レスポンスのregistrationFaceInfoタグに、付与された画像のIDが格納されます。
<registrationFaceInfo> <meta/> <faceId>1</faceId> <imagePath>http://eval.st1.polestars.jp/webapi/public/41016555a056124b20d013758cea2b04/20150109153901834_PC.jpg </imagePath> </registrationFaceInfo>
登録できましたので、別の画像で認証してみましょう。
認証は、modeパラメータに、verifyを指定します。
https://api.apigw.smt.docomo.ne.jp/puxImageRecognition/v1/faceRecognition?APIKEY=XXXXXX&mode=verify&imageURL=http://takae.sakura.ne.jp/wings/image2.jpg
認証結果は、verificationFaceInfoタグに格納されます。認証に用いた画像のID(faceId)と、どのくらい似ているかの認識スコア(score、100以下)の情報が含まれています。
<verificationFaceInfo> <faceCoordinates> </faceCoordinates> <candidate> <score>91</score> <faceId>1</faceId> <meta/> </candidate> </verificationFaceInfo>
オブジェクト認識(objectRecognition)
オブジェクト認識APIは、顔認証APIと同様の使い方です。ただし、レスポンスデータには、顔認証と異なり、認識オブジェクト情報(recognitionObjectInfo)として、オブジェクトの座標、角度、認識スコアが含まれています。
手書き文字認識(rakuhira)
手書き文字認識では、一文字の認識が可能です。APIの呼び出しは、筆跡の座標データが必要となり、データ量が多くなりますので、POSTメソッドのみに対応しています。
パラメータには、文字入力の枠サイズ(frameSizeWidth、frameSizeHeight)、その枠内の筆跡座標情報(coordinatePointArray)などを指定します。認識可能な文字は、漢字、カタカナ、ひらがな、記号、アルファベットになります。
レスポンスデータは、認識結果(handwritingRecognition)として、文字コード(recognitionResult)のリストが含まれます。
例えば、「高」の文字を認識させた場合は、次のように、高、萵、窩、富、貢、蒿という文字コードのリストとなりました。
"frameSizeWidth":256 "frameSizeHeight":256 "coordinatePointArray":{(96,22),(97,22),(98,23),(98,24),(99,24),...}
<handwritingRecognition> <recognitionResultList> <recognitionResult>0x9ad8</recognitionResult> <recognitionResult>0x8435</recognitionResult> <recognitionResult>0x7aa9</recognitionResult> <recognitionResult>0x5bcc</recognitionResult> <recognitionResult>0x8ca2</recognitionResult> <recognitionResult>0x84bf</recognitionResult> </recognitionResultList> </handwritingRecognition>
モザイクカメラを作ろう(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クラスを参照してください。
モザイクカメラを作ろう(2)
顔情報を抽出する
Android Asynchronous Http Clientを使うと、レスポンスデータは、Android SDK標準のJSONObjectに変換されます。今回は、FaceCoordinatesというクラスで、そのJSONObjectから顔の位置データ(左上のXY座標、幅、高さ)をArrayListデータとして抽出しています。
public class FaceCoordinates { // 顔の位置データ public class faceCoordinate { int left; int top; // 左上の座標 int width; int height; // 幅と高さ } // 顔検出結果のJSON文字列から、顔の位置データを抽出する public List<faceCoordinate> extract(JSONObject response) { List<faceCoordinate> faces = new ArrayList<faceCoordinate>(); try { JSONArray detectionFaceInfo = response.getJSONObject("results") .getJSONObject("faceRecognition") .getJSONArray("detectionFaceInfo"); for (int i = 0; i < detectionFaceInfo.length(); i++) { JSONObject faceCoordinates = detectionFaceInfo.getJSONObject(i) .getJSONObject("faceCoordinates"); faceCoordinate face = new faceCoordinate(); face.left = faceCoordinates.getInt("faceFrameLeftX"); face.top = faceCoordinates.getInt("faceFrameTopY"); face.width = faceCoordinates.getInt("faceFrameRightX") - faceCoordinates.getInt("faceFrameLeftX"); face.height = faceCoordinates.getInt("faceFrameBottomY") - faceCoordinates.getInt("faceFrameTopY"); faces.add(face); } } catch (JSONException e) { } return faces; } }
モザイク処理行う
モザイク処理は、MainActivityクラスのsaveMosaicメソッドとして実装しています。
モザイク処理を行うために、コールバックメソッドに渡されたJPEG形式の画像データを、1ピクセル32ビット(αチャネル、RGB、各8ビット)のビットマップデータに変換します(1)。
そしてモザイク処理(2)を追加してから、再度JPEG形式で保存し(3)、画像ギャラリーに反映させるために、AndroidのDBに登録します(4)。
// 画像データに、顔情報を利用してモザイク処理を行い、JPEG形式で保存する private void saveMosaic(byte[] data, JSONObject response) { // バイト配列をビットマップデータに変換する Bitmap original = BitmapFactory.decodeByteArray(data, 0, data.length); // ビットマップデータを、 // 32ビットのARGB(alpha, red, green, blue)データに変換する(1) Bitmap b = original.copy(Bitmap.Config.ARGB_8888, true); // 顔情報を利用してモザイク処理を行う(2) FaceCoordinates fs = new FaceCoordinates(); for (FaceCoordinates.faceCoordinate f : fs.extract(response)) { b = mosaic(b, f.left, f.top, f.width, f.height); } // 保存する画像ファイル名を設定する long now = System.currentTimeMillis(); File mediaFile = new File( Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DCIM), now + ".jpg"); try { FileOutputStream fos = new FileOutputStream(mediaFile, true); // JPEG形式で保存する(3) b.compress(CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); } catch (IOException e) { } // 画像ファイルを登録(4) ContentValues values = new ContentValues(); ContentResolver contentResolver = getContentResolver(); values.put(Images.Media.MIME_TYPE, "image/jpeg"); values.put("_data", mediaFile.getAbsolutePath()); // フルパス values.put(Images.Media.DATE_TAKEN, now); // 撮影日時 contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values); }
モザイクアルゴリズム
モザイク処理のアルゴリズムは、比較的単純で、画像を一定のサイズの矩形領域で区切り、その矩形全体を、領域内の色の平均値で塗りつぶします。ここでは、8ピクセル四方の矩形としています。
// 指定した範囲をモザイク処理する private Bitmap mosaic(Bitmap bitmap, int left, int top, int width, int height) { int dot = 8; int square = dot * dot; for (int x = 0; x < width / dot; x++) { for (int y = 0; y < height / dot; y++) { int r = 0, g = 0, b = 0; for (int w = 0; w < dot; w++) { for (int h = 0; h < dot; h++) { int dotColor = bitmap.getPixel(left + x * dot + w, top + y * dot + h); r += Color.red(dotColor); g += Color.green(dotColor); b += Color.blue(dotColor); } } r /= square; g /= square; b /= square; // 色を平均する for (int dw = 0; dw < dot; dw++) { for (int dh = 0; dh < dot; dh++) { bitmap.setPixel(left + x * dot + dw, top + y * dot + dh, Color.rgb(r, g, b)); } } } } return bitmap; }
モザイク処理については本稿の範疇を越えますので、ここでは割愛します。詳しくは『Androidでモザイク画像を作ってみる』などを参考にしてください。
最後に
今回は、PUX社の技術を用いた顔・オブジェクト認識APIの紹介と、顔検出APIを用いたモザイクカメラを作ってみました。顔検出APIでは、顔の位置だけでなく、年齢や、どや顔判定などもありますので、ゲームやジョークアプリにおもしろく応用できそうです。
次回も、APIを利用して、楽しいAndroidアプリを作ってみます。