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:

Nic Gillespie said...

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

Alexis Chretienne said...

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

Alexis Chretienne said...

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

jogo said...

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.