Camera2フォーカス設定の方法|Android開発

      2018/10/09

Camera2 APIでフォーカス設定を行う方法です。

定数定義

private static final int STATE_INIT = -1;
private static final int STATE_WAITING_LOCK = 0;
private static final int STATE_WAITING_PRE_CAPTURE = 1;
private static final int STATE_WAITING_NON_PRE_CAPTURE = 2;
private static final int AF_SAME_STATE_REPEAT_MAX = 20;

メンバ変数

private int mState;
private int mSameAFStateCount;
private int mPreAFState;

フォーカス設定処理

  • パラメータfocusPointsはフォーカス中心位置です。プレビュー中の領域に対する割合(0.01(1%)~1.00(100%))を指定します。
  • フォーカス位置は`CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE`で取得できる矩形内の座標で指定します。
  • フォーカス位置は矩形で指定します。本サンプルでは`focusPoints`を中心として上下左右4dpを指定しています。

mOpenCameraIdはオープン中のカメラID。

public void startAutoFocus(PointF[] focusPoints, Context context) {
    int maxRegionsAF = 0;
    Rect activeArraySize = null;
    CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    try {
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(mOpenCameraId);
        maxRegionsAF = characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
        activeArraySize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
    if (activeArraySize == null) {
        activeArraySize = new Rect();
    }

    if (maxRegionsAF <= 0) {
        return;
    }
    if (focusPoints == null) {
        return;
    }

    // フォーカス範囲
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    int r = (int)(4 * metrics.density);

    MeteringRectangle[] afRegions = new MeteringRectangle[focusPoints.length];
    for (int i = 0; i < focusPoints.length; i++) {
        int x = (int)(activeArraySize.width() * focusPoints[i].x);
        int y = (int)(activeArraySize.height() * focusPoints[i].y);
        Rect p = new Rect(Math.max(activeArraySize.bottom, x - r), Math.max(activeArraySize.top, y - r),
                Math.min(x + r, activeArraySize.right), Math.min(y + r, activeArraySize.bottom));
        afRegions[i] = new MeteringRectangle(p, MeteringRectangle.METERING_WEIGHT_MAX);
    }

    // 状態初期化
    mState = STATE_WAITING_LOCK;
    mSameAFStateCount = 0;
    mPreAFState = -1;

    try {
        CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        if (null != mPreviewSurface) {
            captureBuilder.addTarget(mPreviewSurface);
        }
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
        if (0 < afRegions.length) {
            captureBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
        }
        captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
        mCaptureSession.setRepeatingRequest(captureBuilder.build(), mAFListener, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
厳密には`CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE`で取得できる矩形とプレビューサイズのアスペクト比からフォーカス座標を調整する必要があります。今回はフォーカス処理のみを説明するため、その部分は含めていません。

[2016/12/14]記事を追加しました。
Camera2 フォーカス座標の補正について|Android開発

オートフォーカスリスナ

AF_SAME_STATE_REPEAT_MAXは同一の状態が延々通知されてフォーカス処理が終了しなくなることがあるためのガード処理です。

CameraCaptureSession.CaptureCallback mAFListener = new CameraCaptureSession.CaptureCallback() {

    @Override
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);

        if (mState == STATE_WAITING_LOCK) {
            Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
            if (afState == null) {
                Log.w(TAG, "onCaptureCompleted AF STATE is null");
                mState = STATE_INIT;
                autoFocusEnd(false);
                return;
            }

            if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
                    afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                Log.i(TAG, "onCaptureCompleted AF STATE = " + afState + ", AE STATE = " + aeState);
                if (mCancel || (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED)) {
                    mState = STATE_INIT;
                    autoFocusEnd(false);
                    return;
                }
            }

            if (afState != CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN && afState == mPreAFState) {
                mSameAFStateCount++;
                // 同一状態上限
                if (mSameAFStateCount >= AF_SAME_STATE_REPEAT_MAX) {
                    mState = STATE_INIT;
                    autoFocusEnd(false);
                    return;
                }
            } else {
                mSameAFStateCount = 0;
            }
            mPreAFState = afState;
            return;
        }

        if (mState == STATE_WAITING_PRE_CAPTURE) {
            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
            Log.i(TAG, "WAITING_PRE_CAPTURE AE STATE = " + aeState);
            if (aeState == null ||
                    aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                    aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                mState = STATE_WAITING_NON_PRE_CAPTURE;
            } else if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                mState = STATE_INIT;
                autoFocusEnd(true);
            }
            return;
        }

        if (mState == STATE_WAITING_NON_PRE_CAPTURE) {
            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
            Log.i(TAG, "WAITING_NON_PRE_CAPTURE AE STATE = " + aeState);
            if (aeState == null ||
                    aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                mState = STATE_INIT;
                autoFocusEnd(true);
            }
        }
    }

    private void autoFocusEnd(boolean isSuccess) {
        // フォーカス完了/失敗時の処理
    }
};

 - Android