0
点赞
收藏
分享

微信扫一扫

【opencv】教程代码 —features2D(4)利用两张摄像机拍摄的图片计算单应性矩阵...

c011209281ae0e106040e01dc4bfab67.png

homography_from_camera_displacement.cpp

3c402bf19ec1bddb8a29a20d23b16c94.png

Chessboard poses 棋盘姿态

1641b7fc55e4c3e5ced790fe5ed1c22e.png

使用根据相机位移计算的单应性扭曲图像

a3f7f7f6404160a24bb86a6fca3efa10.png

使用根据绝对相机姿势计算的单应性扭曲图像

415bf793b7c344a67510d6e1b49678c0.png

Warped images comparison 扭曲图像比较

左侧-nfindHomography  右侧-使用根据相机位移计算的单应性扭曲图像

终端输出:

Euclidean Homography:(根据相机位移计算单应性)
[0.2215344870864446, -0.9949332344575121, 0.1140657984013492;
 0.6776201343947539, 0.1839368762588706, -0.1530250021245755;
 0.3300066201371719, -0.5683454599466965, 1]
Euclidean Homography 2:(同上,但使用绝对相机姿势而不是相机位移 仅供检查)
[0.2215344870864446, -0.9949332344575118, 0.1140657984013491;
 0.6776201343947537, 0.1839368762588706, -0.1530250021245755;
 0.3300066201371718, -0.5683454599466965, 1]




findHomography H:
[0.3290339333220099, -1.244138808862929, 536.4769088231476;
 0.6969763913334047, -0.0893590907257152, -80.3406850408241;
 0.0004051172959296097, -0.001079740100565012, 1]
homography from camera displacement:(相机位移的单应性)
[0.4160569974777896, -1.306889022263172, 553.7055455031656;
 0.7917584238390836, -0.06341244817498765, -108.2770026444472;
 0.0005926357279773245, -0.00102065172285972, 1]
homography from absolute camera poses:(绝对相机姿势的单应性)
[0.4160569974777895, -1.306889022263171, 553.7055455031656;
 0.7917584238390833, -0.06341244817498759, -108.2770026444472;
 0.0005926357279773244, -0.00102065172285972, 1]

d9fdeae07ea7e41f52e738ff039ed57f.png

// 包含必要的库
#include <iostream> // 用于基本输入输出
#include <opencv2/core.hpp> // 包含OpenCV库的核心部分
#include <opencv2/imgproc.hpp> // 包含OpenCV库的图像处理部分
#include <opencv2/highgui.hpp> // 包含OpenCV库的高级GUI部分
#include <opencv2/calib3d.hpp> // 包含OpenCV库的相机标定和3D重建部分


using namespace std; // 使用标准命名空间
using namespace cv; // 使用OpenCV命名空间


namespace // 定义一个无名命名空间
{
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID }; // 定义一个枚举类型Pattern,包含棋盘、圆形网格和非对称圆形网格三种模式


void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CHESSBOARD)
// 定义一个函数,用于计算棋盘角点
{
    corners.resize(0); // 重置角点向量


    switch (patternType) // 根据模式类型进行不同的操作
    {
    case CHESSBOARD:
    case CIRCLES_GRID:
        for( int i = 0; i < boardSize.height; i++ ) // 遍历棋盘的每一行
            for( int j = 0; j < boardSize.width; j++ ) // 遍历棋盘的每一列
                corners.push_back(Point3f(float(j*squareSize), // 将计算得到的角点坐标添加到角点向量中
                                          float(i*squareSize), 0));
        break;


    case ASYMMETRIC_CIRCLES_GRID:
        for( int i = 0; i < boardSize.height; i++ ) // 遍历棋盘的每一行
            for( int j = 0; j < boardSize.width; j++ ) // 遍历棋盘的每一列
                corners.push_back(Point3f(float((2*j + i % 2)*squareSize), // 将计算得到的角点坐标添加到角点向量中
                                          float(i*squareSize), 0));
        break;


    default:
        CV_Error(Error::StsBadArg, "Unknown pattern type\n"); // 如果模式类型未知,则抛出错误
    }
}


