【opencv】示例 3calibration.cpp 利用OpenCV库进行三路相机校准

此代码是一个利用OpenCV库进行三路相机校准的C++程序。这个示例程序主要用于校准水平摆放的三台相机。

以下是关键函数及其功能的简要总结:

  • help(char** argv): 显示程序的使用方法。

  • calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners): 计算棋盘格角点的3D位置

  • run3Calibration(...): 运行三个相机的校准过程,包括单独校准每个相机和校准相机对(1,2)和(1,3)。

  • readStringList(const string& filename, vector<string>& l): 从文件读取图像列表。

  • main(int argc, char** argv): 程序的入口点。处理命令行参数,执行校准流程,并显示结果。

主要的类和方法:

  • Size:表示图像或棋盘格的尺寸。

  • Point3fPoint2f:分别表示3D点和2D图像点。

  • Mat:OpenCV的核心类,用于存储和处理图像数据。

  • FileStorage:用于读写XML或YAML格式的OpenCV数据文件。

程序的执行流程:

  1. 解析命令行参数,包括棋盘格尺寸、输出文件名等。

  2. 从文件读取图像列表并检查其合法性。

  3. 对每张图像检测棋盘格角点,并存储到相应的列表中。

  4. 调用run3Calibration函数对三个相机单独进行校准,然后对相机对进行立体校准,得到摄像机的矩阵和畸变系数,以及相机间的旋转和平移矩阵。

  5. 使用stereoCalibrate函数获取立体校准的结果和相关参数。

  6. 通过rectify3Collinear函数计算纠正相机图像畸变后的矩阵。

  7. 使用initUndistortRectifyMapremap函数处理图像,消除畸变并进行矫正。

  8. 显示矫正后的图像,并通过按键操作来退出图像显示。

最后,该程序将所有计算出的参数写入YAML格式的输出文件中。这些参数包括传感器的内参矩阵、畸变系数、旋转矩阵、投影矩阵和视差比率。这些参数对于计算机视觉和图像处理应用非常重要,可以用来纠正图像的畸变并将多个相机视图整合到同一坐标系中。

// 文件名: 3calibration.cpp -- 用于一起校准一条水平线上的3个相机的程序。


#include "opencv2/calib3d.hpp"          // 包含OpenCV相机标定和三维重建相关功能的头文件
#include "opencv2/imgproc.hpp"          // 包含OpenCV图像处理相关功能的头文件
#include "opencv2/imgcodecs.hpp"        // 包含OpenCV图像编解码相关功能的头文件
#include "opencv2/highgui.hpp"          // 包含OpenCV高级用户界面相关功能的头文件
#include "opencv2/core/utility.hpp"     // 包含OpenCV核心功能(如命令行解析)相关的头文件


#include <stdio.h>                      // 包含标准输入输出相关功能的头文件
#include <string.h>                     // 包含C语言字符串操作相关功能的头文件
#include <time.h>                       // 包含C语言时间操作相关功能的头文件


using namespace cv;                    // 使用opencv命名空间
using namespace std;                   // 使用std命名空间


enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 }; // 定义枚举类型,用于描述相机的不同状态


static void help(char** argv)          // 定义帮助函数,用于输出程序的用法说明
{
        // 输出帮助信息,包含如何使用程序和各种命令行参数的说明
        printf( "\nThis is a camera calibration sample that calibrates 3 horizontally placed cameras together.\n"
               "Usage: %s\n"
               "     -w=<board_width>         # the number of inner corners per one of board dimension\n"
               "     -h=<board_height>        # the number of inner corners per another board dimension\n"
               "     [-s=<squareSize>]       # square size in some user-defined units (1 by default)\n"
               "     [-o=<out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters\n"
               "     [-zt]                    # assume zero tangential distortion\n"
               "     [-a=<aspectRatio>]      # fix aspect ratio (fx/fy)\n"
               "     [-p]                     # fix the principal point at the center\n"
               "     [input_data]             # input data - text file with a list of the images of the board\n"
               "\n", argv[0] );


}


