简介
上图是几种双目视觉系统的例子,一般而言,双目视觉系统中的两颗摄像头的主光轴会尽量平行。上图中下方的三幅图分别表示左摄像机和右摄像机看到的画面,以及经过计算后得到的深度图。
双目视觉系统应用领域非常广泛,比如机器人和自动驾驶领域。
上图展示了一个双目视觉系统的概览。第一步是做相机的离线标定,获得相机内外参。第二部是进行立体校正,主要是做极线校正。
双目校正
我们如果得到了左右两幅图像,已知在左图上有一点,如何能快速地找到右图中该点的同名点?根据对极几何的极线约束,我们知道只需要搜索右图中同名点对应的极线就可以了,不需要在整个图像上进行搜索。
上图展示了一个实际中可能出现的例子,左右两个摄像机的成像平面并不是完全处于同一水平面上,此时如果要找x在右图的同名点,可以根据x和e算出左图中极线的位置,根据两个成像平面的几何关系,可以算出右图中极线。可以参考之前关于二视图重建的笔记。这种方法虽然可行,但在实际处理中,由于右图的极线是倾斜的,在离散的像素坐标系上并不太好处理,并且每得到一个左图中的点,我们都需要计算左右成像平面中对应的极线。我们希望能得到这样一种结果:通过对左右图像经过特殊变换,能达到如下效果:
变换后的左右像平面同名点都处于同一列,这样在搜索同名点的时候,只需要拿到左图x点的y坐标,在右图对应的y坐标上搜索即可。这就是双目立体视觉中极线校正的目的。来看一个实际的例子:
对于已经做过极线校正的图像,要重建3D的点,过程一般如下:
三角测量的原理如上图,假设以左相机为基础坐标系,则有相机的矩阵P'和左图像之前相差一个[R|t]变换,通过这两个相机矩阵可以算出两个同名点链接两个光心的射线,射线交于一点(理想情况下交于一点,实际中存在误差大概率无法相交,需要进行额外计算),这个点就是该点在三维世界中对应的位置点。
对于已经做了极线校正的情况,计算3D点位置会更简单,如下所示:
上图中表示Reference成像平面下x的坐标以及Target成像平面上x的同名点的坐标。一般来说,都是以左相机的成像平面作为参考平面,因此视差(Disparity)也可以写成。关于上面的式子详细推导,可以参考下面的文章:
上面的X,Y很容易推出,以X为例子, 其实就代表了光轴所代表的直线(坐标轴的X轴)和光心与形成的直线之间的夹角的正切值,用Z乘上这个正切值就能得到对应的X坐标(画个图就理解了)。
我们如何能使得极线变得水平呢?
首先回顾一下没有做极线校正前的情况:
左成像平面中的一点x,通过射影变换后就能得到右成像平面上的同名点x'的位置。
假设做完了极线校正,同名点在同一个水平线上是怎么推导出来的呢?
当左右成像平面的极线处于同一平面,并且和基线平行时,极点位置在无穷远处。上图中,我们以左成像平面作为参考平面,则有上图红框中的关系,旋转矩阵R是单位阵I。对于右成像平面来说,相当于是将左成像平面沿着X方向平移了T个单位, 因此右图像的旋转矩阵R也是单位阵I,平移量是(T,0,0)。 当我们有了R和t,就能构建出本质矩阵E。本质矩阵能够将左右像平面上的点关联起来,总是满足,进一步,将像平面的点x的(u,v)和x'的(u',v')坐标带入:
将上图的式子的点乘写出来并整理,可以得到:
这个式子可以看出,做完极线校正后,空间中的任意一点,在左右成像平面中的y坐标是一样的,处于同一条水平线上。
这样我们就引入了立体校正的概念。
上图中,灰色的平面表示未校正之前的情况,黄色平面表示校正之后的情况。
那么如何进行极线校正呢?
以下图为例,在未进行立体校正前的情况:
首先旋转右相机,让它和左相机坐标系的坐标轴两两平行。
旋转后,得到如下结果(右图像和左图像平行):
旋转左右成像平面,使得极点在无穷远处
旋转后得到如下结果:
最后是调整尺度:
下面从数学上看上面的步骤
首先我们要计算出本质矩阵E,通过E得到R
在二视图重建笔记中有讲到,四种解中只有一种情况是成像点都在两个相机前面:
得到R矩阵后,就可以对右相机平面进行旋转了,接下来我们需要从极点e得到校正矩阵,应用这个矩阵可以将极点放到无穷远点。
综上,立体校正的算法步骤如下所示:
在做完立体校正后,如何应用?
得到校正后的结果后,由于两幅图像的形状发生了变化,一般会剪裁出一块特定矩形区域作为立体匹配的基础左右视图进行后续立体匹配操作。
在极线上匹配到左右图的同名点后,就能生成出视差图。
下图是一个实际的案例:
对于双目深度检测,一般步骤如下:
对于2.b这个步骤,一般我们会设置一个搜索窗口,用这个窗口在极线上进行滑动匹配。
如下图,要找到左图中衣领纽扣在右图中的同名点,搜索时会沿着右图对应扫描线(蓝色)上进行滑动计算匹配度。
在实际应用中,找同名点会碰到很多问题,比如弱纹理或无纹理,重复的图案模式,镜面反射产生的高光,深度不连续(如遮挡)等:
根据深度的计算公式,视差变大,所对应的深度会越小,而且变化并非线性变化。由于视差本身是按照像素的个数差异来算(离散值),因此计算所得的深度值变化如下图所示:
这样的结果会使得视差即使只差一个像素,深度也会有很大的跳变。因此实际中会对视差值进行插值计算(比如使用浮点型而非整型)。
对于代价计算和聚合,有很多方法,下面是一种最简单的例子:
关于视差优化,可以参考下面这篇文章: