Camera1 对焦(二) 对焦区域计算的几种伪代码算法(Touch to Focus)

一、摘要

本章介绍点击对焦区域计算的几种方式,包括如下几部分:

  1. 算法解析
  2. 网上常用方法
  3. 最佳计算方法
  4. 官方源码

相关文章:

  1. Camera1 对焦(一) UI坐标系和相机坐标系
  2. 【Android Camera1】Camera1 对焦(三) 对焦功能标准化流程
  3. Camera1 Parameters参数详解(二)—— 3A算法 (对焦、曝光、白平衡)
  4. Android AF Work Flow安卓自动对焦工作流程

二、算法

这里回顾一下算法如下

  1. 获取点击对应的Point(x, y)
  2. Point(x, y)根据相机的orient方向,来判断是否进行x, y互换【后置从UI到相机参考系需要逆时针旋转-90度】
  3. Point(x, y) 从(0, 0, mPreviewWidth, mPreviewHeight) => (-1000, -1000, 1000, 1000)
  4. Point(x, y) [camera] 根据AspectRatio和区域Size => Rect[Camera]

点击对焦区域的计算结果和如下几个因素相关:

  1. Point(x,y):触摸点的坐标
  2. isFront:相机是否前后置
  3. mPreviewUIWidth, mPreviewUIHeight:UI Size
  4. orientation:CameraInfo.orientation
  5. curZoomValue: 当前缩放值:在未锁放下可不考虑
  6. isFocus:用于区分是计算对焦区域还是计算曝光区域
  7. weight:区域的权重 :可默认1000
  8. aspectRatio:宽高比:可不考虑直接返回正方形的rect

三、计算方法

3.1 网上常用方法

如果从网上搜,则大概率会搜到如下函数代码,但是它只适合一部分的应用场景。

calculateTapArea
在这里插入图片描述

3.2 最佳计算方法

分为2部分,具体通过Matrix类进行坐标转换

  1. 构造CoordinateTransformer.java类。
  2. 具体计算

构造CoordinateTransformer.java

// ui坐标系转换到相机坐标系的Matrix
private final Matrix mTransformMatrix;
// 相机的范围RectF
private RectF mCamera1RectF;
// ui坐标范围
private int mPreviewUIWidth,mPreviewUIHeight;

/**
*@isCameraFront : 相机是否前置、前置需要做一次水平翻转
*@rotateDegree:matrix旋转角度 为相机的rotation
*@mCamera1RectF:camera1坐标系
*@previewRect:ui坐标系
*@mTransformMatrix transform Matrix
**/
public Camera2CoordinateTransformer(boolean isCameraFront, int rotateDegree, int mPreviewUIWidth,int mPreviewUIHeight) {
    
    
	  this.mPreviewUIWidth = mPreviewUIWidth;
	  this.mPreviewUIHeight = mPreviewUIHeight;
	  mCamera1RectF = new RectF(-1000, -1000, 1000, 1000);
	  RectF previewRect = new RectF(0,0,mPreviewUIWidth,mPreviewUIHeight);
	  mTransformMatrix = previewToCameraTransform(isCameraFront, rotateDegree, previewRect);
}

//核心方法
private Matrix previewToCameraTransform(boolean mirrorX, int sensorOrientation,
                                        RectF previewRect) {
    
    
    Matrix transform = new Matrix();
    //前置的话需要镜像,scaleX进行反转
    transform.setScale(mirrorX ? -1 : 1, 1);
    //ui的坐标系和相机的坐标系有夹角,需要做rotate处理
    //如后置,这里传入的是90度,ui坐标系转换为相机坐标系就需要顺时针旋转90度 即postRotate(-90)
    transform.postRotate(-sensorOrientation);
    // 先在previewRect添加旋转操作
    transform.mapRect(previewRect);
    // Map  preview coordinates to driver coordinates
    Matrix fill = new Matrix();
    fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);
    // fill做坐标转换映射到镜像和旋转
    transform.setConcat(fill, transform);
    // finally get transform matrix
    return transform;
}

