安卓相机对焦模式

15

我正在尝试制作一个自定义相机应用程序。

我希望用户在该应用程序中可以选择对焦模式。

对焦模式有自动对焦和触摸对焦两种。

如果我们想在相机中使用触摸对焦,我们该如何开始呢?

5个回答

17
该功能取决于软件/硬件/制造商,我的建议是首先找到一款像Galaxy Nexus这样的手机,刷上Android 4.x,然后在其上尝试android.hardware.Camera.Parameters.getMaxNumFocusAreas(),如果返回值大于零,则您很幸运,可以使用setFocusAreas()实现“触摸对焦”功能。
为什么:
在旧版Android中,没有公共API可用于设置对焦区域。虽然许多制造商设法创建了自己的API和实现,但他们不会分享。
Android在API级别14中引入了对焦区域API,但是手机制造商可以选择不实现它(即选择坚持自己的解决方案)。要检查是否支持API,可以首先调用getMaxNumFocusAreasa(),如果返回正整数,则表示手机确实实现了API,并且您可以继续在相机应用程序中启用“触摸对焦”功能。 (当识别到人脸时,该API也是“面部检测”功能的启用器,相机应用程序使用该API让相机对其进行自动对焦。)
您可以参考原始Android相机应用程序源代码,了解如何正确使用API。
参考文献:
1. Android Camera API

获取最大对焦区域数()

设置对焦区域()

  1. Android 4.0相机应用程序源代码

mInitialParams.getMaxNumFocusAreas()

mParameters.setFocusAreas()

问候

Ziteng Chen


如果Android版本是2.3.4呢? - Jeff Bootsholz
1
但是 SetFocusableInTouch() 也被支持吗? - Jeff Bootsholz
没有setFocusAreas()方法,你只能实现“触摸拍照”功能,即当用户在屏幕上的任何位置触摸时,相机应用程序将自动对焦于一个固定的焦点区域(通常是取景器中心),然后进行图像捕获。坦率地说,这个功能并不是很吸引人,我建议你放置一个专用的屏幕拍照按钮。 - Ziteng Chen
你的意思是:
  1. 将按钮设置为重新对焦,还是
  2. 每次按下拍照按钮时,自动重新对焦,然后拍照?
- Jeff Bootsholz
第二项正是我所需要的。屏幕截图按钮是必要的,因为您的应用程序可能在没有硬件相机键的手机上运行。 - Ziteng Chen

8

试试这个:

public void takePhoto(File photoFile, String workerName, int width, int height, int    quality) {
if (getAutoFocusStatus()){
    camera.autoFocus(new AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        }
    }); 
}else{
    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}

然而,我也发现这种方法可能更准确:
if (getAutoFocusStatus()){
    camera.autoFocus(new AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
           if(success) camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        }
    }); 
}else{
    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}

最后一种方法是在对焦成功完成时拍摄照片。这种方法非常适合用于QR码扫描等场景。我相信这种方法同样适用于类似的情况。


为什么我们需要使用这种方法来扫描二维码,而不是调用意图(Intent)? - Jeff Bootsholz
getAutoFocusStatus() 函数是做什么的? - Deus chami

5

这个功能已经实现了,但如果我想添加触摸对焦怎么办?

public void takePhoto(File photoFile, String workerName, int width, int height, int    quality) {
    if (getAutoFocusStatus()){
        camera.autoFocus(new AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                camera.takePicture(shutterCallback, rawCallback, jpegCallback);
            }
        }); 
    }else{
        camera.takePicture(shutterCallback, rawCallback, jpegCallback);
    }

    this.photoFile = photoFile;
    this.workerName = workerName;
    this.imageOutputWidth = width;
    this.imageOutputHeight = height;
}

public void takePhoto(File photoFile, int width, int height, int quality) {
    takePhoto(photoFile, null, width, height, quality);
}

