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:

Rez said...

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

Erik said...

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.

Unknown said...

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.

Erik said...

hello bumdeal2,

please refer Select Image using Android build-in Gallery

Unknown said...

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 :-)

pradeep said...

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

Erik said...

hello bumdeal2,

You can use BitmapFactory.decodeStream().

Please refer Display Gallery selected image using BitmapFactory

Erik said...

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)

Unknown said...

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.

Erik said...

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.

Unknown said...

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) {
}

}

}
}

pradeep said...

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

pradeep said...

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

Unknown said...

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

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

Erik said...

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!

Erik said...

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.

Erik said...

The code too long to post here!

Please download here.

Unknown said...

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

pradeep said...

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

pradeep said...

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

flybarry said...

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.

Raghunandan said...

@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?

Raghunandan said...

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

Unknown said...

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

Andolasoft said...

Great blog, Thanks for the android related information.

sunny khandelwal said...

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.

Erik said...

hello sunny khandelwal,

for face detection for camera, please refer http://android-er.blogspot.hk/2012/04/face-detection-for-camera.html

sunny khandelwal said...

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

Anonymous said...

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

Erik said...

hello MKdev,

Activity3 replay bumdeal2's comment.

AJC said...

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..

Erik said...

hello AJC,

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

AJC said...

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..

versa said...

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??

Erik said...

hello versa,

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

Unknown said...

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?