双目视觉--立体校正

前言

立体校正是双目视觉中重要的图像处理技术。双目视觉符合极线约束,即左右目的匹配点与双目相机的光心在同一平面。

​ 通常,左目一点在右目的极线是倾斜的,这使得在沿着极线搜索进行立体匹配时要实时根据极线方程计算下一个在极线上的坐标。立体校正可以使得双目视觉投影到一个相同的平面,这使得极线将会完全水平。

opencv c++实现立体校正

​ 在实现立体校正,要先完成双目相机的立体标定,得到相应的参数。

​ K1,D1,K2,D2,R,T分别对应左目内参矩阵,左目畸变矩阵,右目内参矩阵,右目畸变矩阵,两个相机坐标系之间的旋转、平移矩阵(通常是右目转左目)。

​ 在立体校正过程中,首先通过cv::stereoRectify函数计算出校正变换矩阵R和新的相机内参矩阵newCameraMatrix,然后使用cv::initUndistortRectifyMap函数生成左右相机的校正映射表。接下来,可以通过cv::remap函数对左右相机的图像进行校正,将它们映射到校正后的图像上,从而消除立体图像的视差,得到对应的校正后图像。

void cv::stereoRectify(
    const cv::Mat& cameraMatrix1, const cv::Mat& distCoeffs1,
    const cv::Mat& cameraMatrix2, const cv::Mat& distCoeffs2,
    const cv::Size& imageSize, const cv::Mat& R, const cv::Mat& T,
    cv::Mat& R1, cv::Mat& R2, cv::Mat& P1, cv::Mat& P2, cv::Mat& Q,
    int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1,
    const cv::Size& newImageSize = cv::Size(), cv::Rect* validPixROI1 = nullptr,
    cv::Rect* validPixROI2 = nullptr
);
参数说明:
cameraMatrix1cameraMatrix2:左右相机的内参矩阵。
distCoeffs1distCoeffs2:左右相机的畸变系数。
imageSize:输入图像的大小。
RT:左右相机之间的旋转矩阵和平移向量。
R1R2:输出参数,左右相机的校正旋转矩阵。
P1P2:输出参数,左右相机的投影矩阵。
Q:输出参数,立体校正的深度映射矩阵。
flags:可选参数,表示校正方式的标志,默认为cv::CALIB_ZERO_DISPARITY
alpha:可选参数,调整校正后图像的缩放因子,默认为-1,表示根据校正后图像的内容自动调整缩放因子。
newImageSize:可选参数,校正后图像的大小,默认为空,表示保持与输入图像大小相同。
validPixROI1validPixROI2:输出参数,校正后的图像的有效像素区域。
-------
 R1是从原相机坐标系转移到矫正后的相机坐标系旋转矩阵
 P1从矫正后的相机坐标系投影到图像坐标系的投影矩阵
-------
alpha 参数是一个自由的缩放参数。如果设置为 -1 或缺席,函数将执行默认的缩放。否则,参数应该在 0  1 之间。
alpha = 0 表示校正后的图像被缩放和移动,只有有效像素可见(校正后没有黑色区域)。
alpha = 1 表示对校正后的图像进行抽取和移位,使所有来自相机的原始图像的像素都保留在校正后的图像中(无源图像像素丢失)。
任何中间值都是这两个极端情况的中间结果。
例如,如果 alpha 设置为 0.5,则校正后的图像将缩放为原始图像的一半大小,并且只有在原始图像中有效的像素将在校正后的图像中可见。
void cv::initUndistortRectifyMap(
    const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs,
    const cv::Mat& R, const cv::Mat& newCameraMatrix,
    const cv::Size& size, int m1type, cv::OutputArray map1, cv::OutputArray map2
);
参数说明:
cameraMatrix:相机的内参矩阵,包含相机的焦距、主点和畸变参数等信息。
distCoeffs:相机的畸变系数。
R:校正变换矩阵,用于校正图像。
newCameraMatrix:新的相机内参矩阵,用于生成校正后的图像。
size:输入图像的大小。
m1type:映射表的数据类型,通常为CV_16SC2
map1map2:输出参数,计算得到的映射矩阵。
-------
map1  map2 是函数计算得到的两个映射矩阵,用于实现图像畸变校正和立体校正。
映射矩阵中存放的是原图像中的每个像素在校正后的图像中的新位置坐标。
具体来说:
map1 矩阵: 储存源图像的 x 坐标
map2 矩阵: 储存源图像的 y 坐标
其中,map1map2的大小与新图像大小相同,类型通常为 CV_16SC2  CV_32FC1
CV_16SC2:
    矩阵中每个元素是一个两通道的向量,(x,y)坐标对。
    每个通道用short类型表示,2字节。
    这样可以将xy坐标存储在同一个矩阵中,使用时方便。