1
我的第一条回复中已经有答案了。 - Ziteng Chen
如果我想将cameraView设置为可点击,这样当我在屏幕上触摸一个点时,相机的焦点就会在那个点上,怎么办? - Jeff Bootsholz
然后,您需要调用Camera.Parameters.setFocusAreas() API来让相机知道焦点位置,可以参考我的第一条回复。 - Ziteng Chen
4
getAutoFocusStatus() 方法是用来做什么的? - Paul

2

我试图在我的应用程序中实现焦点功能,并以我想要的方式实现了这个功能。要实现“触摸对焦”,请参考下面的代码。

CameraPreview.java

public class CameraPreview extends SurfaceView implements
    SurfaceHolder.Callback {
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private OnFocusListener onFocusListener;

private boolean needToTakePic = false;

private Camera.AutoFocusCallback myAutoFocusCallback = new Camera.AutoFocusCallback() {

    @Override
    public void onAutoFocus(boolean arg0, Camera arg1) {
        if (arg0) {
            mCamera.cancelAutoFocus();
        }
    }
};

// Constructor that obtains context and camera
@SuppressWarnings("deprecation")
public CameraPreview(Context context, Camera camera) {
    super(context);
    this.mCamera = camera;
    this.mSurfaceHolder = this.getHolder();
    this.mSurfaceHolder.addCallback(this);
    this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    this.onFocusListener = (OnFocusListener) context;
}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    try {
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        mCamera.setDisplayOrientation(90);
        mCamera.startPreview();
    } catch (IOException e) {
        // left blank for now
        e.printStackTrace();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    mCamera.stopPreview();
    this.mSurfaceHolder.removeCallback(this);
    mCamera.release();
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                           int width, int height) {
    // start preview with new settings
    try {
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();
    } catch (Exception e) {
        // intentionally left blank for a test
        e.printStackTrace();
    }
}

/**
 * Called from PreviewSurfaceView to set touch focus.
 *
 * @param - Rect - new area for auto focus
 */
public void doTouchFocus(final Rect tfocusRect) {
    try {
        List<Camera.Area> focusList = new ArrayList<Camera.Area>();
        Camera.Area focusArea = new Camera.Area(tfocusRect, 1000);
        focusList.add(focusArea);

        Camera.Parameters param = mCamera.getParameters();
        param.setFocusAreas(focusList);
        param.setMeteringAreas(focusList);
        mCamera.setParameters(param);

        mCamera.autoFocus(myAutoFocusCallback);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (isNeedToTakePic()) {
        onFocusListener.onFocused();
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        float x = event.getX();
        float y = event.getY();

        Rect touchRect = new Rect(
                (int) (x - 100),
                (int) (y - 100),
                (int) (x + 100),
                (int) (y + 100));


        final Rect targetFocusRect = new Rect(
                touchRect.left * 2000 / this.getWidth() - 1000,
                touchRect.top * 2000 / this.getHeight() - 1000,
                touchRect.right * 2000 / this.getWidth() - 1000,
                touchRect.bottom * 2000 / this.getHeight() - 1000);

        doTouchFocus(targetFocusRect);
    }

    return false;
}

public boolean isNeedToTakePic() {
    return needToTakePic;
}

public void setNeedToTakePic(boolean needToTakePic) {
    this.needToTakePic = needToTakePic;
}
}

MainActivity.java

public class MainActivity extends Activity
    implements OnFocusListener {
private Button captureButton, switchCameraButton;
private Camera mCamera;
private CameraPreview mCameraPreview;
private int currentCameraId;

/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (getIntent().hasExtra("camera_id")) {
        currentCameraId = getIntent().getIntExtra("camera_id", Camera.CameraInfo.CAMERA_FACING_BACK);
    } else {
        currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
    }

    captureButton = (Button) findViewById(R.id.button_capture);
    captureButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            // Obtain MotionEvent object
            v.setEnabled(false);
            mCameraPreview.setNeedToTakePic(true);
            long downTime = SystemClock.uptimeMillis();
            long eventTime = SystemClock.uptimeMillis() + 100;
            float x = mCameraPreview.getWidth() / 2;
            float y = mCameraPreview.getHeight() / 2;
            // List of meta states found here:     developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
            int metaState = 0;
            MotionEvent motionEvent = MotionEvent.obtain(
                    downTime,
                    eventTime,
                    MotionEvent.ACTION_DOWN,
                    x,
                    y,
                    metaState
            );

            // Dispatch touch event to view
            mCameraPreview.dispatchTouchEvent(motionEvent);
        }
    });

    switchCameraButton = (Button) findViewById(R.id.button_switch_camera);
    switchCameraButton.setVisibility(
            Camera.getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE);
    switchCameraButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mCamera.stopPreview();
            //NB: if you don't release the current camera before switching, you app will crash
            mCameraPreview.getHolder().removeCallback(mCameraPreview);
            mCamera.release();

            //swap the id of the camera to be used
            if (currentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
                currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
            } else {
                currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
            }

            mCamera = getCameraInstance(currentCameraId);
            mCameraPreview = new CameraPreview(MainActivity.this, mCamera);
            FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
            preview.removeAllViews();
            preview.addView(mCameraPreview);
        }
    });
}

