最近因为种种原因,要在手机上实现VR双目渲染的输出,原本想直接使用Google的Cardboard VR SDK,奈何折腾了一通之后发现,Google的双目设置界面没法修改,且如果要适配新的眼镜,需要在Google网站上生成Viewer Profile。又在网上没有找到相关的反畸变开源代码,不得不一头扎进基本的算法原理中去。
搞定之后小小总结一下。
什么是畸变
在几何光学中,畸变是与直线投影的偏差,它是光学像差(即光经过透镜后产生的图像失真或模糊)的一种形式。而在VR眼镜中,我们所涉及的畸变都是径向畸变,即产生的图像变形都是径向对称的,这也是由摄像镜头的径向对称性所决定的。径向畸变通常有三种形式:
1、 桶形畸变
即图像放大率随着距光轴的距离而减小。在这样的畸变中,直线在中心向外凸出,像桶一样。
2、 枕形畸变
图像放大率随着距光轴的距离而增加。在这样的畸变中,直线穿过图像向内弯曲,正方形的四个角外发拉长,像枕头一样。
3、 胡子畸变
胡子畸变是上面两种类型的混合物,在这样的畸变中,它开始于靠近图像中心的桶形失真,并逐渐变成朝向图像周边的枕形失真,使得框架上半部分的水平线看起来像车把胡须。
在数学上,除了胡子畸变以外,桶形和枕形畸变是二次的,这意味着它们随着距中心的距离的平方而增加。在VR中,我们只需要关注桶形和枕形畸变。
VR眼镜中的畸变
在VR眼镜中,为了获取得沉浸式的体验,需要给用户提供比较大的视场角(FOV),作为廉价的VR方案,肯定不可能在用户面前放上一个超大的曲面显示屏。替代方案就是在用户眼前小的显示屏上放置一个放大透镜。
效果如下图所示:
这样有个靠近眼睛的透镜,可以让用户获得比较大的视场角,但这样做的代价就是光线经过透镜后会产生径向畸变,视角场越大,畸变就会越严重。
如下图所示,正常的图像经过放大透镜后会产生枕形畸变。
VR眼镜中的畸变纠正办法
要使得人眼通过放大透镜看到正常的图像,其解决办法就是提前对显示屏上输出的图片进行畸变纠正,即反向畸变。
将输出到显示屏上的图像转换成对应的桶状畸变,这样经过透镜到达人眼之后,就会反向形成正常的图像了。
畸变公式
根据网上找到的资料,Zhang(1999) 证明了具有两个系数的多项式径向畸变模型可以模拟为如下公式:
但这个4次方的计算太麻烦了,一般情况下都是用的另一个,即费兹本(2001)近似径向畸变模型:
其中 ru 和 rd 分别是非畸变和畸变后的图像中心距离。α 是每一个镜片固有的常量系数。
算法实现
由于费兹本公式中的r是围绕光轴所在图像中心点进行的计算,要使用这个公式,我们首先需要先进行坐标归一化,将图像中的坐标系向这个公式中的坐标系进行映射。
如下图所示,需要将左图的坐标映射为右图的座标。
(xn,yn) 代表归一化后的坐标系,w 和 h 代表宽和高。
在使用公式完成反向畸变计算后,再进行坐标反向映射。
搞清楚了上面这些东西,在Unity中,使用一个简单的片元着色器就可以方便的搞定了,参考代码如下:
// 镜片的畸变常量系数float alpha = 0.1f;
// 将坐标向费兹本公式坐标转换
float x = (2.0 * i.uv.x - 1.0) / 1.0f;
float y = (2.0 * i.uv.y - 1.0) / 1.0f;
// 将UV坐标转换为反向畸变后的坐标
float r = x * x + y * y;
float x3 = x / (1.0 - alpha * r);
float y3 = y / (1.0 - alpha * r);
float x2 = x / (1.0 - alpha * (x3 * x3 + y3 * y3));
float y2 = y / (1.0 - alpha * (x3 * x3 + y3 * y3));
// 将坐标进行反向转换
float i2 = (x2 + 1.0) * 1.0 / 2.0;
float j2 = (y2 + 1.0) * 1.0 / 2.0;
__________________________________________________
BY:HW-ZPP