static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners) // 定义函数计算棋盘角点
{
    corners.resize(0); // 清空角点向量


    // 使用嵌套循环按棋盘的格子顺序,计算每个内角点的世界坐标,存入角点向量中
    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));
}


// 定义了三相机标定函数,输入为2D图像点,图像大小,棋盘大小,方格大小,纵横比等参数,输出为相机矩阵、
// 畸变系数以及两两相机之间的旋转和平移矩阵
static bool run3Calibration(vector<vector<Point2f> > imagePoints1,
                            vector<vector<Point2f> > imagePoints2,
                            vector<vector<Point2f> > imagePoints3,
                            Size imageSize, Size boardSize,
                            float squareSize, float aspectRatio,
                            int flags,
                            Mat& cameraMatrix1, Mat& distCoeffs1,
                            Mat& cameraMatrix2, Mat& distCoeffs2,
                            Mat& cameraMatrix3, Mat& distCoeffs3,
                            Mat& R12, Mat& T12, Mat& R13, Mat& T13)
{
    int c, i;


    // step 1: 分别校准每个相机
    vector<vector<Point3f> > objpt(1);                         // 定义对象点的向量(3D点)
    vector<vector<Point2f> > imgpt;                            // 定义图像点的向量(2D点)
    calcChessboardCorners(boardSize, squareSize, objpt[0]);    // 计算每个棋盘角的三维点
    vector<Mat> rvecs, tvecs;                                  // 分别定义旋转向量和平移向量的向量


    // 对每一个相机进行循环校准
    for( c = 1; c <= 3; c++ ) 
    {
        // 根据相机序号选择对应的图像点
        const vector<vector<Point2f> >& imgpt0 = c == 1 ? imagePoints1 : c == 2 ? imagePoints2 : imagePoints3;
        imgpt.clear();                                          // 清空图像点向量
        int N = 0;                                              // 总图像点数初始化为0
        for( i = 0; i < (int)imgpt0.size(); i++ )             
            if( !imgpt0[i].empty() )                           // 如果当前视图的点不为空
            {
                imgpt.push_back(imgpt0[i]);                    // 加入图像点向量
                N += (int)imgpt0[i].size();                    // 累加图像点数
            }


        // 如果有效的视图少于3个,则输出错误信息,返回false
        if( imgpt.size() < 3 ) 
        {
            printf("Error: not enough views for camera %d\n", c);
            return false;
        }


        objpt.resize(imgpt.size(),objpt[0]);                   // 调整对象点向量的大小


        Mat cameraMatrix = Mat::eye(3, 3, CV_64F);             // 初始化相机矩阵为单位矩阵
        if( flags & CALIB_FIX_ASPECT_RATIO )                   // 如果设置了纵横比标志,则修改相机矩阵中对应纵横比的元素
            cameraMatrix.at<double>(0,0) = aspectRatio;


        Mat distCoeffs = Mat::zeros(5, 1, CV_64F);             // 初始化畸变系数为全0矩阵


        // 使用calibrateCamera函数进行相机标定,并获取重投影误差err
        double err = calibrateCamera(objpt, imgpt, imageSize, cameraMatrix,
                        distCoeffs, rvecs, tvecs,
                        flags|CALIB_FIX_K3/*|CALIB_FIX_K4|CALIB_FIX_K5|CALIB_FIX_K6*/);


        bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);   // 检查相机矩阵和畸变系数的有效性
        if(!ok)                       // 如果不合规则输出错误信息,并返回false
        {
            printf("Error: camera %d was not calibrated\n", c);
            return false;
        }
        printf("Camera %d calibration reprojection error = %g\n", c, sqrt(err/N));   // 输出当前相机的标定重投影误差


        // 根据相机序号,存储对应相机的相机矩阵和畸变系数
        if( c == 1 )
            cameraMatrix1 = cameraMatrix, distCoeffs1 = distCoeffs;
        else if( c == 2 )
            cameraMatrix2 = cameraMatrix, distCoeffs2 = distCoeffs;
        else
            cameraMatrix3 = cameraMatrix, distCoeffs3 = distCoeffs;
    }


    vector<vector<Point2f> > imgpt_right;                      // 定义用于双目标定的另一组图像点向量


    // step 2: 分别进行(1,2)和(3,2)的双目标定
    for( c = 2; c <= 3; c++ )   //遍历相机2 相机3                              // 
    {
        const vector<vector<Point2f> >& imgpt0 = c == 2 ? imagePoints2 : imagePoints3;


        imgpt.clear();
        imgpt_right.clear();                                   // 清空第二组图像点向量
        int N = 0;


        // 对于imagePoints1和imgpt0中的每对视图,如果图片点非空,则加入图像点向量
        for( i = 0; i < (int)std::min(imagePoints1.size(), imgpt0.size()); i++ )
            if( !imagePoints1.empty() && !imgpt0[i].empty() )
            {
                imgpt.push_back(imagePoints1[i]);//相机1 图像点
                imgpt_right.push_back(imgpt0[i]);//相机2或3图像点
                N += (int)imgpt0[i].size();
            }


        // 如果有效的共享视图小于3个,则输入错误信息,并返回false
        if( imgpt.size() < 3 )
        {//相机1 与相机2或3 的共享视图至少3张图像
            printf("Error: not enough shared views for cameras 1 and %d\n", c);
            return false;
        }


        objpt.resize(imgpt.size(),objpt[0]);  // 调整对象点向量的大小以便进行双目校准
        Mat cameraMatrix = c == 2 ? cameraMatrix2 : cameraMatrix3;
        Mat distCoeffs = c == 2 ? distCoeffs2 : distCoeffs3;
        Mat R, T, E, F;                        // 分别定义旋转、平移、本质和基础矩阵
        // 使用stereoCalibrate函数进行双目标定
        double err = stereoCalibrate(objpt, imgpt, imgpt_right, cameraMatrix1, distCoeffs1,
                                     cameraMatrix, distCoeffs,
                                     imageSize, R, T, E, F,
                                     CALIB_FIX_INTRINSIC,
                                     TermCriteria(TermCriteria::COUNT, 30, 0));


        printf("Pair (1,%d) calibration reprojection error = %g\n", c, sqrt(err/(N*2))); // 输出相机1和c的双目标定重投影误差
        if( c == 2 )
        {
            cameraMatrix2 = cameraMatrix;   // 存储相机2的相机矩阵
            distCoeffs2 = distCoeffs;       // 存储相机2的畸变系数
            R12 = R; T12 = T;               // 存储相机1和2之间的旋转和平移矩阵
        }
        else
        {
            R13 = R; T13 = T;               // 存储相机1和3之间的旋转和平移矩阵
        }
    }


    return true;
}


