blog

Androidの顔認識機能を試す

2011.08.22

Androidの顔認識機能を試す

デバイスソリューション部 佐伯です。

Android のフレームワークに FaceDetector という顔認識(検出?)を行うクラスが標準で用意されているようなので試してみました。折角なので、こちらでご紹介したいと思います。

▼FaceDetectorクラス

まず最初に、FaceDetectorの概要について纏めておきたいと思います。

●コンストラクタ


パラメータに、認識する画像の幅と高さ、認識する顔の最大数を指定します。

●findFaces()メソッド

顔認識の実行には、findFaces() メソッドを使用します。

パラメータに、認識を行いたい画像のBitmapクラスと認識結果を受け取るFaceクラスの配列を指定します。(Faceクラスの配列は、FaceDetectorクラス生成時に指定したmaxFacesと同等かそれ以上確保しておく必要があります)
戻り値として、認識した顔の数が返されます。
また、パラメータに指定したFaceクラスの配列には認識結果の詳細情報が入ってきます。

●Faceクラス

Faceクラスには、認識した顔の情報を取得する下記メソッドが用意されています。

float confidence()
   認識された顔の信頼度(0~1の範囲)
float eyesDistance()
   両目の間隔
void getMidPoint(PointF point)
   両目の中心座標
float pase(int euler)
   顔のポーズ(euler で指定した軸に対する角度?)
   euler : 軸の指定(0: EULER_X, 1: EULER_Y, 2: EULER_Z)

 
 
▼テストアプリ

色々な画像に対してFaceDetectorを試せるように、下記のようなテストアプリケーションを考えてみました。

・ SDカードの所定フォルダ内にある画像のサムネイルをギャラリーを使って表示し、選択出来るようにする
・ ギャラリーで選択された画像に対して顔認識を実行し、認識結果を赤枠で示した画像を表示する
・ 検出結果の詳細情報(信頼度、中心座標、ポーズ)を画面下部にリスト表示する
・ アプリケーションタイトルに画像ファイル名と画像サイズを表示する

アプリケーション名は、FaceGallery としました。
 
●SDカードの所定フォルダにある画像ファイル名の一覧取得

SDカード上の画像を読み出すためには、まず最初にSDスロットにカードが存在し、読み出し可能となっているかを確認する必要があります。
SDカードの状態は、Environment.getExternalStorageState()により取得できます。
戻り値は文字列で、次に示すSDカードの状態を表す定数が用意されています。
読み書きとも可能 : Environment.MEDIA_MOUNTED
読み出しのみ可能 : Environment.MEDIA_MOUNTED_READ_ONLY

以下に、SDカードからの読み出しが可能かをチェックするためのサンプルコードを示します。

続いて、SDカードのディレクトリを Environment.getExternalStorageDirectory().getPath() を使い取得します。
所定フォルダ内にあるファイル名の一覧を取得するには、Fileオブジェクトのlist()メソッドを使用します。

以下に、SDカードの所定フォルダ内にあるファイル名の一覧を取得するサンプルコードを示します。

mPicturesList に SDカードの所定フォルダ(今回は、/Pictures/FaceImageとしています) 内に存在するファイル名の一覧が格納されます。
この段階ではファイル名の一覧に画像以外が含まれている可能性があるため、次のような処理で画像のみのリストを再構築します。

今回は、JPG形式とPNG形式の画像のみを対象としました。

●サムネイル画像の作成

ギャラリーに表示するサムネイル画像を生成する処理を次に示します。

BitmapFactory.Options の inJustDecodeBounds に true を指定すると画像を読み込むのではなく、画像情報のみを読み込むことが出来ます。
outWidth, outHeight にて画像の幅と高さを取得できます。
inSampleSize にて縮小比を設定できます。この例では、幅80ピクセル、高さ60ピクセル以下となるように設定しています。(補足: inSampleSize に 4 を指定すると縦横ともに1/4になる)

所定フォルダ内の各画像についてサムネイル画像を生成するには、ファイルサイズや数にもよりますが、ある程度の時間が必要となるため、実際のプログラムではプログレスダイアログ等を表示した上で処理することが望ましいと思います。

●ギャラリーに画像を表示

ギャラリーにサムネイル画像を表示するため、独自のアダプタを作成します。
BaseAdapter から派生し、一部のメソッド(getCount(), getItem(), getItemId(), getView())をオーバーライドして実現します。

以下にサンプルを示します。

getCount() メソッドでは、選択肢の数を返します。
getItem() と getItemId() は、今回のアプリケーションでは特に必要としていない機能のため適当な実装です。
getView() メソッドでは、サムネイル画像を設定したイメージビューを作成し返します。

