前言
在讲解坐标系的转换之前,我们需要知道一点简单而又重要的知识点。
相机镜头方向跟输出流(预览输出流、图片输出流)是存在夹角的
后置摄像头的角度一般为90度
前置摄像头的角度一般为270度
Camera坐标系其实就是基于相机镜头方向基准的坐标系
图解镜头方向与输出流的夹角
假设一个人要给一个人进行拍照,不同视角呈现的效果是完全不同的,如下图
这是就有些人会问了,为啥拍照的时候,相机显示的方向却一样是人眼的视角的方向呢?按照你这么说不应该呈现的是摄像头的视角方向吗?
但是我的回答,“咱就是说有没有可能相机就是人类设计的,我们在设计预览的时候就人为把摄像头视角方向的样子转换成了人眼视角的模样”,专业点说就是,假设我们采用的是TextureView作为预览的空间,textureview空间内部就自动将摄像头方向的成像转成了人眼视角的自然方向进行成像 。
预览坐标系与Camera坐标系的图解
有了上面的理解之后,View坐标系与Camera坐标系就很好理解。接下来我会画出Camera跟Camera2的坐标系,大家可以对比来进行学习。
上面图画的前提是默认后置摄像头以及竖屏的情况下的预览坐标系以及Camera2坐标系。
可能不太了解的人会觉得,这个Camera2坐标系有什么用,但其实这个坐标系作用的地方还是挺多的,例如触摸对焦或触摸曝光收敛这两个操作,就需要将预览坐标系上的触摸坐标转换成Camera坐标系的坐标,因为在Camera HAL在处理AE跟AF的时候是只认识Camera坐标系的。
接下来我将从代码层面一步一步解释一下应该怎么代码转换。
代码实现坐标系的转换
下面这个工具类是为了实现触摸对焦时候,将触摸点的预览坐标系转换成Camera2坐标系的工具类。使用方法也是很简单,直接new声明,通过构造函数传入两个参数,一个是当前摄像头的CameraCharacteristics,以及预览视图的一个RectF(其实就是拿到textureview的长宽,根据长宽构建的RectF)。假设我们点击一个地方,会基于预览坐标系产生一个rect,我们如果要将这个rect转成Camera2的坐标系,只需要调用CoordinateTransformer对象的toCameraSpace()方法进行转换就行。使用就是这么简单,但是接下来我将从代码的层面看看到底是怎么转换的。
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方法就知道整个转换的流程,我这里将几句重要的代码进行图解一下。
重要代码:
// 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);
首先看第一句:transform.setScale(mirrorX ? -1 : 1, 1);
这一步其实是判断是否是前摄像头,如果是前置就需要将Matrix进行镜像一下。
第二句:transform.postRotate(-sensorOrientation);
这一步是初步把Matrix进行旋转,现在可能还没有看出效果,这里需要对matrix有一个理解就是,Matrix其实本质上就是一个矩阵,他会将一些操作以改变矩阵中的数值的形式进行存储,本身不会有什么改变,只能在别人使用它的时候帮别人按照保存的值进行改变,有点像一个工具,比如说类比手机,手机有打电话的功能,但是没有人使用他也不能使用打电话功能。
第三句: transform.mapRect(previewRect);
我们来看看mapRect方法是做什么的吧,下面这段代码出自Matrix.mapRect()理解 - Matrixin - 博客园 (cnblogs.com)
其实就是将Rect矩形映射到matrix设置好的对应变化中。代码实现图解具体如下图
第四句:fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);
我们先来看看setRectToRect()方法是做什么的,下图来自Matrix.setRectToRect - 右耳Deng - 博客园 (cnblogs.com)
Android Matrix 方法详解(另类) - 简书 (jianshu.com)
下面这个图是不同第三个参数填充的效果
而回到我们这个代码其实就是将previewRect填充到Active array size的Rect,如下图
看着不是很直观,我们还是从宏观的角度来看
自此坐标系转换成功,将tranform,fill矩阵进行相乘得到最后效果的Matrix (transform.setConcat(fill, transform)),将tranform返回