@Override
protected void onResume() {
    super.onResume();
    mCamera = getCameraInstance(currentCameraId);
    mCameraPreview = new CameraPreview(this, mCamera);
    FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
    preview.addView(mCameraPreview);
}

/**
 * Helper method to access the camera returns null if it cannot get the
 * camera or does not exist
 *
 * @return
 */
private Camera getCameraInstance(int currentCameraId) {
    Camera camera = null;
    try {
        camera = Camera.open(currentCameraId);
    } catch (Exception e) {
        // cannot get camera or does not exist
    }
    return camera;
}

Camera.PictureCallback mPicture = new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        File pictureFile = getOutputMediaFile();
        if (pictureFile == null) {
            return;
        }
        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
};

private static File getOutputMediaFile() {
    File mediaStorageDir = new File(
            Environment
                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp");
    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }
    // Create a media file name
    //        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
    //                .format(new Date());
    File mediaFile;
    mediaFile = new File(mediaStorageDir.getPath() + File.separator
            + "IMG_" + "DEMO_" + ".jpg");
    if (mediaFile.exists()) mediaFile.delete();

    return mediaFile;
}

@Override
public void onFocused() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mCamera.takePicture(null, null, mPicture);
            mCameraPreview.setNeedToTakePic(false);
            captureButton.setEnabled(true);
        }
    }, 1500);
}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

<Button
    android:id="@+id/button_switch_camera"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="Switch Camera" />

<Button
    android:id="@+id/button_capture"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="Capture" />

</LinearLayout>

您可以在Github - 自定义相机应用中找到示例应用程序。


0

调用此函数以启用触摸对焦模式:

private void setTouchToFocusMode(Camera.Parameters parameters){
    String focusMode;
    if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
        focusMode = Camera.Parameters.FOCUS_MODE_AUTO;
    }
    if (focusMode != null && focusMode.length() > 0){
        parameters.setFocusMode(focusMode);
    }
}

当用户点击屏幕时,请调用以下内容来设置焦点区域:
private static final int FOCUS_WIDTH = 80;
private static final int FOCUS_HEIGHT = 80;

public static String setFocalPoint(Camera.Parameters params, int x, int y){
    String focusMode = "";
    if (params != null && params.getMaxNumFocusAreas() > 0) {
        List<Camera.Area> focusArea = new ArrayList<Camera.Area>();
        focusArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000));

        params.setFocusAreas(focusArea);

        if(params.getMaxNumMeteringAreas() > 0) {
            params.setMeteringAreas(focusArea);
        }
        if(params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            focusMode = Camera.Parameters.FOCUS_MODE_AUTO;
        }
    }
    return focusMode;
}

调用 autoFocus/cancelAutoFocus 方法执行操作:

mCamera.cancelAutoFocus();    
mCamera.autoFocus(mAutoFocusCallback);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接