// 定义读取字符串列表的函数,用于从文件中读取图像列表
static bool readStringList( const string& filename, vector<string>& l )
{
    l.resize(0);                         // 清空字符串列表
    FileStorage fs(filename, FileStorage::READ);  // 打开文件
    if( !fs.isOpened() )                 // 如果无法打开文件,则返回false
        return false;
    FileNode n = fs.getFirstTopLevelNode();   // 获取文件中的第一个节点
    if( n.type() != FileNode::SEQ )      // 如果节点类型不是序列,则返回false
        return false;
    FileNodeIterator it = n.begin(), it_end = n.end();   // 获取节点的迭代器
    for( ; it != it_end; ++it )          // 遍历每个节点,并将值加入字符串列表
        l.push_back((string)*it);
    return true;
}


// main函数,是程序的入口
// 主函数入口
int main( int argc, char** argv )
{
    int i, k;
    int flags = 0; // 标志定义  flags 为标定函数使用的设置,其中可以包括是否固定长宽比、假设零切向畸变以及是否固定主点在中心等标志。
    Size boardSize, imageSize; // 板子和图像的尺寸
    float squareSize, aspectRatio; // 棋盘方块大小和宽高比
    string outputFilename; // 输出文件名
    string inputFilename = ""; // 输入文件名,默认为空


    // 存储每个摄像头的图像检测点
    vector<vector<Point2f> > imgpt[3];
    vector<string> imageList; // 存储输入图像列表


    // 解析命令行参数
    cv::CommandLineParser parser(argc, argv,
        "{help ||}{w||}{h||}{s|1|}{o|out_camera_data.yml|}"
        "{zt||}{a|1|}{p||}{@input||}");
    if (parser.has("help")) // 如果包含帮助标志
    {
        help(argv); // 显示帮助信息
        return 0;
    }
    // 获取棋盘宽度、高度、方块大小、图像宽高比等参数
    // 从命令行参数中解析棋盘格的宽度  棋盘的宽度(内角点的数量)
    boardSize.width = parser.get<int>("w");
    // 从命令行参数中解析棋盘格的高度  棋盘表面的高度(内角点的数量)
    boardSize.height = parser.get<int>("h");
    // 从命令行参数中解析棋盘格的单元格尺寸 棋盘格的物理尺寸(用户自定义单位,默认单位大小为1)
    squareSize = parser.get<float>("s");
    // 从命令行参数中解析摄像头的长宽比 摄像机的焦距的宽高比(fx 与 fy的比率)。
    aspectRatio = parser.get<float>("a");
    // 如果在命令行参数中指定了长宽比,则设置标志以固定长宽比
    if (parser.has("a"))
        flags |= CALIB_FIX_ASPECT_RATIO;
    // 如果在命令行参数中指定了零切向畸变,则设置标志以假设零切向畸变
    if (parser.has("zt"))
        flags |= CALIB_ZERO_TANGENT_DIST;
    // 如果在命令行参数中指定了固定主点,则设置标志以固定主点在中心
    if (parser.has("p"))
        flags |= CALIB_FIX_PRINCIPAL_POINT;
    // 获取输出和输入的文件名
    outputFilename = parser.get<string>("o");
    inputFilename = parser.get<string>("@input");
    // 检查参数是否正确
    if (!parser.check())
    {
        help(argv);
        parser.printErrors();
        return -1;
    }
    // 如果棋盘的宽度小于或等于0,打印错误信息到标准错误输出,并返回-1
    if (boardSize.width <= 0)
        return fprintf(stderr, "Invalid board width\n"), -1;
    if (boardSize.height <= 0)
        return fprintf(stderr, "Invalid board height\n"), -1;
    if (squareSize <= 0)
        return fprintf(stderr, "Invalid board square width\n"), -1;
    if (aspectRatio <= 0)
        return printf("Invalid aspect ratio\n"), -1;
    // 检查输入的图像列表文件是否有效
    if(inputFilename.empty() ||
       !readStringList(inputFilename, imageList) ||
       imageList.size() == 0 || imageList.size() % 3 != 0 )
    {
        printf("Error: the input image list is not specified, or cannot be read, or the number of files is not divisible by 3\n");
        return -1;
    }


    // 定义一些需要用到的变量
    Mat view, viewGray; // view 存储当前图像,viewGray 存储转换为灰度的图像
    // 定义三个摄像头的相机矩阵、畸变系数、旋转矩阵和投影矩阵
    Mat cameraMatrix[3], distCoeffs[3], R[3], P[3], R12, T12;
    for(k = 0; k < 3; k++)
    {
        cameraMatrix[k] = Mat_<double>::eye(3,3);
        cameraMatrix[k].at<double>(0,0) = aspectRatio;
        cameraMatrix[k].at<double>(1,1) = 1;
        distCoeffs[k] = Mat_<double>::zeros(5,1);
    }
    // 定义第三个摄像头相对于第一个的旋转和平移矩阵
    Mat R13 = Mat_<double>::eye(3,3), T13 = Mat_<double>::zeros(3,1);


    FileStorage fs; // 文件存储类
    namedWindow("Image View", 0); // 创建一个窗口


    // 为每个摄像头的图像点预留空间
    for(k = 0; k < 3; k++)
        imgpt[k].resize(imageList.size()/3);// 这是调整图像点向量中第k个元素的大小,使其能够容纳图像列表大小的三分之一的元素


    // 加载每张图像,找到棋盘角点,储存至imgpt
    for(i = 0; i < (int)(imageList.size()/3); i++ )
    {
        for(k = 0; k < 3; k++)//遍历三个相机
        {   //令k1等于2(k=0时),0(k=1时),1(k=2时)
            int k1 = k == 0 ? 2 : k == 1 ? 0 : 1;//图像文件路径列表依次对应 相机2、相机1、相机3的图像路径
            printf("%s\n", imageList[i*3+k].c_str());
            view = imread(imageList[i*3+k], IMREAD_COLOR);


            //如果图像数据不为空,则继续处理图像
            if (!view.empty())
            {
                // 定义一个存储角点的变量
                vector<Point2f> ptvec;
                // 获取当前图像的尺寸
                imageSize = view.size();
                // 将彩色图像转为灰度图像
                cvtColor(view, viewGray, COLOR_BGR2GRAY);
                // 寻找暗格中的角点,并把它们保存到变量ptvec
                bool found = findChessboardCorners(view, boardSize, ptvec, CALIB_CB_ADAPTIVE_THRESH);


                //在图像上画出找到的角点
                drawChessboardCorners(view, boardSize, Mat(ptvec), found);
                // 如果找到角点,那么将其复制到imgpt[k1][i]
                if (found)
                {
                    // 重设目标存储空间的大小 
                    imgpt[k1][i].resize(ptvec.size());
                    // 复制找到的角点到目标存储空间
                    std::copy(ptvec.begin(), ptvec.end(), imgpt[k1][i].begin());
                }
                //imshow("view", view);
                //int c = waitKey(0) & 255;
                //if(c == 27 || c == 'q' || c == 'Q')
                //    return -1;
            }
        }
    }


    printf("Running calibration ...\n");


    // 进行标定计算
    run3Calibration(imgpt[0], imgpt[1], imgpt[2], imageSize,
                    boardSize, squareSize, aspectRatio, flags|CALIB_FIX_K4|CALIB_FIX_K5,
                    cameraMatrix[0], distCoeffs[0],
                    cameraMatrix[1], distCoeffs[1],
                    cameraMatrix[2], distCoeffs[2],
                    R12, T12, R13, T13);


    // 打开文件存储相机的参数
    fs.open(outputFilename, FileStorage::WRITE);


    // 将得到的相机参数写入文件
    fs << "cameraMatrix1" << cameraMatrix[0];
    fs << "cameraMatrix2" << cameraMatrix[1];
    fs << "cameraMatrix3" << cameraMatrix[2];


    fs << "distCoeffs1" << distCoeffs[0];
    fs << "distCoeffs2" << distCoeffs[1];
    fs << "distCoeffs3" << distCoeffs[2];


    fs << "R12" << R12;
    fs << "T12" << T12;
    fs << "R13" << R13;
    fs << "T13" << T13;


    fs << "imageWidth" << imageSize.width;
    fs << "imageHeight" << imageSize.height;


    // 计算校正变换
    Mat Q;
    double ratio = rectify3Collinear(cameraMatrix[0], distCoeffs[0], cameraMatrix[1],
             distCoeffs[1], cameraMatrix[2], distCoeffs[2],
             imgpt[0], imgpt[2],
             imageSize, R12, T12, R13, T13,
             R[0], R[1], R[2], P[0], P[1], P[2], Q, -1.,
             imageSize, 0, 0, CALIB_ZERO_DISPARITY);
    Mat map1[3], map2[3];


    // 将得到的校正变换写入文件
    fs << "R1" << R[0];
    fs << "R2" << R[1];
    fs << "R3" << R[2];


    fs << "P1" << P[0];
    fs << "P2" << P[1];
    fs << "P3" << P[2];


    fs << "disparityRatio" << ratio;
    fs.release();


    printf("Disparity ratio = %g\n", ratio);


    // 初始化用于矫正畸变的地图
    for(k = 0; k < 3; k++)
        initUndistortRectifyMap(cameraMatrix[k], distCoeffs[k], R[k], P[k], imageSize, CV_16SC2, map1[k], map2[k]);


    // 准备画布用于显示校正后的图像
    Mat canvas(imageSize.height, imageSize.width*3, CV_8UC3), small_canvas;
    destroyWindow("view");
    canvas = Scalar::all(0);


    // 遍历每张输入图片进行校正,并显示
    // 对于imageList中的每组三个图像(imageList大小的1/3,假设每组三张图像表示三种不同的视角),执行以下操作
    for (i = 0; i < (int)(imageList.size() / 3); i++)
    {
        // 创建一个空的画布,设置画布颜色为黑色
        canvas = Scalar::all(0);
        
        
        /*   这里顺序有些乱,以下是根据代码推断得出:
        顺序:
        索引 K:          0         1        2             
        相机位置:       左        中       右  
        图像列表         中        左        右    imageList[i * 3 + 0\1\2]
        矫正映射顺序     左        右        中    map1[0\1\2]
        cameraMatrix    左        右        中    cameraMatrix[0\1\2]
        */
        // 遍历三种视角的图像
        for (k = 0; k < 3; k++)
        {
            // 根据当前视角k计算下一个视角的索引k1和k2,确保它们在0,1,2之间循环
            int k1 = k == 0 ? 2 : k == 1 ? 0 : 1;//相机参数map顺序: 中map1[2]、左map1[0]、右map1[1]  
            int k2 = k == 0 ? 1 : k == 1 ? 0 : 2;// k=0 中间     k=1 左侧    k=2 右侧
            // 根据计算出的索引加载图像
            view = imread(imageList[i * 3 + k], IMREAD_COLOR);//顺序: 中、左、右
    
            // 如果图像为空(未加载成功),则跳过当前循环继续下一轮
            if (view.empty())
                continue;
    
            // 选取画布上对应视角k2的区域进行图像投射
            Mat rview = canvas.colRange(k2 * imageSize.width, (k2 + 1) * imageSize.width);
            // 使用remap函数对当前加载的图像进行重映射,并结果放在画布对应的区域
            remap(view, rview, map1[k1], map2[k1], INTER_LINEAR);
        }
        // 输出处理过的三个图像的文件名
        printf("%s %s %s\n", imageList[i * 3].c_str(), imageList[i * 3 + 1].c_str(), imageList[i * 3 + 2].c_str());
        // 将处理后的大画布缩小,宽度为1500,按照3:1的比例缩放
        resize(canvas, small_canvas, Size(1500, 1500 / 3), 0, 0, INTER_LINEAR_EXACT);
        // 在小画布上从上到下,每隔16行绘制一条绿色的横线
        for (k = 0; k < small_canvas.rows; k += 16)
            line(small_canvas, Point(0, k), Point(small_canvas.cols, k), Scalar(0, 255, 0), 1);
        // 显示处理后已校正的图像
        imshow("rectified", small_canvas);
        // 等待用户输入,如果按下Esc键('27'),或者'q'/'Q'键,则退出循环
        char c = (char)waitKey(0);
        if (c == 27 || c == 'q' || c == 'Q')
            break;
    }
    return 0;
}

