Saturday, January 25, 2014

Detect faces with Android SDK, no OpenCV

By now, we have tackled this in many ways :). Usually with OpenCV. See a list of posts here...
Now we are going to basically take this post, where we detected the faces with OpenCV in Android, and replace the face detection portion (CascadeClassifier + detectMultiScale) and replace it for FaceDetector.findFaces method in Android SDK. So, everything else is the same as that post, only thing is that we need to:
  1. Convert Mat to Bitmap on the right format for the face detection method (RGB_565).
  2. Do the face detection with the Android SDK.
We also reduce the original image resolution so that the detection happens much faster, as we did in the original post. So, although I copy here all the code so you don't have to go back and forth, the part changing is what goes after VIEW_MODE_GRAY inside the "public Mat onCameraFrame(CvCameraViewFrame inputFrame)" method. Notice that we still use all the framework from OpenCV to capture and display the image, and not the Android SDK approach.

Note: it seems that the FaceDetector used here is not the one my built-in camera app is using. I know this from simple performance evaluation. For instance, if I rotate the camera, the camera app still detects my face but FaceDetector loses it. It seems that there is one more way in the SDK to detect faces starting from Android 4.0 which I have not tried (so, don't now its performance). Still, probably that is not what is used in the camera app. This post here points to the same and has no answer, in case anybody wants to get some StackOverflow points :). I agree though that using the built-in camera app would likely narrow my software down to my phone.

_3DActivity.java
 /*  
  * Working demo of face detection (remember to put the camera in horizontal)  
  * using OpenCV/CascadeClassifier.  
  * Posted in http://cell0907.blogspot.com/2014/01/detecting-faces-in-android-with-opencv.html  
  */  
 package com.cell0907.td1;  
 import org.opencv.android.BaseLoaderCallback;  
 import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;  
 import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;  
 import org.opencv.android.LoaderCallbackInterface;  
 import org.opencv.android.OpenCVLoader;  
 import org.opencv.android.Utils;  
 import org.opencv.core.Core;  
 import org.opencv.core.CvException;  
 import org.opencv.core.CvType;  
 import org.opencv.core.Mat;  
 import org.opencv.core.Scalar;  
 import org.opencv.core.Size;  
 import org.opencv.imgproc.Imgproc;  
 import org.opencv.core.Point;  
 import android.app.Activity;  
 import android.graphics.Bitmap;  
 import android.graphics.PointF;  
 import android.media.FaceDetector;  
 import android.media.FaceDetector.Face;  
 import android.os.Bundle;  
 import android.util.Log;  
 import android.view.Menu;  
 import android.view.MenuItem;  
 import android.view.SurfaceView;  
 import android.view.WindowManager;  
 public class _3DActivity extends Activity implements CvCameraViewListener2 {  
   private static final int         VIEW_MODE_CAMERA  = 0;  
   private static final int         VIEW_MODE_GRAY   = 1;  
   private static final int         VIEW_MODE_FACES  = 2;  
   private MenuItem             mItemPreviewRGBA;  
   private MenuItem             mItemPreviewGrey;  
   private MenuItem             mItemPreviewFaces;  
   private int               mViewMode;  
   private Mat               mRgba;  
   private Mat               mGrey;  
   private int                              screen_w, screen_h;  
   private Tutorial3View            mOpenCvCameraView;   
   private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {  
     @Override  
     public void onManagerConnected(int status) {  
       switch (status) {  
         case LoaderCallbackInterface.SUCCESS:  
         {  
           // Load native library after(!) OpenCV initialization  
           mOpenCvCameraView.enableView();        
         } break;  
         default:  
         {  
           super.onManagerConnected(status);  
         } break;  
       }  
     }  
   };  
   public _3DActivity() {  
   }  
   /** Called when the activity is first created. */  
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  
     setContentView(R.layout.tutorial2_surface_view);  
     mOpenCvCameraView = (Tutorial3View) findViewById(R.id.tutorial2_activity_surface_view);  
     mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);  
     mOpenCvCameraView.setCvCameraViewListener(this);  
   }  
   @Override  
   public void onPause()  
   {  
     super.onPause();  
     if (mOpenCvCameraView != null)  
       mOpenCvCameraView.disableView();  
   }  
   @Override  
   public void onResume()  
   {  
     super.onResume();  
     OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);  
   }  
   public void onDestroy() {  
     super.onDestroy();  
     if (mOpenCvCameraView != null)  
       mOpenCvCameraView.disableView();  
   }  
   public void onCameraViewStarted(int width, int height) {  
        screen_w=width;  
        screen_h=height;  
     mRgba = new Mat(screen_w, screen_h, CvType.CV_8UC4);  
     mGrey = new Mat(screen_w, screen_h, CvType.CV_8UC1);  
     Log.v("MyActivity","Height: "+height+" Width: "+width);  
   }  
   public void onCameraViewStopped() {  
     mRgba.release();  
     mGrey.release();  
   }  
   public Mat onCameraFrame(CvCameraViewFrame inputFrame) {  
        long startTime = System.nanoTime();  
        long endTime;  
        boolean show=true;  
        mRgba=inputFrame.rgba();  
        if (mViewMode==VIEW_MODE_CAMERA) {  
             endTime = System.nanoTime();  
          if (show==true) Log.v("MyActivity","Elapsed time: "+ (float)(endTime - startTime)/1000000+"ms");  
             return mRgba;  
        }     
        if (mViewMode==VIEW_MODE_GRAY){  
          Imgproc.cvtColor( mRgba, mGrey, Imgproc.COLOR_BGR2GRAY);           
          endTime = System.nanoTime();  
          if (show==true) Log.v("MyActivity","Elapsed time: "+ (float)(endTime - startTime)/1000000+"ms");  
             return mGrey;  
        }  
        // REDUCE THE RESOLUTION TO EXPEDITE THINGS  
        Mat low_res = new Mat(screen_w, screen_h, CvType.CV_8UC4);  
        Imgproc.resize(mRgba,low_res,new Size(),0.25,0.25,Imgproc.INTER_LINEAR);  
        Bitmap bmp = null;  
        try {  
          bmp = Bitmap.createBitmap(low_res.width(), low_res.height(), Bitmap.Config.RGB_565);  
          Utils.matToBitmap(low_res, bmp);  
        }  
        catch (CvException e){Log.v("MyActivity",e.getMessage());}  
           int maxNumFaces = 1; // Set this to whatever you want  
           FaceDetector fd = new FaceDetector((int)(screen_w/4),(int)(screen_h/4),  
                  maxNumFaces);  
           Face[] faces = new Face[maxNumFaces];  
           try {  
                  int numFacesFound = fd.findFaces(bmp, faces);  
                  if (numFacesFound<maxNumFaces) maxNumFaces=numFacesFound;  
                  for (int i = 0; i < maxNumFaces; ++i) {  
                       Face face = faces[i];  
                       PointF MidPoint = new PointF();  
                 face.getMidPoint(MidPoint);  
 /*                      Log.v("MyActivity","Face " + i + " found with " + face.confidence() + " confidence!");  
                       Log.v("MyActivity","Face " + i + " eye distance " + face.eyesDistance());  
                       Log.v("MyActivity","Face " + i + " midpoint (between eyes) " + MidPoint);*/  
                       Point center= new Point(4*MidPoint.x, 4*MidPoint.y);  
                       Core.ellipse( mRgba, new Point(center.x,center.y), new Size(8*face.eyesDistance(), 8*face.eyesDistance()), 0, 0, 360, new Scalar( 255, 0, 255 ), 4, 8, 0 );  
                  }  
             } catch (IllegalArgumentException e) {  
                  // From Docs:  
                  // if the Bitmap dimensions don't match the dimensions defined at initialization   
                  // or the given array is not sized equal to the maxFaces value defined at   
                  // initialization  
                  Log.v("MyActivity","Argument dimensions wrong");  
             }  
           if (mViewMode==VIEW_MODE_FACES) {  
                endTime = System.nanoTime();  
                if (show==true) Log.v("MyActivity","Elapsed time: "+ (float)(endTime - startTime)/1000000+"ms");  
             return mRgba;  
                //return low_res;  
        }          
           return mRgba;  
    }  
   @Override  
   public boolean onCreateOptionsMenu(Menu menu) {  
     mItemPreviewRGBA = menu.add("RGBA");  
     mItemPreviewGrey = menu.add("Grey");  
     mItemPreviewFaces = menu.add("Faces");  
     return true;  
   }  
   public boolean onOptionsItemSelected(MenuItem item) {  
     if (item == mItemPreviewRGBA) {  
       mViewMode = VIEW_MODE_CAMERA;  
     } else if (item == mItemPreviewGrey) {  
       mViewMode = VIEW_MODE_GRAY;  
     } else if (item == mItemPreviewFaces) {  
       mViewMode = VIEW_MODE_FACES;  
     }  
     return true;  
   }    
 }  

For the rest of the files, please check the original post.

Just a final note... Somebody may wonder why I am mixing OpenCV with face detection from Android SDK. I am using OpenCV because it allows me to display something completely unrelated to what the camera is capturing (I need that for my final app). Although I found a method to do that without OpenCV I think it doesn't work with all the devices out there. And I use the face detection from Android SDK because I think it is more robust and still works, for instance, when turning the head... The weird thing is that it doesn't seem to work as good as the one I get on my camera app (the one that comes from factory with my phone, and HTC One). I also thought it would be faster, but actually looks slower...

PS.: Reference I used...

No comments:

Post a Comment