- 操作系统:ubuntu22.04
- OpenCV版本:OpenCV4.9
- IDE:Visual Studio Code
- 编程语言:C++11
算法描述
为已校准的立体相机的每个头计算校正变换。
cv::stereoRectify 是 OpenCV 中用于立体校正的函数,它基于已知的相机参数和相对位置(通过 cv::stereoCalibrate 或其他方法获得),计算出两个相机的投影矩阵和重映射变换,使得从两个相机获取的图像能够被矫正为仿佛它们是并排且光学轴平行的状态。这样做的目的是为了简化后续的视差图计算。
函数原型
void cv::stereoRectify
(
InputArray cameraMatrix1,
InputArray distCoeffs1,
InputArray cameraMatrix2,
InputArray distCoeffs2,
Size imageSize,
InputArray R,
InputArray T,
OutputArray R1,
OutputArray R2,
OutputArray P1,
OutputArray P2,
OutputArray Q,
int flags = CALIB_ZERO_DISPARITY,
double alpha = -1,
Size newImageSize = Size(),
Rect * validPixROI1 = 0,
Rect * validPixROI2 = 0
)
参数
- 参数cameraMatrix1:第一个相机的内参矩阵。
- 参数distCoeffs1:第一个相机的畸变参数。
- 参数cameraMatrix2:第二个相机的内参矩阵。
- 参数distCoeffs2:第二个相机的畸变参数。
- 参数imageSize:用于立体校准的图像尺寸。
- 参数R:从第一个相机坐标系到第二个相机坐标系的旋转矩阵,详见 stereoCalibrate。
- 参数T:从第一个相机坐标系到第二个相机坐标系的平移向量,详见 stereoCalibrate。
- 参数R1:第一个相机的输出3x3校正变换(旋转矩阵)。该矩阵将未校正的第一个相机坐标系中的点转换为校正后的第一个相机坐标系中的点。更技术性地说,它执行了从未校正的第一个相机坐标系到校正后的第一个相机坐标系的基础变换。
- 参数R2:第二个相机的输出3x3校正变换(旋转矩阵)。该矩阵将未校正的第二个相机坐标系中的点转换为校正后的第二个相机坐标系中的点。同样地,它执行了从未校正的第二个相机坐标系到校正后的第二个相机坐标系的基础变换。
- 参数P1:第一个相机在新的(校正后)坐标系中的输出3x4投影矩阵,即它将校正后的第一个相机坐标系中的点投影到校正后的第一个相机的图像中。
- 参数P2:第二个相机在新的(校正后)坐标系中的输出3x4投影矩阵,即它将校正后的第一个相机坐标系中的点投影到校正后的第二个相机的图像中。
- 参数Q:输出4×4视差到深度映射矩阵(见 reprojectImageTo3D)。
- 参数flags:操作标志,可以是零或 CALIB_ZERO_DISPARITY。如果设置了该标志,则函数会使每个相机的主点在校正后的视图中具有相同的像素坐标。如果没有设置该标志,函数可能会沿水平或垂直方向(取决于极线的方向)移动图像,以最大化有用图像区域。
- 参数alpha:自由缩放参数。如果它是 -1 或未指定,函数将执行默认缩放。否则,参数应在 0 和 1 之间。alpha=0 表示校正后的图像被缩放和移动,使得只有有效的像素可见(校正后没有黑色区域)。alpha=1 表示校正后的图像被减少和移动,使得来自原始图像的所有像素都保留在校正后的图像中(不丢失源图像像素)。任何中间值都会产生这两个极端情况之间的中间结果。
- 参数newImageSize:校正后的新图像分辨率。应该传递给 initUndistortRectifyMap(见 OpenCV 示例目录中的 stereo_calib.cpp 样本)。当传递 (0,0)(默认值)时,它被设置为原始 imageSize。设置为更大的值可以帮助保留原始图像中的细节,尤其是在存在较大径向畸变的情况下。
- 参数validPixROI1:可选输出矩形,在校正后的图像中包含所有有效像素的区域。如果 alpha=0,则 ROI 覆盖整个图像。否则,它们可能较小(见下图)。
- 参数validPixROI2:同上,适用于第二个相机。
该函数计算每个相机的旋转矩阵,这些矩阵(虚拟地)使两个相机的图像平面成为同一平面。因此,这使得所有的极线平行,从而简化了密集立体对应问题。函数以 stereoCalibrate 计算的矩阵作为输入,并提供两个旋转矩阵以及两个新坐标系中的投影矩阵作为输出。根据相机的相对位置,函数区分以下两种情况:
水平立体
第一个和第二个相机视图主要沿 x 轴相对偏移(可能有小的垂直偏移)。在校正后的图像中,左右相机中的对应极线是水平的并且具有相同的 y 坐标。P1 和 P2 的形式如下:
P1
=
[
f
0
c
x
1
0
0
f
c
y
0
0
0
1
0
]
\texttt{P1} = \begin{bmatrix} f & 0 & cx_1 & 0 \\ 0 & f & cy & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix}
P1=
f000f0cx1cy1000
P2
=
[
f
0
c
x
2
T
x
⋅
f
0
f
c
y
0
0
0
1
0
]
,
\texttt{P2} = \begin{bmatrix} f & 0 & cx_2 & T_x \cdot f \\ 0 & f & cy & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} ,
P2=
f000f0cx2cy1Tx⋅f00
,
Q
=
[
1
0
0
−
c
x
1
0
1
0
−
c
y
0
0
0
f
0
0
−
1
T
x
c
x
1
−
c
x
2
T
x
]
\texttt{Q} = \begin{bmatrix} 1 & 0 & 0 & -cx_1 \\ 0 & 1 & 0 & -cy \\ 0 & 0 & 0 & f \\ 0 & 0 & -\frac{1}{T_x} & \frac{cx_1 - cx_2}{T_x} \end{bmatrix}
Q=
10000100000−Tx1−cx1−cyfTxcx1−cx2
其中 Tx 是相机之间的水平偏移,如果设置了 CALIB_ZERO_DISPARITY,则cx1=cx2。
垂直立体
第一个和第二个相机视图主要沿垂直方向相对偏移(可能有一点水平偏移)。在校正后的图像中,极线是垂直的并且具有相同的 x 坐标。P1 和 P2 的形式如下:
P1
=
[
f
0
c
x
0
0
f
c
y
1
0
0
0
1
0
]
\texttt{P1} = \begin{bmatrix} f & 0 & cx & 0 \\ 0 & f & cy_1 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix}
P1=
f000f0cxcy11000
P2
=
[
f
0
c
x
0
0
f
c
y
2
T
y
⋅
f
0
0
1
0
]
,
\texttt{P2} = \begin{bmatrix} f & 0 & cx & 0 \\ 0 & f & cy_2 & T_y \cdot f \\ 0 & 0 & 1 & 0 \end{bmatrix},
P2=
f000f0cxcy210Ty⋅f0
,
Q
=
[
1
0
0
−
c
x
0
1
0
−
c
y
1
0
0
0
f
0
0
−
1
T
y
c
y
1
−
c
y
2
T
y
]
\texttt{Q} = \begin{bmatrix} 1 & 0 & 0 & -cx \\ 0 & 1 & 0 & -cy_1 \\ 0 & 0 & 0 & f \\ 0 & 0 & -\frac{1}{T_y} & \frac{cy_1 - cy_2}{T_y} \end{bmatrix}
Q=
10000100000−Ty1−cx−cy1fTycy1−cy2
其中 Ty 是相机之间的垂直偏移,如果设置了 CALIB_ZERO_DISPARITY,则cy1=cy2。
可以看到,P1 和 P2 的前三列实际上会成为新的“校正”相机矩阵。这些矩阵连同 R1 和 R2 可以传递给 initUndistortRectifyMap 来初始化每个相机的校正映射。
示例截图
下面是来自 stereo_calib.cpp 示例的截图。一些红色水平线通过对应的图像区域,这意味着图像已经很好地进行了校正,这是大多数立体对应算法所依赖的。绿色矩形是 roi1 和 roi2。可以看到,它们内部都是有效的像素。
代码示例
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
// 生成测试图像函数
void generateTestImages( Size imageSize, Mat& img1, Mat& img2 )
{
img1 = Mat::zeros( imageSize, CV_8UC3 );
img2 = Mat::zeros( imageSize, CV_8UC3 );
// 在第一张图像上画水平线,在第二张图像上画稍微偏移的水平线模拟立体图像
for ( int y = 50; y < imageSize.height; y += 50 )
{
line( img1, Point( 0, y ), Point( imageSize.width, y ), Scalar( 0, 255, 0 ), 2 );
line( img2, Point( 0, y + 10 ), Point( imageSize.width, y + 10 ), Scalar( 0, 255, 0 ), 2 );
}
}
int main()
{
// 假设我们已经有以下数据(来自 stereoCalibrate 或其他来源)
Mat cameraMatrix1 = ( Mat_< double >( 3, 3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
Mat cameraMatrix2 = ( Mat_< double >( 3, 3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
Mat distCoeffs1 = Mat::zeros( 5, 1, CV_64F ); // 简化的畸变系数模型
Mat distCoeffs2 = Mat::zeros( 5, 1, CV_64F );
// 假定的旋转和平移矩阵(根据实际情况调整)
Mat R = ( Mat_< double >( 3, 3 ) << 0.999, 0.001, -0.044, -0.002, 0.998, 0.061, 0.044, -0.061, 0.998 );
Mat T = ( Mat_< double >( 3, 1 ) << 60.0, 0.0, 0.0 ); // 假设两个相机沿X轴平移了60个单位
Size imageSize( 640, 480 ); // 图像尺寸
// 输出变量
Mat R1, R2; // 校正后的旋转矩阵
Mat P1, P2; // 新的投影矩阵
Mat Q; // 视差到深度映射矩阵
Rect validPixROI1, validPixROI2; // 有效像素区域
// 操作标志和自由缩放参数
int flags = CALIB_ZERO_DISPARITY;
double alpha = -1;
Size newImageSize = imageSize; // 使用原始图像尺寸
// 执行立体校正
stereoRectify( cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q, flags, alpha, newImageSize, &validPixROI1, &validPixROI2 );
cout << "Rectification matrices for the first camera:\n" << R1 << endl;
cout << "Rectification matrices for the second camera:\n" << R2 << endl;
cout << "Projection matrix for the first camera:\n" << P1 << endl;
cout << "Projection matrix for the second camera:\n" << P2 << endl;
cout << "Disparity-to-depth mapping matrix:\n" << Q << endl;
// 初始化重映射
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap( cameraMatrix1, distCoeffs1, R1, P1, newImageSize, CV_32FC1, map1x, map1y );
initUndistortRectifyMap( cameraMatrix2, distCoeffs2, R2, P2, newImageSize, CV_32FC1, map2x, map2y );
// 生成一对测试图像
Mat img1, img2;
generateTestImages( imageSize, img1, img2 );
// 应用重映射
Mat rectifiedImg1, rectifiedImg2;
remap( img1, rectifiedImg1, map1x, map1y, INTER_LINEAR );
remap( img2, rectifiedImg2, map2x, map2y, INTER_LINEAR );
// 显示结果
imshow( "Original Image 1", img1 );
imshow( "Original Image 2", img2 );
imshow( "Rectified Image 1", rectifiedImg1 );
imshow( "Rectified Image 2", rectifiedImg2 );
waitKey( 0 ); // 等待按键关闭窗口
return 0;
}
运行结果
命令行输出:
Rectification matrices for the first camera:
[0.9990327505785522, 0.002838302200161476, -0.0438806029137316;
-0.001498542304358349, 0.9995325828506972, 0.03053473744403527;
0.04394675917987673, -0.03043944579710149, 0.9985700388541362]
Rectification matrices for the second camera:
[1, 2.782197548109867e-17, -9.101507439329915e-16;
-4.437342568756724e-17, 0.9995349355631407, -0.03049446816700398;
9.047190730013251e-16, 0.03049446816700397, 0.9995349355631405]
Projection matrix for the first camera:
[521, 0, 340.8940467834473, 0;
0, 521, 249.6826610565186, 0;
0, 0, 1, 0]
Projection matrix for the second camera:
[521, 0, 340.8940467834473, 31260;
0, 521, 249.6826610565186, 0;
0, 0, 1, 0]
Disparity-to-depth mapping matrix:
[1, 0, 0, -340.8940467834473;
0, 1, 0, -249.6826610565186;
0, 0, 0, 521;
0, 0, -0.01666666666666667, 0]