这段代码主要实现的是使用OpenCV进行三摄像头系统的校正(标定)和视差校正。整个过程分为读取图片、寻找棋盘角点、相机标定、存储标定结果、计算矫正变换和显示校正后的图像几个主要步骤。在校正过程中,它会处理由三个摄像头拍摄的图像,通过标定每个摄像头及它们之间的摆放关系,来确保在使用这些摄像头进行立体视觉任务时能得到准确的结果。

6305036765dd2adc7472fd0a708ee93e.png

const vector<vector<Point2f> >& imgpt0 = c == 1 ? imagePoints1 : c == 2 ? imagePoints2 : imagePoints3;

c2aa1378b7cb5277d6dbf8deafaf1bf2.png

double err = calibrateCamera(objpt, imgpt, imageSize, cameraMatrix,
    distCoeffs, rvecs, tvecs,
    flags | CALIB_FIX_K3/*|CALIB_FIX_K4|CALIB_FIX_K5|CALIB_FIX_K6*/);

9747f2e28c27987af6f5a294f792604c.png

929505972fc78a597e229e82cfd3f598.png

848f871c9f8e631ec2fa7cb718ccdc19.png

double err = stereoCalibrate(objpt, imgpt, imgpt_right, cameraMatrix1, distCoeffs1,
    cameraMatrix, distCoeffs,
    imageSize, R, T, E, F,
    CALIB_FIX_INTRINSIC,
    TermCriteria(TermCriteria::COUNT, 30, 0));