//! [compute-homography]
Mat computeHomography(const Mat &R_1to2, const Mat &tvec_1to2, const double d_inv, const Mat &normal)
// 定义一个函数,用于计算单应性矩阵
{
    Mat homography = R_1to2 + d_inv * tvec_1to2*normal.t(); // 计算单应性矩阵
    return homography; // 返回单应性矩阵
}
//! [compute-homography]


Mat computeHomography(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
                      const double d_inv, const Mat &normal)
// 定义一个函数,用于计算单应性矩阵
{
    Mat homography = R2 * R1.t() + d_inv * (-R2 * R1.t() * tvec1 + tvec2) * normal.t(); // 计算单应性矩阵
    return homography; // 返回单应性矩阵
}


//! [compute-c2Mc1]
void computeC2MC1(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
                  Mat &R_1to2, Mat &tvec_1to2)
// 定义一个函数,用于计算相机位移
{
    //c2Mc1 = c2Mo * oMc1 = c2Mo * c1Mo.inv()
    R_1to2 = R2 * R1.t(); // 计算旋转矩阵
    tvec_1to2 = R2 * (-R1.t()*tvec1) + tvec2; // 计算平移向量
}
//! [compute-c2Mc1]


void homographyFromCameraDisplacement(const string &img1Path, const string &img2Path, const Size &patternSize,
                                      const float squareSize, const string &intrinsicsPath)
