Wednesday, May 19, 2010

Android FaceDetector

Android provide a class android.media.FaceDetector to identify the faces of people in a Bitmap graphic object.

Android FaceDetector

It's a simple exercise of face detection on Android. You have to place your own photo (in 320x480) into /res/drawable folder, or use the attached photos from the download link on the bottom of the text.

package com.exercise.AndroidFaceDetector;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.media.FaceDetector;
import android.media.FaceDetector.Face;
import android.os.Bundle;
import android.view.View;

public class AndroidFaceDetector extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        setContentView(new myView(this));
    }
    
    private class myView extends View{
     
     private int imageWidth, imageHeight;
     private int numberOfFace = 5;
     private FaceDetector myFaceDetect; 
     private FaceDetector.Face[] myFace;
     float myEyesDistance;
     int numberOfFaceDetected;
     
     Bitmap myBitmap;

  public myView(Context context) {
   super(context);
   // TODO Auto-generated constructor stub
   
   BitmapFactory.Options BitmapFactoryOptionsbfo = new BitmapFactory.Options();
   BitmapFactoryOptionsbfo.inPreferredConfig = Bitmap.Config.RGB_565; 
   myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.face5, BitmapFactoryOptionsbfo);
   imageWidth = myBitmap.getWidth();
   imageHeight = myBitmap.getHeight();
   myFace = new FaceDetector.Face[numberOfFace];
   myFaceDetect = new FaceDetector(imageWidth, imageHeight, numberOfFace);
   numberOfFaceDetected = myFaceDetect.findFaces(myBitmap, myFace); 
   
  }

  @Override
  protected void onDraw(Canvas canvas) {
   // TODO Auto-generated method stub
   
            canvas.drawBitmap(myBitmap, 0, 0, null);
            
            Paint myPaint = new Paint();
            myPaint.setColor(Color.GREEN);
            myPaint.setStyle(Paint.Style.STROKE); 
            myPaint.setStrokeWidth(3);
            
            for(int i=0; i < numberOfFaceDetected; i++)
            {
             Face face = myFace[i];
             PointF myMidPoint = new PointF();
             face.getMidPoint(myMidPoint);
    myEyesDistance = face.eyesDistance();
             canvas.drawRect(
               (int)(myMidPoint.x - myEyesDistance),
               (int)(myMidPoint.y - myEyesDistance),
               (int)(myMidPoint.x + myEyesDistance),
               (int)(myMidPoint.y + myEyesDistance),
               myPaint);
            }
  }
    }
}



Download the files.

Related:
- Face detection for Camera

Updated:
- With the release of Google Play services 7.8, new Mobile Vision APIs was added, to include a new Face API that finds human faces in images and video. Read more: Face Detection with Google Play services, Mobile Vision API (with demo APK)


