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

news2024/11/19 16:20:40

此代码是一个利用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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式 --5观察者模式

观察者模式 观察者模式的优缺点 优点 当一个对象改变的时候 需要同时改变其他对象的相关动作的时候 &#xff0c;而且它不知道有多少具体的对象需要改变 应该考虑使用观察者模式 。观察者模式的工作就是解除耦合 让耦合双方都依赖与抽象 而不是具体 是的各自改变都不会影响另…

07-app端文章搜索

app端文章搜索 1) 今日内容介绍 1.1)App端搜索-效果图 1.2)今日内容 文章搜索 ElasticSearch环境搭建 索引库创建 文章搜索多条件复合查询 索引数据同步 搜索历史记录 Mongodb环境搭建 异步保存搜索历史 查看搜索历史列表 删除搜索历史 联想词查询 联想词的来源 联…

51单片机使用uart串口和助手简单调试

基础知识 参考 特殊功能寄存器PCON&#xff08;控制波特率是否加倍SMOD&#xff09;、TMOD&#xff08;T0,T1计时器的功能方式&#xff09;、TCON&#xff08;T0,T1计时器的控制&#xff09;、串口中断、SCON&#xff08;串口数据控制寄存器&#xff09; 关闭定时器1中断&…

Cisco交换机安全配置

Cisco交换机安全配置 前提 我们以下命令一般都要先进入Config模式 S1> enable S1# conf t S1(config)#端口安全保护 禁用未使用的端口 以关闭fa0/1到fa0/24的端口为例 S1(config)# interface range fa0/1-24 S1(config-if-range)# shutdown缓解MAC地址表攻击 防止CAM…

快递费用一目了然:taobao.item_fee API在电商中的应用

taobao.item_fee API在电商中的应用主要体现在精准计算快递费用&#xff0c;从而为用户提供一个更加透明和便捷的购物体验。这一接口允许淘宝或天猫的开发者根据商品ID、收货地址等信息&#xff0c;精确计算商品的快递费用。对于用户而言&#xff0c;这意味着在购物过程中能够实…

【考研数学】如何做题,做什么题,才能打好基础?

还是建议做经验贴推荐的练习册去巩固基础&#xff01;毕竟目的是考研&#xff0c;考研习题册出的题目更加有针对性&#xff0c;如果拿课后习题练手的话还是差一些强度的&#xff01; 看到网上说1800适合零基础&#xff0c;兴致勃勃下单&#xff0c;买回来发现自己练零基础的题…

学习 Git 基础知识 - 日常开发任务手册

欢迎来到我关于 Git 的综合指南&#xff0c;Git 是一种分布式版本控制系统&#xff0c;已经在软件开发中彻底改变了协作和代码管理方式。 无论你是经验丰富的开发者还是刚开始编程之旅的新手&#xff0c;理解 Git 对于正确掌控代码、高效管理项目和与他人合作至关重要。 在本…

mysql故障排查

MySQL是目前企业最常见的数据库之一日常维护管理的过程中&#xff0c;会遇到很多故障汇总了常见的故障&#xff0c;MySQL默认配置无法满足高性能要求 一 MySQL逻辑架构图 客户端和连接服务核心服务功能存储擎层数据存储层 二 MySQL单实例常见故障 故障1 ERROR 2002 (HY000)…

Docker Desktop 不支持 host 网络模式

先把这个结论的放在前面&#xff0c;直接访问链接就能看到官方文档中已经明确说了不支持。 参考链接&#xff1a;docker desktop for windows 不支持 host 网络模式 以前对于 docker 的网络模式&#xff0c;一直只是了解&#xff0c;没有亲自尝试过。结果今天在尝试 docker 的 …

06 - RS 触发器

---- 整理自B站UP主 踌躇月光 的视频 1. R-S 触发器电路电路 RSQQ’00QQ’011010011100 刚上电时&#xff0c;输出的状态是不确定的&#xff0c;只确定两个输出是相反的。 S0&#xff0c;R0 时候&#xff0c;Q 状态保持不变 S1&#xff0c;R0 时候&#xff0c;Q 状态被设置为 1…

X.509数字证书的结构与解析

1、什么叫数字签名 数字签名&#xff1a; 将报文按双方约定的HASH算法计算得到一个固定位数的报文摘要。在数学上保证&#xff1a;只要改动报文中任何一位&#xff0c;重新计算出的报文摘要值就会与原先的值不相符。这样就保证了报文的不可更改性。 将该报文摘要值用发送者的私…

关于Idea无法正常启动

编辑这个文件 最后一行 加上 pause 双击文件 会显示报错信息

4.4学习总结

一.线段树概念 一.定义: 线段树是一种二叉搜索树&#xff0c;而二叉搜索树&#xff0c;首先满足二叉树&#xff0c;即每个结点最多有两颗子树&#xff0c;并且是一颗搜索树&#xff0c;我们要知道&#xff0c;线段树的每个结点都存储了一个区间&#xff0c;也可以理解成一个线…

使用git 和 github协作开发

文章目录 github浏览器汉化插件github新建仓库git安装以及ssh配置团队创建及基本命令的使用创建团队基本命令 分支管理快速切换远程仓库地址 如何使用git && github进行协作开发&#xff0c;包括git常见基础命令 github浏览器汉化插件 在刚开始使用github的时候&#…

微信小程序上传到gitee

共三步 1、新建gitee仓库 点号&#xff0c;新建仓库&#xff0c;填入仓库信息新建即可 2、修改版本管理参数 微信开发者工具中点开版本管理&#xff0c;未初始化&#xff0c;需要先点初始化 接下来将设置中的通用、网络认证、远程3个部分的参数填写好 通用&#xff1a;核对…

【从浅学到熟知Linux】冯诺依曼体系结构及进程概念详谈!

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程及数据库等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 冯诺依曼体系结构操作系统如何理解管理操作系统概念设计操作系统目的系统调用和库函数概念 进程基…

DashOJ-8.奇偶统计

题目链接&#xff1a; 题目详情 - 奇偶统计 - DashOJ 思路&#xff1a; &#xff08;while循环加if分支语句&#xff09; 巧用死循环 while(1) 然后在里面第一句就判断输入的数字是否等于0 if(x0) &#xff0c;如果 等于0就直接break跳出循环 或者用 while(cin>>x) 代…

红黑树内部结点数量分析

红黑树内部结点数量分析 一、红黑树的性质二、黑高与内部结点数量2.1最大内部结点数量2.2最小内部结点数量 三、伪代码实现四、C语言代码实现五、结论 红黑树是一种自平衡的二叉搜索树&#xff0c;它通过一系列复杂的性质和操作来维持平衡&#xff0c;从而确保各种动态集合操作…

Docker 容器编排技术解析与实践

探索了容器编排技术的核心概念、工具和高级应用&#xff0c;包括 Docker Compose、Kubernetes 等主要平台及其高级功能如网络和存储管理、监控、安全等。此外&#xff0c;文章还探讨了这些技术在实际应用中的案例&#xff0c;提供了对未来趋势的洞见。 一、容器编排介绍 容器编…

用 Wireshark 解码 H.264

H264&#xff0c;你不知道的小技巧-腾讯云开发者社区-腾讯云 这篇文章写的非常好 这里仅做几点补充 init.lua内容&#xff1a; -- Set enable_lua to false to disable Lua support. enable_lua trueif not enable_lua thenreturn end-- If false and Wireshark was start…