当前位置: 代码迷 >> 综合 >> Camera1 对焦(二) 对焦区域计算的几种伪代码算法(Touch to Focus)
  详细解决方案

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

热度:9   发布时间:2024-01-10 22:27:49.0

Touch Focus对焦区域计算

  • 一、摘要
  • 二、算法
  • 三、计算方法
    • 3.1 网上常用方法
    • 3.2 最佳计算方法
      • 构造`CoordinateTransformer.java`类
      • 具体计算
    • 3.3 官方touch 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 coordinatesMatrix fill = new Matrix();fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);// fill做坐标转换映射到镜像和旋转transform.setConcat(fill, transform);// finally get transform matrixreturn 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 catchif(! supportFocusMode(Camera.Parameters.FOCUS_MODE_AUTO)){
    return;}//先取消cancelAutoFocustry {
    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。
  相关解决方案