CV_32FC1:
    矩阵中每个元素是一个单通道的浮点数。
    x坐标和y坐标需要分别存储在两个矩阵中。
    单通道浮点类型占4字节,可以获得更高精度。
void cv::remap(
    const cv::Mat& src, cv::Mat& dst,
    const cv::Mat& map1, const cv::Mat& map2,
    int interpolation,
    int borderMode = cv::BORDER_CONSTANT,
    const cv::Scalar& borderValue = cv::Scalar()
);
参数说明:
src:输入图像,可以是左相机或右相机的图像。
dst:输出参数,校正后的图像。
map1map2:左右相机的校正映射表,通过cv::initUndistortRectifyMap函数生成。
interpolation:插值方法,表示在校正过程中如何对图像进行插值,可以是cv::INTER_NEARESTcv::INTER_LINEARcv::INTER_CUBIC等。
borderMode:可选参数,表示图像边界的处理方式,默认为cv::BORDER_CONSTANT
borderValue:可选参数,当borderModecv::BORDER_CONSTANT时,表示边界像素的值,默认为黑色。
核心思想是:
    1. 根据map1map2中存储的映射关系,计算出输入图像(src)中每个像素在输出图像(dst)中的坐标。
    2. 根据计算出的坐标对输出图像(dst)进行采样,通过插值计算像素值。
    3. 对映射后超出图像范围的坐标,根据borderMode进行边界处理。

完整代码

// 立体矫正
    cv::Size imageSize = left_image.size();
    cv::Mat R1, R2, P1, P2, Q;
    cv::Mat map1x, map1y, map2x, map2y;
    cv::Rect validROI[2];
    // cv::stereoRectify(cvK1, cvD1, cvK2, cvD2, imageSize, cvR, cvT, R1, R2, P1, P2, Q, cv::CALIB_ZERO_DISPARITY, 1.0, imageSize, 0, 0);
    cv::stereoRectify(cvK1, cvD1, cvK2, cvD2, imageSize, cvR, cvT, R1, R2, P1, P2, Q);
    // R1是从原相机坐标系转移到矫正后的相机坐标系旋转矩阵
    // P1从矫正后的相机坐标系投影到图像坐标系的投影矩阵
    cv::Mat map1Left, map2Left, map1Right, map2Right;
    cv::initUndistortRectifyMap(cvK1, cvD1, R1, P1, imageSize, CV_16SC2, map1Left, map2Left);
    cv::initUndistortRectifyMap(cvK2, cvD2, R2, P2, imageSize, CV_16SC2, map1Right, map2Right);
    cv::Mat rectifiedLeft, rectifiedRight;
    cv::remap(left_image, rectifiedLeft, map1Left, map2Left, cv::INTER_LINEAR);
    cv::remap(right_image, rectifiedRight, map1Right, map2Right, cv::INTER_LINEAR);
    cv::imwrite("rectify_left.bmp", rectifiedLeft);
    cv::imwrite("rectify_right.bmp", rectifiedRight);

校正前后坐标转换

void cv::initUndistortRectifyMap(
    const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs,
    const cv::Mat& R, const cv::Mat& newCameraMatrix,
    const cv::Size& size, int m1type, cv::OutputArray map1, cv::OutputArray map2
);

​ 函数的输出参数cv::OutputArray map1, cv::OutputArray map2是校正前和校正后图像坐标系的映射矩阵。

​ 其参数类型由int m1type决定,通常使用CV_16SC2 或 CV_32FC1

map1map2的Size与输出图是一样的,其索引表示了校正的坐标,其内容表示了校正的坐标。

​ 如果map1map2是单通道的,则map1中存储了校正的x坐标,map2中存储了校正的y坐标。

​ 如果int m1type是CV_16SC2,则在map1中就完整的存储了校正的x,y坐标,此时的数据类型是short类型。校正的坐标可以表示为:

map1.at<cv::Vec2s>(NewPoint.y, NewPoint.x)

前➡后

不好算。

https://stackoverflow.com/questions/34265650/can-i-get-the-point-position-using-remap-in-opencv

cv::Point2f SrcPoint; // 已知
cv::Point2f NewPoint;
std::vector<cv::Point2f> originalPointsVec = { SrcPoint };
std::vector<cv::Point2f> newPointsVec = { SrcPoint };
cv::undistortPoints(originalPointsVec, newPointsVec, cvK1, cvD1, R1, P1);
NewPoint = newPointsVec[0];

后➡前

dst(x, y) = src(map_x(x, y), map_y(x, y))

cv::Point2f SrcPoint;
cv::Point2f NewPoint;  // 已知
SrcPoint.x = map1Left.at<cv::Vec2s>(NewPoint.y, NewPoint.x)[0];
SrcPoint.y = map1Left.at<cv::Vec2s>(NewPoint.y, NewPoint.x)[1];