双目立体校正

1. 为什么要进行立体校正

在2-View Geometry中,假设我们有C_1C_2两个相机,p_1p_2是空间中的三维点投影到不同相机上的像素点。在本文中,我们称p_1p_2为对应的。 我们可以通过三角化的方法来得到P在三维空间中的坐标。而求解三角化问题需要已知相机内参、外参还有在像素点之间的对应关系。

如何在两张不同的照片中寻找相对应的两个点是有难度的。给定p_1的情况下,在另一张图片上遍历所有像素点搜索p_2在计算上是非常昂贵的。 幸运的是,我们可以用对极约束(epipolar constraint)将搜索问题简化到一维上。根据对极几何的知识,我们可以知道的p_1对应点p_2一定是在另一张图片的极线上面的。

但是因为大部分情况下极线是倾斜的,使用图像块去匹配的时候,往往效率不高,最方便的情况是能将极线与基线平行,即保持水平。因此我们需要进行立体校正Stereo Rectification。下图为通常情况下沿极线搜索对应点的示意图。

2. 立体校正推导

我们可以认为虚构了两个具有以下特性的相机 两个相机是完全相同的,包括内参。 因为焦距相同,所以成像平面是共面的。 因为内参相同,对应点在同一水平线上,加速搜索。 基线必须平行于相机新的x轴,这样可以保证极线平行。 * 极线是平行的,因此极点在无穷远处。

我们通过单应变换来将原先的图片变换到新的虚拟相机的平面上。因此重点是求左右两边相机对应的单应变换。我们接下来就来推导这个单应变换。 一般我们通过旋转矩阵R=R_{cw}和平移向量

t=t_{cw}来将点从世界坐标系变换到相机坐标系,此时P_c = RP_w +t其中P_c是点在相机坐标系中的坐标,P_w是点在世界坐标系中的坐标。我们一般会将透视投影写成以下的形式。

在之后,我们写出左右两个相机的透视投影关系式。

我们假设了两个完全相同的虚拟相机,他们的成相平面是共面的(相同的外参\hat{R}),同时他们具有相同的内参\hat{K}。此时我们将世界中的点P投影到我们假设的两个相机平面上,可以得到如下的关系式。

经过观察,我们可以发现新的相机和旧的相机投影关系式有相同的公共部分

我们通过提取这个公共部分可以推导出以下的关系式。

这就是我们在立体校正过程中,将原图像变换到新的虚拟相机上的单应变换。

此时仅需要求解虚拟相机的内参\hat{K}以及旋转矩阵\hat{R}

3. 立体校正流程

先进行图片的去畸变。

再计算立体校正需要的单应变换,使用双线性插值完成变换。

4. OpenCV中的StereoRectify

4.1. cv::fisheye::stereoRectify

cv::fisheye::StereoRectify()函数, 主要用于对双目图像做出矫正,计算出用于立体矫正的参数;具体的使用方法如下:

void cv::fisheye::stereoRectify	(InputArray K1,InputArray D1,
                                 InputArray K2, InputArray D2,
                                 const Size &imageSize, InputArray R, 
                                 InputArray tvec, 
                                 OutputArray 	R1,OutputArray 	R2,
                                 OutputArray P1, OutputArray P2, OutputArray Q, 
                                 int flags, const Size &newImageSize = Size(), 
                                 double balance = 0.0, double fov_scale = 1.0)	

其中参数的含义如下:

K1第一个相机的内参,Size为3x3, 数据类型为CV_32F 或者 CV_64F
D1第一个相机的畸变参数, Size必须为4x1, 数据类型为CV_32F 或者 CV_64F
K2第二个相机的内参,Size为3x3, 数据类型为CV_32F 或者 CV_64F
D2第二个相机的畸变参数, Size必须为4x1, 数据类型为CV_32F 或者 CV_64F
imageSize做双目标定StereoCalibration() 时用的图片的size, 如ImageSize = cv::Size(640,480)
R两个相机之间的旋转矩阵, Rrl, 如果内参采用Kalibr标定, 那么这里的R就是Kalibr标定出的T的前3x3
tvec两个相机之间的平移向量,trl, 即为左目相机在右目相机坐标系中的坐标, 所以,如果两个相机左右摆放, 该向量中x值一般为负数;
R1第一个相机的修正矩阵, 即从实际去畸变后的左目摆放位姿到经过极线矫正后的左目位姿之间, 有一个旋转量,为R1
R2第二个相机的修正矩阵, 即从实际去畸变后的右目摆放位姿到经过极线矫正后的右目位姿之间, 有一个旋转量,为R2
P1修正后第一个相机的投影矩阵; P1包含了R1和K1, 可直接将左目相机坐标系的三维点,投影到像素坐标系中; 要注意会投影到修正后的图像中
P2修正后第二个相机的投影矩阵; P2包含了R2和K2, 可直接将左目相机坐标系的三维点,投影到像素坐标系中; 要注意会投影到修正后的图像中
Q视差图转换成深度图的矩阵; 
flagsOperation flags that may be zero or fisheye::CALIB_ZERO_DISPARITY . If the flag is set, the function makes the principal points of each camera have the same pixel coordinates in the rectified views. And if the flag is not set, the function may still shift the images in the horizontal or vertical direction (depending on the orientation of epipolar lines) to maximize the useful image area.
newImageSize修正后图像的新Size.  该参数应该与下一步使用initUndistortRectifyMap()时所使用的iMAGE SIZE一致. 默认为 (0,0), 表示和 imageSize 一致. 当图像的径向畸变较严重时, 这个值设置的大一点,可以更好地保留一个细节;  (see the stereo_calib.cpp sample in OpenCV samples directory)
balance值在[0,1]之间, 设置这个值可以改变新图像的focal length, 从最小值到最大值之间变动;
fov_scale新的focal length = original focal length/ fov_scale