メインアクティビティの onCreate() 等でギャラリーにアダプタを設定します。

setAdapter() にて独自アダプタ GalleryImageAdapter を設定しています。
画像の選択に応答するため、OnItemClickListener を実装しトリガを設定しています。
これにより、ギャラリーの選択肢が押されたときに処理を行えるようになります。

以下に、onItemClick() のサンプルを示します。

1つ目の引数 arg0に操作されたウィジェット、3つ目の引数 arg2 に選択された番号が渡されます。

ここでは、選択された画像に対して顔認識を実行し、検出結果をマーキングした画像を返す処理 ExecFaceDetect() を呼び出しています。

以下に、その処理を示します。

認識した顔があれば、元のビットマップを複製し、認識した各顔についてgetMidPoint(), eyesDistance() を使って求めた領域を赤い矩形で描画しています。

●アプリケーションのタイトルバーをカスタマイズ

アプリケーションのタイトルバーをカスタマイズして、左側にアプリケーション名、右側に任意のテキストを表示出来るようにします。

メインアクティビティの onCreate() に下記のように記述します。

getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title) にて、独自のレイアウトに設定しています。

今回設定したレイアウトを下記に示します。
[res/layout/custom_title.xml]

RelativeLayout を使用し、左寄せのテキストビュー title_left_text と右寄せのテキストビュー title_right_text を配置しています。
title_left_text に通常通りアプリケーションのタイトルを表示、title_right_text に処理画像のファイル名、サイズ等を表示出来るようにしています。

●レイアウトのリソース

レイアウトのリソースを下記に示します。
[res/layout/main.xml]

アクティビティに垂直方向のリニアレイアウトを配置し、その上にギャラリーとイメージビューを配置したスクロールビュー、認識結果を表示するためのリストビューを配置しています。
ScrollViewだけだと縦スクロールしかできなかった為、HorizontalScrollViewを入れ子にして横スクロールに対応しました。(ななめスクロールが出来ない等、少々操作性が悪いです)
画面サイズはHVGAを前提として、ギャラリーとスクロールビューの高さを調節しています。

リストビューに設定するアダプタに適用するTextViewのレイアウトを下記に示します。
[res/layout/list_row.xml]

●ソースコードとパッケージ

最終的なソースコードとアプリケーション(apk)を下記にアップしておきます。
ソースコード: FaceGallery.java
アプリケーション: FaceGallery.apk

 

●実行結果

いくつかの実行結果をご紹介します。
比較的正面を向いており、首を傾げていない画像に対しては、割と正確に認識出来ている様子です。
 

サングラスをしていても大丈夫のようです。
 

モノクロ画像でも大丈夫のようですが、子供の方を検出できていないのは目が隠れ気味の為でしょうか…
 

もちろん、複数人も大丈夫です。(但し、コンストラクタで指定した最大数までです)
findFaces() に渡した、Faceクラスの配列には信頼度の高い順に情報が格納されるようです。
この例での信頼度は、向かって中央→左→右の順となっています。右側の人物の信頼度が低いのは顔の右半分が影気味になっているからでしょうか…
 

こちらの結果をみると、目しか出ていないものは認識しておらず、目だけではなく鼻・口といった部分も見ている可能性が高そうです。

既にお気づきかと思いますが、pose()メソッドの結果は全て 0 が返ってきています。
AVD を Android 2.2, Android 2.3.1, Android 2.3.3, Android 3.0 の各バージョンで作成し確認してみましたが、何れのバージョンでも常に 0 となっていました。どうやら現状はインプリメントされていない様子です。

また、findFaces() の処理時間を測定し表示するようにしてみました。
今回使用した画像は概ねVGA(640×480)サイズの画像ですが、エミュレーターでの処理時間は約3~4秒程度でした。(Core2 Duo @2.93GHz)
手元にあった ARM Cortex A8 @800MHz を搭載した Android 2.2 の実機で試してみたところ、処理時間は 1秒前後と比較的高速でした。

その他にも色々な画像で試してみた結果から、首を傾げていたり、上からのアングルだったりすると認識出来ないことが多々あり、顔認識の性能としては少々難ありといった所感です。
今後のバージョンアップで認識精度が上がって来ることを期待したいと思います。
とはいっても、標準でI/Fが用意されており、そこそこ認識出来ていることに驚きです。

以上で終わりたいと思います。
また何か面白そうな機能があれば紹介させて頂きたいと思います。

なお確認には、こちらのサイトに掲載されていた画像を使わせて頂きました。