6f986b72ea95e7bb0421b82e28d61479.png

@param objectPoints 校准图案点的向量数组。与 @ref calibrateCamera 中的结构相同。
对于每个图案视图,两个摄像机都需要看到相同的对象点。因此,objectPoints.size()、imagePoints1.size() 和 imagePoints2.size() 
需要相等,并且对于每个 i,objectPoints[i].size()、imagePoints1[i].size() 和 imagePoints2[i].size() 
也需要相等。
@param imagePoints1 第一个摄像头观测到的校准图案点的投影的向量数组。结构与 @ref calibrateCamera 中的相同。
@param imagePoints2 第二个摄像头观测到的校准图案点的投影的向量数组。结构与 @ref calibrateCamera 中的相同。
@param cameraMatrix1 输入/输出第一个摄像头的内在矩阵,与 @ref calibrateCamera 中的相同。
此外,对于立体情况,可以使用额外的标志,见下文。
@param distCoeffs1 输入/输出畸变系数向量,与 @ref calibrateCamera 中的相同。
@param cameraMatrix2 输入/输出第二个摄像头的内在矩阵。参见 cameraMatrix1 的描述。
@param distCoeffs2 输入/输出第二个摄像头的镜头畸变系数。参见 distCoeffs1 的描述。
@param imageSize 仅用来初始化相机内在矩阵的图像大小。
@param R 输出旋转矩阵。与平移向量 T 一起,此矩阵将在第一个摄像头坐标系中给出的点带到第二个摄像头的
坐标系中。用更技术性的话来说,R 和 T 的元组执行了一个从第一个摄像头坐标系到第二个摄像头坐标系的基变换。
由于它的二元性,这个元组等同于第一个摄像头相对于第二个摄像头坐标系的位置。
@param T 输出平移向量,见上述描述。
@param E 输出本质矩阵。
@param F 输出基础矩阵。
@param rvecs 输出每个图案视图在立体摄像头对的第一个摄像头坐标系中估计出的旋转向量向量( @ref Rodrigues )(例如 std::vector<cv::Mat>)。更详细地说,每个第 i 个旋转向量连同相应的第 i 个平移向量(见下一个输出参数描述),将校准图案从对象坐标空间(指定对象点的空间)带到立体摄像头对的第一个摄像头的相机坐标空间。用更技术性的话来说,第 i 个旋转和平移向量的元组执行了一个从对象坐标空间到立体摄像头对的第一个摄像头的相机坐标空间的基变换。
@param tvecs 输出每个图案视图估计出的平移向量向量,参见前面输出参数的参数描述( rvecs )。
@param perViewErrors 输出为每个图案视图估计的RMS重投影误差的向量。
@param flags 可以为零或下列值的组合的不同标志:
-   @ref CALIB_FIX_INTRINSIC 固定 cameraMatrix? 和 distCoeffs?,以便仅估算 R、T、E 和 F 矩阵。
-   @ref CALIB_USE_INTRINSIC_GUESS 根据指定的标志优化部分或所有内在参数。初始值由用户提供。
-   @ref CALIB_USE_EXTRINSIC_GUESS R 和 T 包含有效的初始值,可进一步优化。否则,R 和 T 初始化为图案视图的中值(分别对每个维度)。
-   @ref CALIB_FIX_PRINCIPAL_POINT 在优化过程中固定主点。
-   @ref CALIB_FIX_FOCAL_LENGTH 固定 \f$f^{(j)}_x\f$ 和 \f$f^{(j)}_y\f$。
-   @ref CALIB_FIX_ASPECT_RATIO 优化 \f$f^{(j)}_y\f$。固定比率 \f$f^{(j)}_x/f^{(j)}_y\f$。
-   @ref CALIB_SAME_FOCAL_LENGTH 强制 \f$f^{(0)}_x=f^{(1)}_x\f$ 和 \f$f^{(0)}_y=f^{(1)}_y\f$。
-   @ref CALIB_ZERO_TANGENT_DIST 将每个摄像头的切向失真系数设置为零并固定在那里。
-   @ref CALIB_FIX_K1,..., @ref CALIB_FIX_K6 在优化过程中不改变相应的径向
失真系数。如果设置了 @ref CALIB_USE_INTRINSIC_GUESS,
则使用提供的 distCoeffs 矩阵中的系数。否则,它被设置为 0。
-   @ref CALIB_RATIONAL_MODEL 启用系数 k4、k5 和 k6。为了提供向后
兼容性,应明确指定这个额外的标志,使校准
函数使用有理模型并返回 8 个系数。如果没有设置标志,
函数计算并仅返回 5 个失真系数。
-   @ref CALIB_THIN_PRISM_MODEL 启用系数 s1、s2、s3 和 s4。为了提供
向后兼容性,应明确指定这个额外的标志,使校准
函数使用薄棱镜模型并返回 12 个系数。如果没有设置标志,
函数计算并仅返回 5 个失真系数。
-   @ref CALIB_FIX_S1_S2_S3_S4 在优化过程中不改变薄棱镜失真系数。如果设置了 @ref CALIB_USE_INTRINSIC_GUESS,
则使用提供的distCoeffs矩阵中的系数。否则,它被设置为 0。
-   @ref CALIB_TILTED_MODEL 启用系数 tauX 和 tauY。为了提供
向后兼容性,应明确指定这个额外的标志,使校准
函数使用倾斜的感应器模型并返回 14 个系数。如果没有设置标志,
函数计算并仅返回 5 个失真系数。
-   @ref CALIB_FIX_TAUX_TAUY 在优化过程中不改变倾斜感应器模型的系数。如果设置了 @ref CALIB_USE_INTRINSIC_GUESS,
则使用提供的 distCoeffs 矩阵中的系数。否则,它被设置为 0。
@param criteria 迭代优化算法的终止条件。

29563085674f81ed54be6aed99020006.png

//计算 3 头相机的校正变换,其中所有头都在同一条线上。
 double ratio = rectify3Collinear(cameraMatrix[0], distCoeffs[0], cameraMatrix[1],
       distCoeffs[1], cameraMatrix[2], distCoeffs[2],
       imgpt[0], imgpt[2],
       imageSize, R12, T12, R13, T13,
       R[0], R[1], R[2], P[0], P[1], P[2], Q, -1.,
       imageSize, 0, 0, CALIB_ZERO_DISPARITY);

3c52c33877e765f341ac03ba786cffe8.png

initUndistortRectifyMap(cameraMatrix[k], distCoeffs[k], R[k], P[k], imageSize, CV_16SC2, map1[k], map2[k]);

4be359d72096d4413ec5476771b44557.png

631a605f3d4f86cf7d1cc69df89ff6d1.png

https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值