// 定义一个函数,用于根据相机位移计算单应性矩阵
{
    Mat img1 = imread( samples::findFile( img1Path ) ); // 读取第一张图片
    Mat img2 = imread( samples::findFile( img2Path ) ); // 读取第二张图片


    //! [compute-poses]
    vector<Point2f> corners1, corners2; // 定义两个角点向量
    bool found1 = findChessboardCorners(img1, patternSize, corners1); // 在第一张图片中寻找棋盘角点
    bool found2 = findChessboardCorners(img2, patternSize, corners2); // 在第二张图片中寻找棋盘角点


    if (!found1 || !found2) // 如果在任何一张图片中都找不到棋盘角点
    {
        cout << "Error, cannot find the chessboard corners in both images." << endl; // 输出错误信息
        return; // 返回
    }


    vector<Point3f> objectPoints; // 定义一个对象点向量
    calcChessboardCorners(patternSize, squareSize, objectPoints); // 计算棋盘角点


    FileStorage fs( samples::findFile( intrinsicsPath ), FileStorage::READ); // 打开内参文件
    Mat cameraMatrix, distCoeffs; // 定义相机矩阵和畸变系数
    fs["camera_matrix"] >> cameraMatrix; // 读取相机矩阵
    fs["distortion_coefficients"] >> distCoeffs; // 读取畸变系数


    Mat rvec1, tvec1; // 定义旋转向量和平移向量
    solvePnP(objectPoints, corners1, cameraMatrix, distCoeffs, rvec1, tvec1); // 求解PnP问题
    Mat rvec2, tvec2; // 定义旋转向量和平移向量
    solvePnP(objectPoints, corners2, cameraMatrix, distCoeffs, rvec2, tvec2); // 求解PnP问题
    //! [compute-poses]


    Mat img1_copy_pose = img1.clone(), img2_copy_pose = img2.clone(); // 复制两张图片
    Mat img_draw_poses; // 定义一个用于绘制姿态的图像
    drawFrameAxes(img1_copy_pose, cameraMatrix, distCoeffs, rvec1, tvec1, 2*squareSize); // 在第一张图片上绘制坐标轴
    drawFrameAxes(img2_copy_pose, cameraMatrix, distCoeffs, rvec2, tvec2, 2*squareSize); // 在第二张图片上绘制坐标轴
    hconcat(img1_copy_pose, img2_copy_pose, img_draw_poses); // 将两张图片水平拼接
    imshow("Chessboard poses", img_draw_poses); // 显示棋盘姿态


    //! [compute-camera-displacement]
    Mat R1, R2; // 定义两个旋转矩阵
    Rodrigues(rvec1, R1); // 将旋转向量转换为旋转矩阵
    Rodrigues(rvec2, R2); // 将旋转向量转换为旋转矩阵


    Mat R_1to2, t_1to2; // 定义旋转矩阵和平移向量
    computeC2MC1(R1, tvec1, R2, tvec2, R_1to2, t_1to2); // 计算相机位移
    Mat rvec_1to2; // 定义旋转向量
    Rodrigues(R_1to2, rvec_1to2); // 将旋转矩阵转换为旋转向量
    //! [compute-camera-displacement]


    //! [compute-plane-normal-at-camera-pose-1]
    //平面法线在相机1坐标系下的坐标表示
    Mat normal = (Mat_<double>(3,1) << 0, 0, 1); // 定义法线向量
    Mat normal1 = R1*normal; // 计算第一帧的法线向量
    //! [compute-plane-normal-at-camera-pose-1]


    //! [compute-plane-distance-to-the-camera-frame-1]
    //世界坐标系原点在相机1坐标系下的坐标表示origin1 
    Mat origin(3, 1, CV_64F, Scalar(0)); // 定义原点
    Mat origin1 = R1*origin + tvec1; // 计算第一帧的原点
    double d_inv1 = 1.0 / normal1.dot(origin1); 
    // 计算平面到第一帧相机坐标系原点的距离的倒数
    //! [compute-plane-distance-to-the-camera-frame-1]


    //!根据相机位移(位姿变化)计算单应性 [compute-homography-from-camera-displacement]
    Mat homography_euclidean = computeHomography(R_1to2, t_1to2, d_inv1, normal1); // 计算欧几里得单应性矩阵
    Mat homography = cameraMatrix * homography_euclidean * cameraMatrix.inv(); // 计算单应性矩阵


    homography /= homography.at<double>(2,2); // 归一化单应性矩阵
    homography_euclidean /= homography_euclidean.at<double>(2,2); // 归一化欧几里得单应性矩阵
    //! [compute-homography-from-camera-displacement]


    //Same but using absolute camera poses instead of camera displacement, just for check
    Mat homography_euclidean2 = computeHomography(R1, tvec1, R2, tvec2, d_inv1, normal1); // 计算欧几里得单应性矩阵
    Mat homography2 = cameraMatrix * homography_euclidean2 * cameraMatrix.inv(); // 计算单应性矩阵


    homography_euclidean2 /= homography_euclidean2.at<double>(2,2); // 归一化欧几里得单应性矩阵
    homography2 /= homography2.at<double>(2,2); // 归一化单应性矩阵


    cout << "\nEuclidean Homography:\n" << homography_euclidean << endl; // 输出欧几里得单应性矩阵
    cout << "Euclidean Homography 2:\n" << homography_euclidean2 << endl << endl; // 输出欧几里得单应性矩阵


    //! 估计单应性矩阵H  corners2 = H * corners1   [estimate-homography]
    Mat H = findHomography(corners1, corners2); // 估计单应性矩阵
    cout << "\nfindHomography H:\n" << H << endl; // 输出估计的单应性矩阵
    //! [estimate-homography]


    cout << "homography from camera displacement:\n" << homography << endl; // 输出由相机位移计算得到的单应性矩阵
    cout << "homography from absolute camera poses:\n" << homography2 << endl << endl; // 输出由绝对相机姿态计算得到的单应性矩阵


    //! [warp-chessboard]
    Mat img1_warp; // 定义一个变形后的图像
    warpPerspective(img1, img1_warp, H, img1.size()); // 对第一张图片进行透视变换
    //! [warp-chessboard]


    Mat img1_warp_custom; // 定义一个自定义的变形后的图像
    warpPerspective(img1, img1_warp_custom, homography, img1.size()); // 对第一张图片进行透视变换
    imshow("Warped image using homography computed from camera displacement", img1_warp_custom); // 显示由相机位移计算得到的单应性矩阵变形后的图像


    Mat img_draw_compare; // 定义一个用于比较的图像
    hconcat(img1_warp, img1_warp_custom, img_draw_compare); // 将两张变形后的图像水平拼接
    imshow("Warped images comparison", img_draw_compare); // 显示比较后的图像


    Mat img1_warp_custom2; // 定义一个自定义的变形后的图像
    warpPerspective(img1, img1_warp_custom2, homography2, img1.size()); // 对第一张图片进行透视变换
    imshow("Warped image using homography computed from absolute camera poses", img1_warp_custom2); // 显示由绝对相机姿态计算得到的单应性矩阵变形后的图像


    waitKey(); // 等待用户按键
}


