android FaceDetector

FaceDetectorといっても、android.media.FaceDetectorのほうです。Google開発者サービスがない機種でも使えます。

使い方

FaceDetector | Android Developer

Bitmapの幅、高さ、maxFacesごとに、FaceDetectorのインスタンスを作ります。

maxFaces長さの結果用配列を用意しておきます。

Bitmapは、RGB_565限定です。

findFacesメソッドの戻り値は、検出した顔の個数です。結果用配列に顔検出データが入ってきます。

結果用配列の長さが3で、検出した顔の個数が1のとき、結果用配列[1]、結果用配列[2]はnullのままです。配列として使うにしろ、Listにして使うにしろ、長さを切り詰めたほうが使いやすいでしょう。

public List<FaceDetector.Face> findFaces(final Bitmap bmp, final int maxFaces) {
    final int w = bmp.getWidth();
    final int h = bmp.getHeight();
    FaceDetector faceDetector = new FaceDetector(w, h, maxFaces);

    final FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces];
    final int nFaces = faceDetector.findFaces(bmp, faces);
    faceDetector = null;

    final List<FaceDetector.Face> list = Arrays.asList(faces).subList(0, nFaces);
    return list;
}Code language: Java (java)

戻り値

戻り値のFaceDetector.Faceからは、左右の目の中間点、両目の距離がわかります。残念ながら、顔の向きはわかりません。

face.confidence() は、検出結果の精度で、0.0〜1.0です。0.4以上なら、良好な検出結果とあります。アプリの対象画像によって、調整してください。

face.eyesDistance()は、両目の距離です。ピクセル単位です。

face.getMidPoint()は、左右の目の中間点です、ピクセル単位です。

face.pose()は、EULER_X、EULER_Y、EULER_Z、どれを与えても、常に0が返ってきます。
Android Facedetector pose values are always 0
https://stackoverflow.com/questions/9960837/android-facedetector-pose-values-are-always-0

public static void debug(final FaceDetector.Face face) {
    PointF midPoint = new PointF();
    face.getMidPoint(midPoint);
    Log.d(TAG, "----------");
    Log.d(TAG, "confidence " + face.confidence());
    Log.d(TAG, "getMidPoint " + midPoint);
    Log.d(TAG, "eyesDistance " + face.eyesDistance());
    Log.d(TAG, "pose.X " + face.pose(FaceDetector.Face.EULER_X));
    Log.d(TAG, "pose.Y " + face.pose(FaceDetector.Face.EULER_Y));
    Log.d(TAG, "pose.Z " + face.pose(FaceDetector.Face.EULER_Z));
}Code language: Java (java)

マルチスレッド

Bitmapのサイズにもよりますが、処理に1〜2秒かかります。例えば5個のBitmapを処理すると約10秒かかってしまうで、可能ならマルチスレッドで処理したいところです。

1つのインスタンスを作り、2スレッドからfindFaces()を呼ぶと、どちらのスレッドの戻り値も0でした。マルチスレッド対応ではないようです。

2つのインスタンスを作り、2スレッドからfindFaces()を呼ぶと、正しい戻り値が返ってきました。

メモリーリーク?

次のようなことがありました。FaceDetectorの問題ではなく、アプリ側の構成や実装の問題だと思います。

あるアプリが、このFaceDetectorを使っていました。ストレステストとして、起動〜顔検出〜閉じるを100回繰り返したところ、Out Of Memoryが発生したり、無言でテスト途中終了しました。

原因がわからないので、いろいろな処理をコメントにしてみました。すると、FaceDetectorの処理をコメントにすると、100回成功しました。

FaceDetectorの使い方は、あるメソッドのローカル変数として宣言していて、外部には渡していません。問題ないように見えます。

ストレステストをしながら、android studioのProfilerでandroid.media.FaceDetectorのインスタンスを表示すると、3〜10個まで増えることがありますが、それ以上は増えません。ガベージコレクションで解放されていて、問題ないように見えます。

次にターミナルで、adb shell dumpsys meminfo com.example.myapp を表示すると、次のことがわかりました。

ストレステストの初回は、TOTALが、200MB前後、10回後には、約300MB、20回後には、約400MB、と、TOTALが増えていきました。
70回後には、約900MBまで増えて、このあたりでOut Of Memoryか、無言でテスト途中終了しました。

手動で100回繰り返してみました。70回すぎにTOTALが900MBぐらいまで増えました。そして、アプリを起動したとき、adb shell dumpsys meminfo com.example.myappの表示に、
com.example.myappが2個表示されました。その直後に、TOTALが200MBに戻っていました。自動でアプリ再起動したように見えました。

原因がわからないので、(幅、高さ、maxFaces)をキーにして、FaceDetectorのインスタンスプールを用意して、使いまわすことにしました。

なお、FaceDetectorを使った最小アプリで、再現を試しました。最初のTOTALは45MB、100回後のTOTALは48MB、ほとんど増えませんでした。なので、FaceDetectorの問題ではないと思います。

タイトルとURLをコピーしました