前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Camera2 实现触摸对焦功能(Touch to Focus)

Android Camera2 实现触摸对焦功能(Touch to Focus)

作者头像
雪月清
发布2021-09-22 10:42:36
2.9K0
发布2021-09-22 10:42:36
举报
文章被收录于专栏:雪月清的随笔

之前在 Android Camera2 简介 这篇文章中简单介绍了下 Camera2 中 AF/AE 对焦区域如何进行设置,之前是通过手动计算对应关系实现的,但这种方式需要考虑到前后摄的区别,前摄和后摄坐标映射有区别,通用性不好,本文讲一下如何通过矩阵(Matrix)来实现这个过程

为什么要进行坐标映射

由于我们预览界面通常都是竖屏,而对于 Camera 底层的坐标来说,一般预览竖屏方向和后摄有90度夹角,和前摄有270度夹角,并且预览大小和底层图片实际大小也不是对应的,所以我们点击预览界面某个位置后,需要进行坐标转换,这样才能根据点击位置进行正确的对焦和测光操作

另外 Camera API 1 中的底层坐标区域和 Camera API 2 中的区域也有区别,具体和预览坐标对应关系如下图(以后摄为例):

图片中蓝色框表示手机预览界面,紫色线条坐标为Android View坐标系,绿色为 Camera 坐标系,旧的Camera底层坐标范围大小是固定的,宽高都为2000,而Camera2中的大小要根据查询出来的 SENSOR_INFO_ACTIVE_ARRAY_SIZE 来进行确定

使用Matrix进行坐标映射

  • Camera API 1 关于API 1的坐标映射, 可以参考Android源码中Camera代码, 路径: packages/apps/Camera2/src/com/android/camera/ui/focus/CameraCoordinateTransformer.java

核心代码如下:

代码语言:javascript
复制
private Matrix cameraToPreviewTransform(boolean mirrorX, int displayOrientation,
      RectF previewRect) {
    Matrix transform = new Matrix();

    // 缩放, (1, 1) 无改变, (-1, 1) x轴反向缩放, 即表示沿y轴镜像翻转
    // 如果是前置摄像头需翻转, 后置不需要.
    transform.setScale(mirrorX ? -1 : 1, 1);

    // 旋转, 从上面的坐标图可以看出, 预览和底层坐标有夹角
    transform.postRotate(displayOrientation);

    // 使用矩阵进行坐标映射, 将大小为 2000 x 2000矩形映射到
   // 预览大小, 比如 1920 x 1080  
    Matrix fill = new Matrix();
    fill.setRectToRect(CAMERA_DRIVER_RECT,
          previewRect,
          Matrix.ScaleToFit.FILL);
    // Concat the previous transform on top of the fill behavior.
    transform.setConcat(fill, transform);

    return transform;
}

上面是Android源码里面的代码,是先求的Camera Driver坐标映射到Preview坐标的Matrix,然后通过 Matrix.invert() 得到 Preview坐标到Camera Driver坐标的映射关系。

得到有映射关系的Matrix后,坐标转换只需调用mapRect(result, source)即可

  • Camera API 2 上面 API 1 的代码是不能直接用在 API 2中的, 主要原因是 Camera2 中底层的坐标和Camera中的区别比较大,Matrix.setRectToRect()的调用和API 1 中逻辑稍有差别, 完整的映射关系代码如下: CoordinateTransformer.java

核心代码如下:

代码语言:javascript
复制
package com.smewise.camera2.utils;

import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.camera2.CameraCharacteristics;

/**
 * Transform coordinates to and from preview coordinate space and camera driver
 * coordinate space.
 */
public class CoordinateTransformer {

    private final Matrix mPreviewToCameraTransform;
    private RectF mDriverRectF;

    /**
     * Convert rectangles to / from camera coordinate and preview coordinate space.
     * @param chr camera characteristics
     * @param previewRect the preview rectangle size and position.
     */
    public CoordinateTransformer(CameraCharacteristics chr, RectF previewRect) {
        if (!hasNonZeroArea(previewRect)) {
            throw new IllegalArgumentException("previewRect");
        }
        Rect rect = chr.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
        Integer sensorOrientation = chr.get(CameraCharacteristics.SENSOR_ORIENTATION);
        int rotation = sensorOrientation == null ? 90 : sensorOrientation;
        mDriverRectF = new RectF(rect);
        Integer face = chr.get(CameraCharacteristics.LENS_FACING);
        boolean mirrorX = face != null && face == CameraCharacteristics.LENS_FACING_FRONT;
        mPreviewToCameraTransform = previewToCameraTransform(mirrorX, rotation, previewRect);
    }