const char* params
    = "{ help h         |       | print usage }"
      "{ image1         | left02.jpg | path to the source chessboard image }"
      "{ image2         | left01.jpg | path to the desired chessboard image }"
      "{ intrinsics     | left_intrinsics.yml | path to camera intrinsics }"
      "{ width bw       | 9     | chessboard width }"
      "{ height bh      | 6     | chessboard height }"
      "{ square_size    | 0.025 | chessboard square size }";
    // 定义一个参数列表,包含帮助信息、图像路径、内参路径、棋盘宽度、棋盘高度和棋盘方格大小


}


int main(int argc, char *argv[]) // 主函数
{
    CommandLineParser parser(argc, argv, params); // 定义一个命令行解析器


    if (parser.has("help")) // 如果用户请求帮助
    {
        parser.about("Code for homography tutorial.\n"
            "Example 3: homography from the camera displacement.\n"); // 输出关于信息
        parser.printMessage(); // 打印消息
        return 0; // 返回0
    }


    Size patternSize(parser.get<int>("width"), parser.get<int>("height")); // 获取棋盘的尺寸
    float squareSize = (float) parser.get<double>("square_size"); // 获取棋盘方格的大小
    homographyFromCameraDisplacement(parser.get<String>("image1"), // 调用函数,计算单应性矩阵
                                     parser.get<String>("image2"),
                                     patternSize, squareSize,
                                     parser.get<String>("intrinsics"));


    return 0; // 返回0
}

上面的C++代码是使用OpenCV库来实现基于棋盘格的摄像头标定、计算相机姿态、以及基于相机位移计算单应性矩阵。主要步骤如下:

  1. 读取两张包含棋盘格的图片。

  2. 检测棋盘格的角点。

  3. 计算角点在世界坐标系中的位置。

  4. 读取摄像头参数(内参和畸变参数)。

  5. 使用solvePnP方法分别计算两张图片中棋盘格的旋转向量和平移向量

  6. 计算棋盘格从第一张图片到第二张图片的相机位移(旋转矩阵和平移向量)。

  7. 计算单应性矩阵,将第一张图片中的棋盘格投影到第二张图片的视角

  8. 显示两种方式计算得到的单应性矩阵

  9. 使用单应性矩阵对第一张图片进行变换,以便与第二张图片对齐

该代码对于相机标定和三维重建的学习很有帮助,特别是在理解和应用单应性矩阵计算以实现图像配准和视角变换方面。

7e7d300af7f51d3fc4b6b51cefbe1658.png

863a35bbd1600c2f8e58d29961af2f11.png

18c452fb981015c06d61bb75ff3f49b9.png

单应性矩阵的物理意义

d4a741efd9b614d1e0743b182b8d2b94.png

963e2091af3523c6e2310c9c29318df8.png

如何计算单应性矩阵

564e3bcb997fcaf1ffca1b8b1e8964ce.png

Mat homography = R_1to2 + d_inv * tvec_1to2*normal.t();

46d4d4943b222f6542cbb2e5ea535312.png

solvePnP(objectPoints, corners1, cameraMatrix, distCoeffs, rvec1, tvec1);

cbaebc40ae37f48fbed5abe0c10a3cbd.png

drawFrameAxes(img1_copy_pose, cameraMatrix, distCoeffs, rvec1, tvec1, 2*squareSize);

8c36beeb2041b3ec977cb50a17a9669d.png

CV_EXPORTS_W void Rodrigues( InputArray src, OutputArray dst, OutputArray jacobian = noArray() );

0a466644d216d96fb5cb5f3d5c1aa0e2.png

Mat H = findHomography(corners1, corners2);

6c48881ef5143bbfcedde7ceffe11710.png

homography 与 homography_euclidean的区别

8824b76c66e4dfda2d17c70df8e9f2fb.png

举报

相关推荐

0 条评论