双目相机模型与标定

单目相机无法恢复真实尺度,使得双目相机成为了很多开发者的选择。

双目相机模型

双目恢复深度进行三维重建的原理与人眼类似,为了进一步分析双目测距的原理,可以从下图深入理解双目相机模型:

stereo

双目相机一般由左眼和右眼两个水平放置的相机组成。上图中左右两个相机都是使用针孔相机模型,两个相机的成像平面在同一个水平面上,同时两个相机光心是水平放置的,因此对于一个P点,其在两个图像上的投影点的行坐标是相同的,我们通过对其纵坐标的处理便可以得到视差了。

图中两个相机光心之间的距离b被成为双目相机的基线,是与焦距f一样重要的参数。

根据上图,假设一个空间点P,在左右目各成一像\(P_L\)\(P_R\),分别以两个光心垂直的位置为二维坐标原点,则\(P_L\)的横坐标为\(u_L\)\(P_R\)的横坐标为\(u_R\),可以发现\(u_R\)是一个负数。根据上图中右侧三角形相似的原理,可以得到下面公式

\[\frac{z-f}{z}=\frac{b-u_{L}+u_{R}}{b}\]

经过整理,可以得到

\[ z = \frac{fb}{d}, d= u_L -u_R \]

此处d为投影点在左右图上的横坐标之差,也叫作视差,因此,根据视差我们就可以估计出这个空间点的深度了。此处可以发现,视差最小也就是1个像素,因此双目可以测量的最大深度是有上限的,而这个值和\(fb\)有关,所以一般相机焦距f确定的时候,双目基线越大,可测量的最远深度也越大。

由于以上双目相机模型的特殊,我们不仅可以方便的估计深度,也可以在双目匹配时很容易的进行极线(横坐标一致的水平线)搜索匹配,大大减小匹配计算量。

双目立体标定

上面讲述了双目相机模型的原理,我们可以发现这是一个很苛刻的条件,要求两个相机光心在同一水平线上,并且成像平面平行。这个在双目相机的制作中很难做到。因此在获得一个双目相机后,需要对其进行双目标定,得到两个\(3*4\)的投影矩阵H1和H2,在两个相机成像分别使用这两个矩阵进行投影后得到两个新的矫正后的图像,此时校正后的两个图像应该是满足上面的双目相机模型的。整个矫正过程如下所示

stereo

opencv进行双目标定

知道内参

对于一个已经标定的双目相机(已知内参、畸变参数等),在opencv中可以使用cv:StereoRectify来完成双目矫正矩阵的获得(不只是矫正矩阵,还有两个相机之间的位姿变换,视差到深度的4*4映射矩阵等);

这种方法也称为(Bouguet)方法,需要先分别对两个相机进行标定,分别得出两个相机的内参数矩阵,然后再进行立体标定,进行立体图像的矫正。具体可以去opencv官网查看,函数介绍比较清晰。

不知道内参

对于一个没有标定的双目相机,可以使用opencv中cv::stereoRectifyUncalibrated函数来获得双目的矫正矩阵H1和H2,分别使用这两个矩阵对图像做单应变换,即可矫正图像。

这种方法也叫非标定方法也称为(Hartley)方法,有时候我们不知道相机的内参矩阵,而且也不用知道内参数具体是多少,因为我们仅关心如何得到两幅图像的稠密匹配,或者两幅图像之间的视差图或者深度图就足够了。因为不知道相机的内参数,所以只能借助对极约束来解决问题了,通过计算两幅图像的基础矩阵F,然后利用对极约束条件中对极线为平行且行对准,可以很好的实现目标。

stereo

第三个参数是本质矩阵可以通过cv::findFundamentalMat获得,需要说明一点,该函数的前两个参数Mat& points1, Mat& points2cv::findFundamentalMat的前两个参数并不是相同的数据结构。它们虽然可以是同一个匹配点集,但是他们的数据结构是完全不同的!cv::findFundamentalMat中传入的匹配点集要求是2xN或者Nx2的矩阵,但是cv::stereoRectifyUncalibrated中要求传入的匹配点集必须是1x2N或者2Nx1的矩阵!在很多文档中都说他们的参数是一样的,这其实是一个天大的错误,如果用计算F的匹配点集直接传给图像矫正函数,程序将直接崩溃。正确的做法是利用cv::Mat的构造函数,直接从vector构造一个cv::Mat传入。

cv::stereoRectifyUncalibrated函数默认原始图像是没有径向畸变的,因此在矫正图像之前,最好先对原始图像做径向矫正。另外需要注意的一点,函数返回的单应变换矩阵H1和H2都是double类型,也即CV_64F类型,若不是该类型的矩阵,与之相乘会报错。

生成映射矩阵

在双目立体标定之后,需要根据标定结果生成新的映射表,然后通过映射表完成矫正。

映射表的生成使用如下函数,这个函数需要使用两遍,如下这边是得到左目的映射表,这个映射函数的原理可以查看opencv的docs,关于该函数有详细的公式推导

1
2
3
4
5
6
7
8
9
cv::initUndistortRectifyMap(            //计算无畸变和修正转换映射
K_l, //输入:相机内参矩阵 (单目标定阶段得到的相机内参矩阵)
D_l, //输入:单目标定阶段得到的相机的去畸变参数
R_l, //输入:可选的修正变换矩阵,3*3, 从 cv::stereoRectify 得来.如果这个矩阵为空矩阵,那么就将会被设置成为单位矩阵
P_l.rowRange(0,3).colRange(0,3), //输入:新的相机内参矩阵(后续根据这个矩阵来进行投影变换,畸变已经没有了)
cv::Size(cols_l,rows_l), //在去畸变之前的图像尺寸
CV_32F, //第一个输出映射的类型,映射x
M1l, //输出:第一个输出映射表,映射y
M2l); //输出:第二个输出映射

得到映射表后,可以通过如下函数完成映射:

1
2
3
4
5
6
cv::remap(              //重映射,就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。
imLeft, //输入图像
imLeftRect, //输出图像
M1l, //第一个映射矩阵表, 作用是对于输出图像某个位置(x,y),M1l(x,y)处的值为一个x1坐标
M2l, //第二个映射矩阵,作用是对于输出图像某个位置(x,y),M2l(x,y)处的值一个y1坐标, remap函数将输入图像(x1,y1)处的值赋值给输出图像(x,y)位置处
cv::INTER_LINEAR);

至此,大功告成得到的imLeftRect便是修正后的左目图像了,与同样修正后的右目图像满足上面介绍的双目相机模型

参考文献

  • [1]. 视觉slam十四讲
  • [2]. https://blog.csdn.net/jaccen/article/details/51217936
  • [3]. https://docs.opencv.org/4.2.0/d9/d0c/group__calib3d.html#gaadc5b14471ddc004939471339294f052
  • [4]. https://blog.csdn.net/u010368556/article/details/86484695

双目相机模型与标定
http://line.com/2020/08/08/2020-08-08-stereo-vision/
作者
Line
发布于
2020年8月8日
许可协议