本文会介绍以下内容:
- 如何将相机画面映射到屏幕空间
- 做滤波器时,如何取邻近像素
<1> 相机捕获到屏幕空间
根据我的实践,相机捕获的画面如果不做任何处理,总是向左侧旋转 90 度的状态,言语太难描述,请看下图:
所以,我们不得不将图像旋转一下放置在画面中。
在屏幕坐标和 Metal 之间,通过设置 viewport 来连接两个坐标空间。因为在 Metal 中,所有的坐标都是 normalized 坐标,只考虑 2D 的情况,左下角是 [-1, -1] ,右上角是 [1, 1],原点位于正中间。而屏幕坐标则是原点位于左上角,正方向向右,向下。
我们需要通过 viewport 来告诉系统,屏幕上的哪块区域对应着 Metal 中的坐标空间。根据文档,viewport 使用的单位是 pixel,而不是 point,因此这里需要注意要额外的乘以屏幕的 scale 才行。
<1.1> 纹理坐标
在我们得到相机捕获的 frame 后,要将它转换成一个 Metal 体系内的 Texture,而描述 Texture 的某个位置,使用的是纹理坐标系统,我以上面的图举例,应该是这样的,纹理坐标原点位于左上角,取值范围是 [0, 1]。
<1.2> 顶点坐标与纹理坐标的对应
我们需要向 metal 提供 4 个顶点来摆放我们的纹理,显然这个 4 个顶点刚好就是位于坐标空间的 4 个顶点,为了能构成三角形展示矩形纹理,需要走一个 Z 字型。
为了能够将纹理正确的贴在对应的顶点上,也需要一一做一个对应,但是这个对应就可以完全按照我们的想法来了。
但对于前置摄像头,我们要映射的左右方向需要镜像一下,也只需要按需求改变映射端点即可。
<2> 滤波时邻近像素
在做滤波时,比如高斯滤波或者均值滤波,我们都需要得到邻近的像素点,由于在 metal 中所有的坐标都是浮点数,怎么知道邻近的像素点坐标呢?当时的我陷入思维盲区,后来请求别人才走出盲区。
想一想,如果要知道坐标 (0.5, 0.5) 相邻右边 1 个像素的坐标是多少,只需要再考虑一下当前的分辨率就可以了。假设分辨率是 100 * 100,那么 (0.5, 0.5) 就是点 (50, 50) ,那么 (51, 50) 呢?不就是 51 / 100 吗?
所以这个问题其实还简单的。
这时遇到了另一个问题,我发现我的画面的 y 轴方向,总是有一些残影,但是 x 方向就没有,显然是我的坐标出现了一些问题。经过我的排查和尝试,找到了问题,最后在与家人散步的路上,想明白了此问题。
一开始我的代码是这样的。
int width = 1080;
int height = 1920;
float newX = (textureCoordinate.x * width + 1) / width;
float newY = (textureCoordinate.y * height + 1) / height;
后来我通过调换 width 和 height 的值解决了此问题,原来还是坐标问题。参考上面的图,虽然在屏幕上,我是想向右读取下一个像素的位置,但是在纹理坐标上,是向下。而这里采样也是在纹理坐标上采样的。
所以调换顺序得以解决此问题,希望踩到的坑能帮助到大家。