/**
 * ui坐标系下的rect转换到相机坐标系的rect
 * Transform a rectangle in preview view space into a new rectangle in
 * camera view space.
 * @param source the rectangle in preview view space
 * @return the rectangle in camera view space.
 */
private RectF toCameraCoorRectF(RectF source) {
    
    
    RectF result = new RectF();
    mPreviewToCameraTransform.mapRect(result, source);
    return result;
}

//调用获取Camera.Area
//曝光区域要比对焦区域大。这里默认设置1000
public Camera.Area getArea(float x, float y,boolean isFocusArea) {
    
    
    if (isFocusArea) {
    
    
        return calcTapArea(x,y,mPreviewUIWidth / 6f,1000);
    } else {
    
    
        return calcTapArea(x,y,mPreviewUIWidth / 3f,1000);
    }
}

private Rect toFocusRect(RectF rectF) {
    
    
    Rect mFocusRect = new Rect();
    mFocusRect.left = Math.round(rectF.left);
    mFocusRect.top = Math.round(rectF.top);
    mFocusRect.right = Math.round(rectF.right);
    mFocusRect.bottom = Math.round(rectF.bottom);
    return mFocusRect;
}

private Camera.Area calcTapArea(float currentX, float currentY, float areaSize,int weight) {
    
    
    float left = currentX - areaSize / 2f;
    float top = currentY - areaSize / 2f;
    RectF rect = new RectF(left, top, left + areaSize, top + areaSize);
    return new Camera.Area(toRect(toCameraCoorRectF(rectF)), weight));
}

具体计算

handleFocus方法

public void handleTouchFocus(float x, float y){
    
    
	//判读如果不支持FOCUS_MODE_AUTO 就返回,注意要try catch
	if(! supportFocusMode(Camera.Parameters.FOCUS_MODE_AUTO)){
    
    
		return;
	}
	//先取消cancelAutoFocus
	try {
    
    
	    mCamera.cancelAutoFocus();
	} catch (Exception e) {
    
    
	
	}
	//注意处理zoomValue情况
	if (curZoomValue > 1.0f) {
    
    
	    //zoomX - centerX = (sorceX - centerX)*zoomValue;
	    //sourceX = (zoomX - centerX)/zoomValue+ centerX;
	    //这里的x是zoom之后的x坐标
	    x = (x- previewUIWidth / 2f) / curZoomValue + previewUIWidth / 2f;
	    y = (y - previewUIHeight / 2f) / curZoomValue + previewUIHeight / 2f;
	}
	//CoordinateTransformer类方法
    Camera.Area focusArea = transformer.getArea(x, y, true);
    Camera.Area meterArea = transformer.getArea(x, y, false);
	
	...
	if(supportFocusArea){
    
    
		updateParameters...
	}
	if(supportMeterArea){
    
    
		updateParameters...
	}
}

分析

  1. 判断1:是否支持FOCUS_MODE_AUTOtouch focus需要FOCUS_MODE_AUTO模式,像前置就不支持;
  2. 判断2:是否支持supportFocusArea和supportMeterArea,具体可参考Camera1 Parameters参数详解(二)—— 3A算法 (对焦、曝光、白平衡)
  3. touch focus之前需要mCamera.cancelAutoFocus();
  4. 判断,设置,更新参数需要try-catch
  5. zoom Value处理,这里需要特别注意zoom放大后的坐标计算。因为不管缩放多大,最终都是以zoom = 1.0f的坐标系参考

3.3 官方touch focus区域计算源码

packages/apps/LegacyCamera/src/com/android/camera/FocusManager.java

