【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/519293.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue 事件处理 -- 事件修饰符(prevent、stop、capture、self、once)

1. 事件修饰符 Vue中的事件修饰符&#xff1a; prevent&#xff1a;阻止默认事件&#xff08;常用&#xff09;&#xff1b;stop&#xff1a;阻止事件冒泡&#xff08;常用&#xff09;&#xff1b;once&#xff1a;事件只触发一次&#xff08;常用&#xff09;&#xff1b;cap…

软考 系统架构设计师系列知识点之数据库基本概念(1)

所属章节&#xff1a; 第6章. 数据库设计基础知识 第1节 数据库基本概念 数据&#xff08;Data&#xff09;是描述事务的符号记录&#xff0c;它具有多种表现形式&#xff0c;可以是文字、图形、图像、声音和语言等。信息&#xff08;Information&#xff09;是现实世界事物的…

考研回忆录【二本->211】

备考时长差不多快一年半&#xff0c;从22年的11月底开始陆陆续续地准备考研&#xff0c;因为开始的早所以整个备考过程显得压力不是很大&#xff0c;中途还去一些地方旅游&#xff0c;我不喜欢把自己绷得太紧。虽然考的不是很好&#xff0c;考完我甚至都没准备复试&#xff0c;…

ChatGPT 的核心 GPT 模型:探究其生成式预训练变换架构的革新与应用潜力

GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型是一种深度学习模型&#xff0c;由OpenAI于2018年首次提出&#xff0c;并在随后的几年中不断迭代发展&#xff0c;包括GPT-2、GPT-3以及最新的GPT-4。GPT模型在自然语言处理&#xff08;NLP&#xff09;领域…

JAVA毕业设计132—基于Java+Springboot+Vue的自习室座位预约小程序管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的自习室座位预约小程序管理系统(源代码数据库)132 一、系统介绍 本项目前后端分离带小程序&#xff0c;分为管理员、用户两种角色 1、用户&#xff1a; 注…

跨平台的组播测试工具mping、udp_sender及udp_reciver的源码及使用教程

文章目录 1.前言2.mping工具编译3.mping工具使用3.1 参数说明3.1 组播播发&#xff08;-s&#xff09;3.1 组播播发&#xff08;-r&#xff09;3.3 Linux下mping测试 4.Linux组播udp_sender及udp_reciver使用4.1 udp_sender源码4.1 udp_reciver源码4.3 编译方法4.4 测试使用4.4…

Star GAN论文解析

论文地址&#xff1a;https://arxiv.org/pdf/1912.01865v1.pdf https://openaccess.thecvf.com/content_cvpr_2018/papers/Choi_StarGAN_Unified_Generative_CVPR_2018_paper.pdf 源码&#xff1a;stargan项目实战及源码解读-CSDN博客 1. 概述 在传统方法中&#x…

电子商务平台中大数据的应用|主流电商平台大数据采集API接口

(一)电商平台物流管理中大数据的应用 电商平台订单详情订单列表物流信息API接口应用 电子商务企业对射频识别设备、条形码扫描设备、全球定位系统及销售网站、交通、库存等管理软件数据进行实时或近实时的分析研究,提高物流速度和准确性。部分电商平台已建立高效的物流配送网…

数据采集与整理:知识图谱的根基

数据采集与整理&#xff1a;知识图谱的根基 一、 引言 在今天的数据驱动的世界中&#xff0c;知识图谱已经成为了连接复杂信息的关键工具。它们不仅推动了人工智能的发展&#xff0c;还改变了我们管理和利用知识的方式。然而&#xff0c;任何优秀的知识图谱都离不开一个核心的…

docker安装Nexus,maven私服

文章目录 前言安装创建文件夹设置文件夹权限docker创建指令制作docker-compose.yaml文件 查看网站访问网页查看密码 前言 nexus作为私服的maven仓库&#xff0c;在企业级应用中&#xff0c;提供了依赖来源的稳定性&#xff0c;为构建庞大的微服务体系&#xff0c;打下基础 安…

