之前在 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
核心代码如下:
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
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源码里面先反向求矩阵然后反转. 步骤为:
- 判读是否是前摄, 是否需要镜像翻转
transform.setScale(mirrorX ? -1 : 1, 1);
- 将预览坐标旋转对应角度, 使之和Camera Driver坐标长宽对应
transform.postRotate(-sensorOrientation);
- 将当前的Matrix操作作用于预览对应的矩阵上,
transform.mapRect(previewRect);
此时得到的previewRect
逻辑上和mDriverRectF
已经对应了 - 通过
fill.setRectToRect()
转换后, 坐标已经完整映射到mDriverRectF
坐标系中了, 最后将之前两种变换的Matrix结合起来,transform.setConcat(fill, transform);
,得到最终坐标变换的Matrix.
得到想要的Matrix后, 击屏幕后, 根据屏幕坐标构建一个Rect, 通过调用 RectF toCameraSpace(RectF source);
, 就得到了我们可以直接构造MeteringRectangle(Rect rect, int meteringWeight)
的Rect
注意: 构造函数 public CoordinateTransformer(CameraCharacteristics chr, RectF previewRect)
中的 CameraCharacteristics chr
, 要区分不同Camera ID, 前后摄不能弄错了.
触发对焦操作
这个之前已经讲过了, 再重新贴下代码:
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:
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);
我们是通过 mSession.capture()
触发一次对焦操作的, 但在下次进行 mSession.setRepeatingRequest()
之前, 需要将之前的触发对焦的Request给清除掉, 即设置:
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
如果不设置的话, 会造成连续不断的对焦.
完整Demo
如果想看完整的可运行的Demo App和源码, 可以看下我写的Camera2 Demo:
https://github.com/smewise/Camera2