36 comments:

  1. Hi I couldn't get this example working. Ive tried 2 examples now and neither work ?
    ERROR: Return 0 faces because error exists in btk_FaceFinder_putDCR.
    On Froyo

    ReplyDelete
  2. hello Rza,

    May be you have to use picture with bigger face.

    You see in the example, the bottom-left picture cannot be detected, because the face too small.

    ReplyDelete
  3. Hey,

    Great article by the way, just out of interest how would you get the application to select an image from a gallery in the res folder and perform the face detection from there.

    Thanks.

    ReplyDelete
  4. Thank you for your help:-) Im a tiny bit stuck, how do you create a bitmap object with the following line as the resource has changed from being hard coded.

    myBitmap = BitmapFactory.decodeResource();

    sorry for being a pain,

    Thank you :-)

    ReplyDelete
  5. Hi Android ER,
    I get force close error when i run this app on emulator...i have used the same images provided(320 ,480)....please tell me hw to solve this....thank u

    ReplyDelete
  6. hello bumdeal2,

    You can use BitmapFactory.decodeStream().

    Please refer Display Gallery selected image using BitmapFactory

    ReplyDelete
  7. hello pradeep,

    In my experience, emulator is not too stable in some case, you are suggested to test on real device.

    - When I developer this exercise, also Force Close sometimes, because of large bitmap. You can try to re-size the bitmap. (But the attached bitmap have been tested in my exercise)

    ReplyDelete
  8. Hey,

    One last question if its not too much bother(I promise) :-)I've created the menu and have an onActivity for results. and within that I get the image and detect the faces within the image. The last step required is to use the canvas to draw the square around the face. However how can this be achieved when the on activity result has to be a public void meaning no values can be returned and the bitmap needs to be returned.

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == ACTIVITY_SELECT_IMAGE && resultCode == RESULT_OK) {

    try {

    Uri currImageURI = data.getData();
    String[] proj = { Images.Media.DATA, Images.Media.ORIENTATION };
    Cursor cursor = managedQuery(currImageURI, proj, null, null,null);
    int columnIndex = cursor.getColumnIndex(proj[0]);
    cursor.moveToFirst();
    mCurrentImagePath = cursor.getString(columnIndex);


    BitmapFactory.Options BitmapFactoryOptionsbfo = new BitmapFactory.Options();
    BitmapFactoryOptionsbfo.inPreferredConfig = Bitmap.Config.RGB_565;


    bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(currImageURI));


    int width = bitmap.getWidth();
    int height = bitmap.getHeight();


    myFace = new FaceDetector.Face[numberOfFace];
    myFaceDetect = new FaceDetector(width, height, numberOfFace);
    numberOfFaceDetected = myFaceDetect.findFaces(bitmap, myFace);

    if(numberOfFace >= 1)
    {
    mImageView.setImageBitmap(bitmap);

    }
    else
    {

    }
    }

    catch (Exception e) {
    }


    }


    and my canvas is as follows,


    public void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub

    canvas.drawBitmap(bitmap, 0, 0, null);

    Paint myPaint = new Paint();
    myPaint.setColor(Color.GREEN);
    myPaint.setStyle(Paint.Style.STROKE);
    myPaint.setStrokeWidth(3);

    for(int i=0; i < numberOfFaceDetected; i++)
    {
    Face face = myFace[i];
    PointF myMidPoint = new PointF();
    face.getMidPoint(myMidPoint);
    myEyesDistance = face.eyesDistance();
    canvas.drawRect(
    (int)(myMidPoint.x - myEyesDistance),
    (int)(myMidPoint.y - myEyesDistance),
    (int)(myMidPoint.x + myEyesDistance),
    (int)(myMidPoint.y + myEyesDistance),
    myPaint);
    }
    }

    What im wondering about is how id overcome this problem, and is there anyway in calling the canvas application while passing over the bitmap as well. Sorry about this im just an enthusiastic newbie, and ive tried to get it working for 2-3 days now :-)

    Thanks for your time and help.

    ReplyDelete
  9. hello bumdeal2,

    Do you means how to pass bitmap between activity?

    As I know, passing bitmap between activity is very inefficiency, and strongly not recommended.

    You can save in storage and pass the uri.

    ReplyDelete
  10. Hmm well my code is as follows and I have everything in one file. What I pretty much want to do is pass the bitmap to the canvas file. Would the best way be to save it in the storage and pass the uri;



    public class Activity3 extends Activity {

    private static final int ACTIVITY_SELECT_IMAGE = 1;
    private static final int GALLERY_ID = Menu.FIRST + 1;
    public String mCurrentImagePath = null;
    public int imageWidth, imageHeight;
    public int numberOfFace = 5;
    public FaceDetector myFaceDetect;
    public FaceDetector.Face[] myFace;
    float myEyesDistance;
    int numberOfFaceDetected;
    Bitmap bitmap;
    public Canvas canvas;
    private static final boolean DEBUG = true;
    private Bitmap sourceImage;




    private ImageView mImageView;



    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mImageView = new ImageView(this);
    setContentView(mImageView);


    }



    protected void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub

    canvas.drawBitmap(bitmap, 0, 0, null);

    Paint myPaint = new Paint();
    myPaint.setColor(Color.GREEN);
    myPaint.setStyle(Paint.Style.STROKE);
    myPaint.setStrokeWidth(3);

    for(int i=0; i < numberOfFaceDetected; i++)
    {
    Face face = myFace[i];
    PointF myMidPoint = new PointF();
    face.getMidPoint(myMidPoint);
    myEyesDistance = face.eyesDistance();
    canvas.drawRect(
    (int)(myMidPoint.x - myEyesDistance),
    (int)(myMidPoint.y - myEyesDistance),
    (int)(myMidPoint.x + myEyesDistance),
    (int)(myMidPoint.y + myEyesDistance),
    myPaint);
    }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    menu.add(0, GALLERY_ID, 0, "Gallery");
    return true;
    }


    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
    switch (item.getItemId()) {

    case GALLERY_ID:
    Intent galleryIntent = new Intent(Intent.ACTION_PICK,
    Images.Media.INTERNAL_CONTENT_URI);
    startActivityForResult(galleryIntent, ACTIVITY_SELECT_IMAGE);
    return true;

    }

    return super.onMenuItemSelected(featureId, item);
    }



    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == ACTIVITY_SELECT_IMAGE && resultCode == RESULT_OK) {

    try {

    Uri currImageURI = data.getData();
    String[] proj = { Images.Media.DATA, Images.Media.ORIENTATION };
    Cursor cursor = managedQuery(currImageURI, proj, null, null,null);
    int columnIndex = cursor.getColumnIndex(proj[0]);
    cursor.moveToFirst();
    mCurrentImagePath = cursor.getString(columnIndex);


    BitmapFactory.Options BitmapFactoryOptionsbfo = new BitmapFactory.Options();
    BitmapFactoryOptionsbfo.inPreferredConfig = Bitmap.Config.RGB_565;


    bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(currImageURI));


    int width = bitmap.getWidth();
    int height = bitmap.getHeight();


    myFace = new FaceDetector.Face[numberOfFace];
    myFaceDetect = new FaceDetector(width, height, numberOfFace);
    numberOfFaceDetected = myFaceDetect.findFaces(bitmap, myFace);

    if(numberOfFace >= 1)
    {

    mImageView.setImageBitmap(bitmap);


    }
    else
    {

    }
    }

    catch (Exception e) {
    }

    }

    }
    }

    ReplyDelete
  11. Hi Android ER,
    Thanks for ur reply for the earlier post...I tested the app on a real devices but the force close error still persists.....do i have to make any changes in my manifest file?....i have used the same images u provided..thank u

    ReplyDelete
  12. Also do tell me how to resize the bitmaps.....thank u

    ReplyDelete
  13. Heres a good tutorial on how to resize a bitmap :-)

    http://www.anddev.org/resize_and_rotate_image_-_example-t621.html

    ReplyDelete
  14. hello pradeep,

    I tested again without problem on Nexus One.

    May be you can download the project
    here.

    New a Android project in Eclipse, select "Create a project from existing source", and browser to select the un-zipped folder. Hope it can work as expect!

    ReplyDelete
  15. hello bumdeal2,

    After checking on your code, I think it's a mis-understanding of custom view.

    You have to implement your OWN View, and override the onDraw() method, not implement onDraw() method in your activity.

    The custom view is inner your activity, it can access your bitmap inside your class.

    I modify your code here, a new custom view is implemented. onActivityResult() update the bitmap only.

    ReplyDelete
  16. The code too long to post here!

    Please download here.

    ReplyDelete
  17. Thank you very much, it works brilliantly. Really appreciate all the help you've given to me. Have a cyber pint on me :-D

    ReplyDelete
  18. Hi AndroidER,
    Thank u so much much for the code...it worked great....

    ReplyDelete
  19. Hi Android ER,
    I wanted to know a few things ....why is the resolution restricted to 320*480 can it be changed?
    Can u please tell me a way to corp only the detected face...thank you

    ReplyDelete
  20. Dears,

    I figure out that "ERROR: Return 0 faces because error exists in btk_FaceFinder_putDCR" is caused by the width of input image is not a multiple of 2. Share to you gays.

    ReplyDelete
  21. @flybarry how did u solve that erorr. number of faces detected is 0 always no matter how many facest he image has. Any solutions? Anyone?

    ReplyDelete
  22. @ flybarry or @ Android er. How to solve the o error . no of faces detected is 0 always

    ReplyDelete
  23. Hi,
    I have tried out ur code it worked great...but for pictures with other(larger) resolution it displays a blank screen .....device camera actually takes pic of larger resolution...hw can i get it to work for large images

    ReplyDelete
  24. Great blog, Thanks for the android related information.

    ReplyDelete
  25. Can we use the face detector in camera for a motion sensing game in android.
    The camera will cotinously detect the faces and it coordinates.With the coordinates can we make a motion sensing game.

    ReplyDelete
  26. Android Er i was talking about if the face detection technique be used for making apps and games.

    ReplyDelete
  27. Android Er,

    Thanks for you link to the code, works perfect!

    I have a question about your second link:

    "The code too long to post here!

    Please download here."

    This link is only "Activity3.java"

    What do i do with this file?
    Does it overwrite the previous *.java file under:

    src\com\exercise\AndroidFaceDetector\AndroidFaceDetector.java

    Thank you

    ReplyDelete
  28. hello MKdev,

    Activity3 replay bumdeal2's comment.

    ReplyDelete
  29. Hi you said using gallery we can load images from sd card.But how can we send the particular selected image to face Detector for Face detection...Can you help me in that please..I tried your"Select Image using Android build-in Gallery"But have no idea how i could integrate both :(
    I was trying the following to get image:
    String Path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/MyCameraApp/image.jpg";
    myBitmap = BitmapFactory.decodeFile(Path);

    But the app stops "unfortunately"..Any help please..

    ReplyDelete
  30. hello AJC,

    If you decord bitmap from sdcard directly, may be OutOfMemory error will happen. read the post Scale bitmap Efficiently.

    ReplyDelete
  31. But i tried scaling and then tried to access the file still the same error..
    When the resized image is drawn from drawable it works effectively..But when the path for the same is provided nothing happens..rather it stops "unfortunately"...any help..

    ReplyDelete
  32. Hie..When i perform face detection on stored png images inside drawable folder, it provides correct output. However, when i take an image from camera, load it into bitmap and perform face detection on it, i get an error in logcat saying "Return 0 faces because error exists in btk_FaceFinder_putDCR". Can u please help ?? Do i need to
    convert the camera captured jpg images into png format??

    ReplyDelete
  33. hello versa,

    as I know, no need to convert to png. You can convert your png to jpg, check if it can detect.

    ReplyDelete
  34. Hello,
    I am using Xamarin Android. I have made application to detect face from camera and it is working well. but I want to detect face from image. can we do that using this FaceDetector class?

    ReplyDelete