使用示例

//设定左目内参和畸变参数: 分别是3x3和4x1
Mat cameraMatrixL =(Mat_<double>(3, 3) << 216.0891385028069, 0., 319.19103592168216, 0.,286.915780332698, 237.28788884900933, 0., 0., 1.);
 
Mat distCoeffL =(Mat_<double>(4, 1) << 0.16031814840882294, 0.09948097914060017,
                                      -0.05647543763319335, 0.02313587059407878);
//设定右目内参和畸变参数: 分别是3x3和4x1
Mat cameraMatrixR =(Mat_<double>(3, 3) << 216.47145152004367, 0., 319.57751832884156, 0.,287.23866506549973, 240.30796467665027, 0., 0., 1.);
 
Mat distCoeffR = (Mat_<double>(4, 1) << 0.15400500709721424, 0.109194432654468,
                                      -0.06512886784397008, 0.025788980687450808);
//设定两个相机之间的旋转和平移, R为Rrl
Mat R = (Mat_<double>(3, 3) << 
        0.9998867545031103, -0.007295111520593036,0.013162808102250304, 
        0.007472043953567141, 0.9998817186236534,-0.01344311427322331, 
        -0.013063182169384159, 0.013539944981760093,0.9998229959155261);
//从平移的数值应该能看出来, t为 左目相机在右目相机的坐标系中的位置
Mat T = (Mat_<double>(3, 1) << 
        -0.10139343341319906, -0.0003237508769501881,0.0013986876758678593);
 
Mat Rl, Rr, Pl, Pr, Q;
 
cv::fisheye::stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR, imageSize,R, T, Rl, Rr, Pl, Pr, Q, CALIB_ZERO_DISPARITY, imageSize);
//使用R1,P1输出两个映射矩阵
cv::fisheye::initUndistortRectifyMap(cameraMatrixL, distCoeffL, Rl, Pl, 
                                     imageSize,CV_32FC1, mapLx, mapLy);
//使用R2,P2输出两个映射矩阵
cv::fisheye::initUndistortRectifyMap(cameraMatrixR, distCoeffR, Rr, Pr, 
                                     imageSize,V_32FC1, mapRx, mapRy);
Mat ImageL, ImageR;
ImageL = cv::imread("left.png",-1);
ImageR = cv::imread("right.png", -1);
//将双目矫正后的图像放入rectifyImageL2和rectifyImageR2中
Mat rectifyImageL2, rectifyImageR2;
cv::remap(ImageL, rectifyImageL2, mapLx, mapLy, cv::INTER_LINEAR);
cv::remap(ImageR, rectifyImageR2, mapRx, mapRy, cv::INTER_LINEAR);

4.2. cv::stereoRectify

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/calib3d/calib3d.hpp>


int main( int argc, char* argv[] )
{
   cv::Mat K0 = (cv::Mat_<double>(3,3) << 1457.572438721727, 0, 1212.945694211622, 0, 1457.522226502963, 1007.32058848921, 0, 0, 1);
   cv::Mat kk0 =  cv::Mat_<double>::zeros(1,5);
   cv::Mat K1 = (cv::Mat_<double>(3,3) << 1460.868570835972, 0, 1215.024068023046, 0, 1460.791367088, 1011.107202932225, 0, 0, 1);
   cv::Mat kk1 =  cv::Mat_<double>::zeros(1,5);

   cv::Mat R = (cv::Mat_<double>(3,3) << 0.9985404059825475, 0.02963547172078553, -0.04515303352041626, -0.03103795276460111, 0.9990471552537432, -0.03068268351343364, 0.04420071389006859, 0.03203935697372317, 0.9985087763742083 );

   cv::Mat T = (cv::Mat_<double>(3,1) << 0.9995500167379527, 0.0116311595111068, 0.02764923448462666 );

   cv::Size imgsize( 2456, 2058 );

   cv::Mat R1;
   cv::Mat R2;
   cv::Mat P1;
   cv::Mat P2;
   cv::Mat Q;

   cv::Rect RL;
   cv::Rect RR;

   cv::stereoRectify( K0, kk0, K1, kk1, imgsize, R, T, R1, R2, P1, P2, Q, 0, 1.0, imgsize,  &RL, &RR );

   std::cout << "Results with OpenCV " << CV_VERSION_MAJOR << "." << CV_VERSION_MINOR << "." << CV_VERSION_REVISION << std::endl;
   std::cout << "R1 = " << R1 << std::endl;
   std::cout << "R2 = " << R1 << std::endl;
   std::cout << "P1 = " << R1 << std::endl;
   std::cout << "P2 = " << R1 << std::endl;
   std::cout << " Q = " << Q << std::endl;
   std::cout << "RL = " << RL << std::endl;
   std::cout << "RR = " << RR << std::endl;

   return 0;
}

双目视觉之立体校正的过程是什么? - 知乎

正确使用StereoRectify_三轮车的视觉进阶_的博客-CSDN博客_stereorectify

cv::stereoRectify gives different results between version 3.4.0 and 3.4.1 · Issue #11131 · opencv/opencv · GitHub

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值