Tuesday, April 24, 2012

java.lang.RuntimeException: autoFocus failed


Refer to the last exercise "Android 4 Face Detection: setFocusAreas() using face detected faces", it will throw java.lang.RuntimeException: autoFocus failed almost everytime in onFaceDetection() when camera.autoFocus(myAutoFocusCallback) is called after face detected, and setFocusAreas() called.

I delay calling camera.autoFocus(myAutoFocusCallback) for 500ms (using ScheduledExecutorService), it seem that the problem solved.



package com.exercise.AndroidCamera;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.Face;
import android.hardware.Camera.FaceDetectionListener;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore.Images.Media;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class AndroidCamera extends Activity implements SurfaceHolder.Callback{

 Camera camera;
 SurfaceView surfaceView;
 SurfaceHolder surfaceHolder;
 boolean previewing = false;
 LayoutInflater controlInflater = null;
 
 Button buttonTakePicture;
 TextView prompt;
 
 DrawingView drawingView;
 Face[] detectedFaces;
 
 final int RESULT_SAVEIMAGE = 0;
 
 private ScheduledExecutorService myScheduledExecutorService;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        
        getWindow().setFormat(PixelFormat.UNKNOWN);
        surfaceView = (SurfaceView)findViewById(R.id.camerapreview);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
        drawingView = new DrawingView(this);
        LayoutParams layoutParamsDrawing 
         = new LayoutParams(LayoutParams.FILL_PARENT, 
           LayoutParams.FILL_PARENT);
        this.addContentView(drawingView, layoutParamsDrawing);
        
        controlInflater = LayoutInflater.from(getBaseContext());
        View viewControl = controlInflater.inflate(R.layout.control, null);
        LayoutParams layoutParamsControl 
         = new LayoutParams(LayoutParams.FILL_PARENT, 
           LayoutParams.FILL_PARENT);
        this.addContentView(viewControl, layoutParamsControl);
        
        buttonTakePicture = (Button)findViewById(R.id.takepicture);
        buttonTakePicture.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    camera.takePicture(myShutterCallback, 
      myPictureCallback_RAW, myPictureCallback_JPG);
   }});
        
        LinearLayout layoutBackground = (LinearLayout)findViewById(R.id.background);
        layoutBackground.setOnClickListener(new LinearLayout.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub

    buttonTakePicture.setEnabled(false);
    camera.autoFocus(myAutoFocusCallback);
   }});
        
        prompt = (TextView)findViewById(R.id.prompt);
    }
    
    FaceDetectionListener faceDetectionListener
    = new FaceDetectionListener(){

  @Override
  public void onFaceDetection(Face[] faces, Camera tcamera) {
   
   if (faces.length == 0){
    prompt.setText(" No Face Detected! ");
    drawingView.setHaveFace(false);
   }else{
    prompt.setText(String.valueOf(faces.length) + " Face Detected :) ");
    drawingView.setHaveFace(true);
    detectedFaces = faces;
    
    //Set the FocusAreas using the first detected face
    List<Camera.Area> focusList = new ArrayList<Camera.Area>();
    Camera.Area firstFace = new Camera.Area(faces[0].rect, 1000);
    focusList.add(firstFace);
    
    if(camera.getParameters().getMaxNumFocusAreas()>0){
     camera.getParameters().setFocusAreas(focusList);
    }
    
    if(camera.getParameters().getMaxNumMeteringAreas()>0){
     camera.getParameters().setMeteringAreas(focusList);
    }

    buttonTakePicture.setEnabled(false);
    //camera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
    
    //Stop further Face Detection
    camera.stopFaceDetection();
    
    buttonTakePicture.setEnabled(false);
    
    /*
     * Allways throw java.lang.RuntimeException: autoFocus failed 
     * if I call autoFocus(myAutoFocusCallback) here!
     * 
     camera.autoFocus(myAutoFocusCallback);
    */
    
    //Delay call autoFocus(myAutoFocusCallback)
    myScheduledExecutorService = Executors.newScheduledThreadPool(1);
    myScheduledExecutorService.schedule(new Runnable(){
          public void run() {
           camera.autoFocus(myAutoFocusCallback);
            }
          }, 500, TimeUnit.MILLISECONDS);

   }
   
   drawingView.invalidate();
   
  }};
    
    AutoFocusCallback myAutoFocusCallback = new AutoFocusCallback(){

  @Override
  public void onAutoFocus(boolean arg0, Camera arg1) {
   // TODO Auto-generated method stub
   if (arg0){
    buttonTakePicture.setEnabled(true);
    camera.cancelAutoFocus();      
   }

  }};
    
    ShutterCallback myShutterCallback = new ShutterCallback(){

  @Override
  public void onShutter() {
   // TODO Auto-generated method stub
   
  }};
  
 PictureCallback myPictureCallback_RAW = new PictureCallback(){

  @Override
  public void onPictureTaken(byte[] arg0, Camera arg1) {
   // TODO Auto-generated method stub
   
  }};
  
 PictureCallback myPictureCallback_JPG = new PictureCallback(){

  @Override
  public void onPictureTaken(byte[] arg0, Camera arg1) {
   // TODO Auto-generated method stub
   /*Bitmap bitmapPicture 
    = BitmapFactory.decodeByteArray(arg0, 0, arg0.length); */
   
   Uri uriTarget = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, new ContentValues());

   OutputStream imageFileOS;
   try {
    imageFileOS = getContentResolver().openOutputStream(uriTarget);
    imageFileOS.write(arg0);
    imageFileOS.flush();
    imageFileOS.close();
    
    prompt.setText("Image saved: " + uriTarget.toString());
    
   } catch (FileNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }

   camera.startPreview();
   camera.startFaceDetection();
  }};

 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int width,
   int height) {
  // TODO Auto-generated method stub
  if(previewing){
   camera.stopFaceDetection();
   camera.stopPreview();
   previewing = false;
  }
  
  if (camera != null){
   try {
    camera.setPreviewDisplay(surfaceHolder);
    camera.startPreview();

    prompt.setText(String.valueOf(
      "Max Face: " + camera.getParameters().getMaxNumDetectedFaces()));
    camera.startFaceDetection();
    previewing = true;
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

 @Override
 public void surfaceCreated(SurfaceHolder holder) {
  // TODO Auto-generated method stub
  camera = Camera.open();
  camera.setFaceDetectionListener(faceDetectionListener);
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder holder) {
  // TODO Auto-generated method stub
  camera.stopFaceDetection();
  camera.stopPreview();
  camera.release();
  camera = null;
  previewing = false;
 }
 
 private class DrawingView extends View{
  
  boolean haveFace;
  Paint drawingPaint;

  public DrawingView(Context context) {
   super(context);
   haveFace = false;
   drawingPaint = new Paint();
   drawingPaint.setColor(Color.GREEN);
   drawingPaint.setStyle(Paint.Style.STROKE); 
   drawingPaint.setStrokeWidth(2);
  }
  
  public void setHaveFace(boolean h){
   haveFace = h;
  }

  @Override
  protected void onDraw(Canvas canvas) {
   // TODO Auto-generated method stub
   if(haveFace){

    // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
     // UI coordinates range from (0, 0) to (width, height).
     
     int vWidth = getWidth();
     int vHeight = getHeight();
    
    for(int i=0; i<detectedFaces.length; i++){
     
     if(i == 0){
      drawingPaint.setColor(Color.GREEN);
     }else{
      drawingPaint.setColor(Color.RED);
     }
     
     int l = detectedFaces[i].rect.left;
     int t = detectedFaces[i].rect.top;
     int r = detectedFaces[i].rect.right;
     int b = detectedFaces[i].rect.bottom;
     int left = (l+1000) * vWidth/2000;
     int top  = (t+1000) * vHeight/2000;
     int right = (r+1000) * vWidth/2000;
     int bottom = (b+1000) * vHeight/2000;
     canvas.drawRect(
       left, top, right, bottom,  
       drawingPaint);
    }
   }else{
    canvas.drawColor(Color.TRANSPARENT);
   }
  }
  
 }
}


Download the files.

Next: - Gets the distances from the camera to the focus point - getFocusDistances()

4 comments:

  1. Thanks so much for this, 500ms didn't work for me but 1000ms did. Thanks again!

    ReplyDelete
  2. Thanks you for that, It is work. But you use the back camera. I want to use the front camera.I have changed the camera id in the camera.open(FRONT). But I have to change some values to draw the rectangle in the onDraw method. I do not know how to do that ? Any idea ??

    ReplyDelete
  3. found. I used matrix. For info :

    Matrix matrix = new Matrix();

    matrix.setScale(-1, 1);
    matrix.postRotate(0);
    matrix.postScale(getWidth() / 2000f, getHeight() / 2000f);
    matrix.postTranslate(getWidth() / 2f, getHeight() / 2f);


    int vWidth = getWidth();
    int vHeight = getHeight();

    for(int i=0; i<detectedFaces.length; i++){
    if(i == 0){
    RectF rect = new RectF();
    rect.set(detectedFaces[i].rect);
    matrix.mapRect(rect);
    canvas.drawRect(rect, drawingPaint);

    ReplyDelete
  4. First of all, thank you for this tutorial. It helped me a lot as I am a beginner in Android development.

    Yap, I was getting also occasionally runtime exception on my HTC Hero 500 running 4.1.2. but in setParameters method.

    When I put few logs in the code I saw that onFaceDetection callback method was called many times per second (10-20) => camera got too many updates of focus areas => so the solution is to delay these calls.

    If I understand it correctly (I might be wrong) ScheduledExecutorService schedules calls in future and delays by 500ms. So when I get 20 calls per second (~every 50ms) it will schedule 20 calls in future but there will be 500ms between each call.

    I took simpler approach and I just put a simple code like this:
    long lastUpdate = System.currentTimeMillis();

    FaceDetectionListener faceDetectionListener = new FaceDetectionListener(){

    @Override
    public void onFaceDetection(final Face[] faces, final Camera camera) {
    ...
    ...
    ...

    // give it a rest at least 500ms
    if (System.currentTimeMillis() - lastUpdate < 500) {
    return;
    } else {
    lastUpdate = System.currentTimeMillis();
    }

    //Set the FocusAreas using the first detected face
    List focusList = new ArrayList();

    ...

    This ways it no more throws runtime exception and camera parameters are updated once per 500ms.

    ReplyDelete