docker安装、调试qsign签名服务器

go-cqhttp 在 Docker 里早就部署好了&#xff0c;由于没有搭建 qsign 签名服务器&#xff0c;所以迟迟不敢上线。今天终于搞定了在 Docker 下安装 qsign 签名服务器了。这次用的docker市场里找到的镜像&#xff0c;下次找时间制作一个自己的镜像。 1 拉取和运行镜像&#xff1a…

Win10文件夹共享(有密码的安全共享)(SMB协议共享)

前言 局域网内&#xff08;无安全问题&#xff0c;比如自己家里wifi&#xff09;无密码访问&#xff0c;参考之前的操作视频 【电脑文件全平台共享、播放器推荐】手机、电视、平板播放硬盘中的音、视频资源 下面讲解公共网络如办公室网络、咖啡厅网络等等环境下带密码的安全…

云备份day02

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C云备份项目 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 主要内容介绍了第三方库jsoncpp和bundle库的使用 文章目录 云备…

代码随想录算法训练营第三十一天| 理论基础、LeetCode 455.分发饼干、376. 摆动序列、53. 最大子序和

一、理论基础 文章讲解&#xff1a;https://programmercarl.com/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 1.贪心的定义 贪心的本质是选择每一阶段的局部最优解&#xff0c;从而达到全局最优解。例如&#xff0c;有一堆钞票&#xff0c…

MySQL常见锁探究

MySQL常见锁探究 1. 各种锁类型1.1 全局锁1.2 表级锁1.2.1 表锁1.2.2 元数据锁&#xff08;MDL&#xff09;1.2.3 意向锁1.2.4 AUTO-INC 锁 1.3 行级锁1.3.1 Record Lock1.3.2 Gap Lock1.3.3 Next-Key Lock 2. MySQL是如何加锁的&#xff1f;2.1 什么 SQL 语句会加行级锁&#…

WPS 不登录无法使用基本功能的解决办法

使用wps时&#xff0c;常常有个比较让人烦恼的事&#xff0c;在不登录的情况下&#xff0c;新建或者打开文档时&#xff0c;wps不让你使用其基本的功能&#xff0c;如设置字体等&#xff0c;相关界面变成灰色&#xff0c;这时Wps提示用户登录注册或登录&#xff0c;但我又不想登…

喜讯 ChatGPT 3.5 免登录|免注册就可以使用了

https://chat.openai.com/ 直接访问openai 官网直接使用&#xff0c;当然还是要魔法的&#xff0c;不用再去用别人二次开发的&#xff0c;还有次数限制&#xff0c;还有开会员&#x1f605;才能用的。&#x1f600;试用啦一下&#xff0c;基本秒回答&#xff0c;能力也是在线的…

深入浅出 -- 系统架构之微服务架构常见的六种设计模式

面向服务的架构&#xff08;SOA&#xff09; 面向服务的架构&#xff08;SOA&#xff09;是一种设计方法&#xff0c;也是一个组件模型&#xff0c;它将应用程序的不同功能单元&#xff08;称为服务&#xff09;通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的…

软件工程导论

软件工程选择题复习笔记 一、软件工程学概述 用户使用不当、硬件可靠性差、对软件的错误认识属于软件危机的表现&#xff0c;不是原因软件危机&#xff0c;1960年以来&#xff0c;软件工程1968提出软件工程着重于建造一个软件系统 八个阶段可以归纳为计划(定义)阶段&#xf…

一次java.lang.NullPointerException的排查之旅

一次java.lang.NullPointerException的排查之旅 问题由来问题分析问题处理 问题由来 最近在项目中遇到了一个比较奇怪的java.lang.NullPointerException&#xff0c;就是说在自己的本地环境中&#xff0c;功能正常&#xff0c;运行无异常。但是测试环境点击同样的功能时却总是…