    /**
     * 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.
     */
    public RectF toCameraSpace(RectF source) {
        RectF result = new RectF();
        mPreviewToCameraTransform.mapRect(result, source);
        return result;
    }

    private Matrix previewToCameraTransform(boolean mirrorX, int sensorOrientation,
          RectF previewRect) {
        Matrix transform = new Matrix();
        // Need mirror for front camera.
        transform.setScale(mirrorX ? -1 : 1, 1);
        // Because preview orientation is different  form sensor orientation,
        // rotate to same orientation, Counterclockwise.
        transform.postRotate(-sensorOrientation);
        // Map rotated matrix to preview rect
        transform.mapRect(previewRect);
        // Map  preview coordinates to driver coordinates
        Matrix fill = new Matrix();
        fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);
        // Concat the previous transform on top of the fill behavior.
        transform.setConcat(fill, transform);
        // finally get transform matrix
        return transform;
    }

    private boolean hasNonZeroArea(RectF rect) {
        return rect.width() != 0 && rect.height() != 0;
    }
}

转换逻辑都在 previewToCameraTransform() 函数中,直接求Preview到Camera Driver的坐标转换,而不是像Android源码里面先反向求矩阵然后反转。

步骤为:

判读是否是前摄,是否需要镜像翻转

代码语言:javascript
复制
transform.setScale(mirrorX ? -1 : 1, 1)

将预览坐标旋转对应角度,使之和Camera Driver坐标长宽对应

代码语言:javascript
复制
transform.postRotate(-sensorOrientation)

将当前的Matrix操作作用于预览对应的矩阵上

代码语言:javascript
复制
transform.mapRect(previewRect)

通过 fill.setRectToRect()转换后,坐标已经完整映射到

最后将之前两种变换的Matrix结合起来,得到最终坐标变换的Matrix;

得到想要的Matrix后,击屏幕后,根据屏幕坐标构建一个Rect,通过调用toCameraSpace就得到了我们可以直接构造MeteringRectangleRect

注意:构造函数

代码语言:javascript
复制
public CoordinateTransformer(CameraCharacteristics chr, RectF previewRect)

中的 CameraCharacteristics chr,要区分不同Camera ID,前后摄不能弄错了

触发对焦操作

这个之前已经讲过了, 再重新贴下代码:

代码语言:javascript
复制
public void startControlAFRequest(MeteringRectangle rect,
                                        CameraCaptureSession.CaptureCallback captureCallback) {

    MeteringRectangle[] rectangle = new MeteringRectangle[]{rect};
    // 对焦模式必须设置为AUTO
    mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_AUTO);
    //AE
    mPreviewBuilder.set(CaptureRequest.CONTROL_AE_REGIONS,rectangle);
    //AF 此处AF和AE用的同一个rect, 实际AE矩形面积比AF稍大, 这样测光效果更好
    mPreviewBuilder.set(CaptureRequest.CONTROL_AF_REGIONS,rectangle);
    try {
        // AE/AF区域设置通过setRepeatingRequest不断发请求
        mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
    //触发对焦
    mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);
    try {
        //触发对焦通过capture发送请求, 因为用户点击屏幕后只需触发一次对焦
        mSession.capture(mPreviewBuilder.build(), captureCallback, mHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

上面有一点需要注意, 当设置触发对焦的Request:

代码语言:javascript
复制
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);

我们是通过 mSession.capture()触发一次对焦操作的,但在下次进行

mSession.setRepeatingRequest()

之前,需要将之前的触发对焦的Request给清除掉, 即设置:

代码语言:javascript
复制
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_IDLE);

如果不设置的话, 会造成连续不断的对焦

完整Demo

如果想看完整的可运行的Demo App和源码,可以查看Camera2 Demo:

https://github.com/smewise/Camera2

(文章来自简书--幽客:

https://www.jianshu.com/p/49dcab6a1f75)

~~END~~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 雪月清的随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要进行坐标映射
  • 使用Matrix进行坐标映射
  • 触发对焦操作
  • 完整Demo
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档