public boolean onTouch(MotionEvent e) {
    
    
    if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return false;

    // Let users be able to cancel previous touch focus.
    if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
                mState == STATE_SUCCESS || mState == STATE_FAIL)) {
    
    
        cancelAutoFocus();
    }

    // Initialize variables.
    int x = Math.round(e.getX());
    int y = Math.round(e.getY());
    int focusWidth = mFocusIndicatorRotateLayout.getWidth();
    int focusHeight = mFocusIndicatorRotateLayout.getHeight();
    int previewWidth = mPreviewFrame.getWidth();
    int previewHeight = mPreviewFrame.getHeight();
    if (mFocusArea == null) {
    
    
        mFocusArea = new ArrayList<Area>();
        mFocusArea.add(new Area(new Rect(), 1));
        mMeteringArea = new ArrayList<Area>();
        mMeteringArea.add(new Area(new Rect(), 1));
    }

    // Convert the coordinates to driver format.
    // AE area is bigger because exposure is sensitive and
    // easy to over- or underexposure if area is too small.
    calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
            mFocusArea.get(0).rect);
    calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
            mMeteringArea.get(0).rect);

    // Set the focus area and metering area.
    mListener.setFocusParameters();
    if (mFocusAreaSupported && (e.getAction() == MotionEvent.ACTION_UP)) {
    
    
        autoFocus();
    } else {
    
      // Just show the indicator in all other cases.
        updateFocusUI();
        // Reset the metering area in 3 seconds.
        mHandler.removeMessages(RESET_TOUCH_FOCUS);
        mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
    }

    return true;
}

public void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
        int x, int y, int previewWidth, int previewHeight, Rect rect) {
    
    
    int areaWidth = (int)(focusWidth * areaMultiple);
    int areaHeight = (int)(focusHeight * areaMultiple);
    int left = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
    int top = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);

    RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
    mMatrix.mapRect(rectF);
    Util.rectFToRect(rectF, rect);
}

分析
1.可以看到onTouch方法里调用calculateTapArea计算对焦区域值
2. calculateTapArea中,最终通过mMatrix.mapRect(rectF);把ui坐标系映射成为相机坐标系

public void initialize(View focusIndicatorRotate, View previewFrame,
        FaceView faceView, Listener listener, boolean mirror, int displayOrientation) {
    
    
    mFocusIndicatorRotateLayout = focusIndicatorRotate;
    mFocusIndicator = (FocusIndicatorView) focusIndicatorRotate.findViewById(
            R.id.focus_indicator);
    mPreviewFrame = previewFrame;
    mFaceView = faceView;
    mListener = listener;

    Matrix matrix = new Matrix();
    Util.prepareMatrix(matrix, mirror, displayOrientation,
            previewFrame.getWidth(), previewFrame.getHeight());
    // In face detection, the matrix converts the driver coordinates to UI
    // coordinates. In tap focus, the inverted matrix converts the UI
    // coordinates to driver coordinates.
    matrix.invert(mMatrix);

    if (mParameters != null) {
    
    
        mInitialized = true;
    } else {
    
    
        Log.e(TAG, "mParameters is not initialized.");
    }
}

public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
        int viewWidth, int viewHeight) {
    
    
    // Need mirror for front camera.
    matrix.setScale(mirror ? -1 : 1, 1);
    // This is the value for android.hardware.Camera.setDisplayOrientation.
    matrix.postRotate(displayOrientation);
    // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
    // UI coordinates range from (0, 0) to (width, height).
    matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
    matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
}

分析:

  1. 这里计算逻辑是计算从相机坐标系到ui坐标系 然后matrix.invert(mMatrix); 取返就得到ui坐标系映射到相机坐标系。
  2. 【3.2】中是直接计算ui坐标系到相机坐标系。
  3. 相机坐标系到ui坐标系先处理镜像,然后postRotate(displayOrientation) invert之后正好对应【3.2】中postRotate(-rotateDegree)。最后把from (-1000, -1000) to (1000, 1000)范围映射成为(0, 0) to (width, height)。

综上官方的计算过程为

  • FocusManager里先prepareMatrix为相机坐标系->ui坐标系。
  • 然后通过Matrix.invert得到ui坐标系-> 相机坐标系。
  • 最后在onTouch函数里calculateTapArea -> mMatrix.mapRect(rectF) -> Util.rectFToRect(rectF, rect);从ui坐标系下到RectF得到相机坐标系下到Rect。

猜你喜欢

转载自blog.csdn.net/Scott_S/article/details/122499597