【opencv】示例-stereo_calib.cpp 基于OpenCV的立体视觉相机校准的完整示例

cpp 复制代码
// 包含OpenCV库中用于3D校准的相关头文件
#include "opencv2/calib3d.hpp"
// 包含OpenCV库中用于图像编码解码的相关头文件
#include "opencv2/imgcodecs.hpp"
// 包含OpenCV库中用于GUI操作的相关头文件
#include "opencv2/highgui.hpp"
// 包含OpenCV库中用于图像处理的相关头文件
#include "opencv2/imgproc.hpp"
// 包含OpenCV库中用于处理ChArUco板的相关头文件
#include "opencv2/objdetect/charuco_detector.hpp"


// 引入一些常用的标准库
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>


// 使用cv和std名称空间中的变量和函数,避免每次调用时都写cv::和std::
using namespace cv;
using namespace std;


// 声明一个静态函数print_help,用来打印程序的使用说明
// print_help函数的实现:打印使用帮助说明
static int print_help(char** argv)
{
    // 输出程序的使用方法,该方法包括双目校准过程中所需的参数说明
    cout <<
            " Given a list of chessboard or ChArUco images, the number of corners (nx, ny)\n"
            " on the chessboards and the number of squares (nx, ny) on ChArUco,\n"
            " and a flag: useCalibrated for \n"
            "   calibrated (0) or\n"
            "   uncalibrated \n"
            "     (1: use stereoCalibrate(), 2: compute fundamental\n"
            "         matrix separately) stereo. \n"
            " Calibrate the cameras and display the\n"
            " rectified results along with the computed disparity images.   \n" << endl;
    // 输出程序的具体使用格式,包括棋盘宽度,高度,模式类型(棋盘或ChArUco),平方大小,标记大小,预定义的aruco字典名称,aruco字典文件和图像列表XML/YML文件
    cout << "Usage:\n " << argv[0] << " -w=<board_width default=9> -h=<board_height default=6>"
        <<" -t=<pattern type: chessboard or charucoboard default=chessboard> -s=<square_size default=1.0> -ms=<marker size default=0.5>"
        <<" -ad=<predefined aruco dictionary name default=DICT_4X4_50> -adf=<aruco dictionary file default=None>"
        <<" <image list XML/YML file default=stereo_calib.xml>\n" << endl;
    // 打印可用的Aruco字典列表信息
    cout << "Available Aruco dictionaries: DICT_4X4_50, DICT_4X4_100, DICT_4X4_250, "
        << "DICT_4X4_1000, DICT_5X5_50, DICT_5X5_100, DICT_5X5_250, DICT_5X5_1000, "
        << "DICT_6X6_50, DICT_6X6_100, DICT_6X6_250, DICT_6X6_1000, DICT_7X7_50, "
        << "DICT_7X7_100, DICT_7X7_250, DICT_7X7_1000, DICT_ARUCO_ORIGINAL, "
        << "DICT_APRILTAG_16h5, DICT_APRILTAG_25h9, DICT_APRILTAG_36h10, DICT_APRILTAG_36h11\n";


    // 函数返回0,表示成功执行
    return 0;
}
// 声明一个静态函数StereoCalib,用于执行双目相机的校准
// StereoCalib函数的实现:执行双目摄像头的校准
static void
// 函数定义,包括所需的参数
StereoCalib(const vector<string>& imagelist, Size inputBoardSize, string type, float squareSize, float markerSize, cv::aruco::PredefinedDictionaryType arucoDict, string arucoDictFile, bool displayCorners = false, bool useCalibrated=true, bool showRectified=true)
{
    // 检查图像列表的数量是否为偶数,否则返回错误
    if( imagelist.size() % 2 != 0 )
    {
        cout << "Error: the image list contains odd (non-even) number of elements\n";
        return;
    }


    // 定义变量和存储来进行校准过程
    const int maxScale = 2;
    // ARRAY AND VECTOR STORAGE:


    // 创建两个图像点数组和一个对象点向量,以及图像大小变量
    vector<vector<Point2f> > imagePoints[2];
    vector<vector<Point3f> > objectPoints;
    Size imageSize;


    // 定义一些需要的索引变量和图像数量
    int i, j, k, nimages = (int)imagelist.size()/2;


    // 调整图像点数组的大小以匹配图像的数量
    imagePoints[0].resize(nimages);
    imagePoints[1].resize(nimages);
    // 创建一个存储良好图像的列表
    vector<string> goodImageList;


    // 定义棋盘的两种尺寸,内角尺寸和单位尺寸
    Size boardSizeInnerCorners, boardSizeUnits;
    // 检查棋盘的类型,并依此计算板大小
    if (type == "chessboard") {
        // 若是普通棋盘,则内角大小即为给定的板大小
        boardSizeInnerCorners = inputBoardSize;
        // 棋盘单位尺寸需要增加1,因为边缘的格子也要计算进去
        boardSizeUnits.height = inputBoardSize.height+1;
        boardSizeUnits.width = inputBoardSize.width+1;
    }
    else if (type == "charucoboard") {
        // 若是ChArUco棋盘,板大小则是以方块为单位给出的
        boardSizeUnits = inputBoardSize;
        // 减去1以得到内角尺寸
        boardSizeInnerCorners.width = inputBoardSize.width - 1;
        boardSizeInnerCorners.height = inputBoardSize.height - 1;
    }
    else {
        // 若棋盘类型未知,则输出错误并返回
        std::cout << "unknown pattern type " << type << "\n";
        return;
    }


    // 定义并初始化Aruco字典
    cv::aruco::Dictionary dictionary;
    // 如果未指定字典文件,则使用预定义字典
    if (arucoDictFile == "None") {
        dictionary = cv::aruco::getPredefinedDictionary(arucoDict);
    }
    else {
        // 否则从文件中加载字典
        cv::FileStorage dict_file(arucoDictFile, cv::FileStorage::Mode::READ);
        cv::FileNode fn(dict_file.root());
        dictionary.readDictionary(fn);
    }
    // 创建ChArUco板和检测器对象
    cv::aruco::CharucoBoard ch_board(boardSizeUnits, squareSize, markerSize, dictionary);
    cv::aruco::CharucoDetector ch_detector(ch_board);
    // 创建一个用来存储标记的ID的容器
    std::vector<int> markerIds;


        // 对图像列表中的每一对图像进行处理
    for( i = j = 0; i < nimages; i++ )
    {
        // inner loop to go through each image of the pair
        for( k = 0; k < 2; k++ )
        {
            // 获取当前处理的图像的文件名
            const string& filename = imagelist[i*2+k];
            // 以灰度模式读取图像
            Mat img = imread(filename, IMREAD_GRAYSCALE);
            // 如果图像为空,跳过当前循环 
            if(img.empty())
                break;
            // 如果imageSize尚未设置,初始化它
            if( imageSize == Size() )
                imageSize = img.size();
            // 如果当前读取的图像大小与之前的不一致,输出错误信息并跳过当前图像对
            else if( img.size() != imageSize )
            {
                cout << "The image " << filename << " has the size different from the first image size. Skipping the pair\n";
                break;
            }
            // 定义一个布尔类型的变量,用来判断是否找到角点 
            bool found = false;
            // 引用当前图像对应的角点向量
            vector<Point2f>& corners = imagePoints[k][j];
            // 尝试不同的图像缩放级别,以找到角点
            for( int scale = 1; scale <= maxScale; scale++ )
            {
                // 根据当前的缩放等级,准备图像
                Mat timg;
                // 如果是原始尺度,直接使用原图
                if( scale == 1 )
                    timg = img;
                else
                    // 不是原始尺度时,改变图像大小
                    resize(img, timg, Size(), scale, scale, INTER_LINEAR_EXACT);
                    // 根据棋盘类型找到角点,并存储到corners变量中
                    if (type == "chessboard") {
                        // 查找棋盘角点
                        found = findChessboardCorners(timg, boardSizeInnerCorners, corners,
                            CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
                    }
                    else if (type == "charucoboard") {
                        // 查找ChArUco板角点
                        ch_detector.detectBoard(timg, corners, markerIds);
                        found = corners.size() == (size_t) (boardSizeInnerCorners.height*boardSizeInnerCorners.width);
                    }
                    else {
                        // 若棋盘类型未知,输出错误信息并返回
                        cout << "Error: unknown pattern " << type << "\n";
                        return;
                    }
                // 如果找到角点,结束当前缩放级别的处理
                if( found )
                {
                    // 如果图像已缩放,将角点尺度调整回原始图像尺度
                    if( scale > 1 )
                    {
                        Mat cornersMat(corners);
                        cornersMat *= 1./scale;
                    }
                    break;
                }
            }
            // 如果需要显示每个角点,则进行绘制并显示
            if( displayCorners )
            {
                cout << filename << endl;
                Mat cimg, cimg1;
                cvtColor(img, cimg, COLOR_GRAY2BGR);
                drawChessboardCorners(cimg, boardSizeInnerCorners, corners, found);
                double sf = 640./MAX(img.rows, img.cols);
                resize(cimg, cimg1, Size(), sf, sf, INTER_LINEAR_EXACT);
                imshow("corners", cimg1);
                char c = (char)waitKey(500);
                if( c == 27 || c == 'q' || c == 'Q' ) // 允许使用ESC键退出
                    exit(-1);
            }
            else
                putchar('.');
            // 如果没有找到角点,结束当前图像对处理
            if( !found )
                break;
            // 对找到的角点进行亚像素精调
            if (type == "chessboard") {
                cornerSubPix(img, corners, Size(11, 11), Size(-1, -1),
                    TermCriteria(TermCriteria::COUNT + TermCriteria::EPS,
                        30, 0.01));
            }
        }
        // 如果两张图像都已处理,将其添加到良好图像列表中,并计数
        if( k == 2 )
        {
            goodImageList.push_back(imagelist[i*2]);
            goodImageList.push_back(imagelist[i*2+1]);
            j++;
        }
    }
    cout << j << " pairs have been successfully detected.\n";
    nimages = j;
    // 如果检测到的图像对过少,则返回错误信息 
    if( nimages < 2 )
    {
        cout << "Error: too little pairs to run the calibration\n";
        return;
    }


    // 根据检测到的图像对调整向量的大小
    imagePoints[0].resize(nimages);
    imagePoints[1].resize(nimages);
    objectPoints.resize(nimages);
    
    // 为每个图像对生成三维场景中的角点坐标
    for( i = 0; i < nimages; i++ )
    {
        // 通过循环遍历棋盘的每个位置
        for( j = 0; j < boardSizeInnerCorners.height; j++ )
            for( k = 0; k < boardSizeInnerCorners.width; k++ )
                // 定位场景中的3D点
                objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, 0));
    }


    // 输出开始校准的信息
    cout << "Running stereo calibration ...\n";


    // 声明并初始化相机矩阵和畸变系数
    Mat cameraMatrix[2], distCoeffs[2];
    // 使用固有的相机猜测来初始化相机矩阵
    cameraMatrix[0] = initCameraMatrix2D(objectPoints,imagePoints[0],imageSize,0);
    cameraMatrix[1] = initCameraMatrix2D(objectPoints,imagePoints[1],imageSize,0);
    // 声明旋转矩阵、平移矩阵、本质矩阵和基础矩阵
    Mat R, T, E, F;


    // 调用stereoCalibrate函数进行立体校准
    double rms = stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
                    cameraMatrix[0], distCoeffs[0],
                    cameraMatrix[1], distCoeffs[1],
                    imageSize, R, T, E, F,
                    // 定义校准时的标志
                    CALIB_FIX_ASPECT_RATIO +
                    CALIB_ZERO_TANGENT_DIST +
                    CALIB_USE_INTRINSIC_GUESS +
                    CALIB_SAME_FOCAL_LENGTH +
                    CALIB_RATIONAL_MODEL +
                    CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,
                    // 校准准则
                    TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-5) );
    // 输出校准的RMS误差
    cout << "done with RMS error=" << rms << endl;


    // CALIBRATION QUALITY CHECK
    // 因为基础矩阵隐含了所有输出信息,
    // 我们可以使用极线几何约束来检查校准的质量:m2^t*F*m1=0
    // 初始化误差值和点数总计
    double err = 0;
    int npoints = 0;
    // 创建线性向量数组,此处为2视图
    vector<Vec3f> lines[2];
    // 对于每一组图像,计算极线和对应点的误差
    for( i = 0; i < nimages; i++ )
    {
        // 获取第一视图中的点数
        int npt = (int)imagePoints[0][i].size();
        // 创建Mat对象来存储两视图的校正后像素点
        Mat imgpt[2];
        for( k = 0; k < 2; k++ )
        {
            // 拷贝图像点到Mat对象
            imgpt[k] = Mat(imagePoints[k][i]);
            // 校正畸变
            undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);
            // 计算极线
            computeCorrespondEpilines(imgpt[k], k+1, F, lines[k]);
        }
        // 计算并累加每个点的误差
        for( j = 0; j < npt; j++ )
        {
            // 使用极线方程计算误差
            double errij = fabs(imagePoints[0][i][j].x*lines[1][j][0] +
                                imagePoints[0][i][j].y*lines[1][j][1] + lines[1][j][2]) +
                           fabs(imagePoints[1][i][j].x*lines[0][j][0] +
                                imagePoints[1][i][j].y*lines[0][j][1] + lines[0][j][2]);
            // 累加总误差
            err += errij;
        }
        // 更新点数总计
        npoints += npt;
    }
    // 打印平均极线误差
    cout << "average epipolar err = " <<  err/npoints << endl;


    // 保存内参数
    FileStorage fs("intrinsics.yml", FileStorage::WRITE);
    if( fs.isOpened() )
    {
        // 写入相机矩阵和畸变系数到文件
        fs << "M1" << cameraMatrix[0] << "D1" << distCoeffs[0] <<
            "M2" << cameraMatrix[1] << "D2" << distCoeffs[1];
        fs.release(); // 关闭文件
    }
    else
        cout << "Error: can not save the intrinsic parameters\n";


    // 定义存储校正结果的变量
    Mat R1, R2, P1, P2, Q;
    // 定义有效的ROI区域数组
    Rect validRoi[2];


    // 立体校正函数
    stereoRectify(cameraMatrix[0], distCoeffs[0],
                  cameraMatrix[1], distCoeffs[1],
                  imageSize, R, T, R1, R2, P1, P2, Q,
                  CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);


    // 打开外参数文件进行写入
    fs.open("extrinsics.yml", FileStorage::WRITE);
    if( fs.isOpened() )
    {
        // 写入外参数到文件
        fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
        fs.release(); // 关闭文件
    }
    else
        cout << "Error: can not save the extrinsic parameters\n";


    // 检测立体摄像头的排列,是左-右还是上-下
    bool isVerticalStereo = fabs(P2.at<double>(1, 3)) > fabs(P2.at<double>(0, 3));


// 计算和显示校正结果,这一部分包括了校正图像生成的映射
    if( !showRectified )
        // 如果不显示校正结果,则直接返回
        return;


    // 存储映射的变量
    Mat rmap[2][2];
// 如果选择了校正(使用BOUGUET'S METHOD)
    if( useCalibrated )
    {
        // 说明全部校正计算已完成
    }
// 如果未校正(使用HARTLEY'S METHOD)
    else
 // 使用每个相机的内参数,但直接从基础矩阵计算校正转换
    {
        // 创建存储所有图像点的向量
        vector<Point2f> allimgpt[2];
        // 拷贝所有图像点到向量中
        for( k = 0; k < 2; k++ )
        {
            for( i = 0; i < nimages; i++ )
                std::copy(imagePoints[k][i].begin(), imagePoints[k][i].end(), back_inserter(allimgpt[k]));
        }
        // 通过8点算法找到基础矩阵
        F = findFundamentalMat(Mat(allimgpt[0]), Mat(allimgpt[1]), FM_8POINT, 0, 0);
        // 定义和计算校正未校正的立体视图所需的单应性矩阵
        Mat H1, H2;
        stereoRectifyUncalibrated(Mat(allimgpt[0]), Mat(allimgpt[1]), F, imageSize, H1, H2, 3);


        // 根据单应性矩阵计算旋转矩阵和投影矩阵
        R1 = cameraMatrix[0].inv()*H1*cameraMatrix[0];
        R2 = cameraMatrix[1].inv()*H2*cameraMatrix[1];
        P1 = cameraMatrix[0];
        P2 = cameraMatrix[1];
    }


    // 预计算映射
    initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
    initUndistortRectifyMap(cameraMatrix[1], distCoeffs[1], R2, P2, imageSize, CV_16SC2, rmap[1][0], rmap[1][1]);


    // 创建画布用于显示校正后的图像
    Mat canvas;
    // 缩放因子
    double sf;
    // 定义宽度和高度
    int w, h;
    // 根据摄像头布局配置画布
    if( !isVerticalStereo )
    {
        // 对于水平布局,设置宽度和高度
        sf = 600./MAX(imageSize.width, imageSize.height);
        w = cvRound(imageSize.width*sf);
        h = cvRound(imageSize.height*sf);
        // 创建画布,双倍宽度用于并排显示
        canvas.create(h, w*2, CV_8UC3);
    }
    else
    {
        // 对于垂直布局,设置宽度和高度
        sf = 300./MAX(imageSize.width, imageSize.height);
        w = cvRound(imageSize.width*sf);
        h = cvRound(imageSize.height*sf);
        // 创建画布,双倍高度用于上下显示
        canvas.create(h*2, w, CV_8UC3);
    }


    // 循环遍历所有校准的图像对
    for( i = 0; i < nimages; i++ )
    {
        // 对每一对图像进行处理
        for( k = 0; k < 2; k++ )
        {
            // 读取图像对中的一幅图像,并将它转换为灰度图
            Mat img = imread(goodImageList[i*2+k], IMREAD_GRAYSCALE), rimg, cimg;
            // 使用预先计算的地图来变换图像,消除畸变并校正
            remap(img, rimg, rmap[k][0], rmap[k][1], INTER_LINEAR);
            // 将校正后的单通道图像转换为三通道图像
            cvtColor(rimg, cimg, COLOR_GRAY2BGR);
            // 为校正后图像切割画布部分,垂直立体时使用不同布局
            Mat canvasPart = !isVerticalStereo ? canvas(Rect(w*k, 0, w, h)) : canvas(Rect(0, h*k, w, h));
            // 将校正后图像缩放到与画布部分匹配的大小
            resize(cimg, canvasPart, canvasPart.size(), 0, 0, INTER_AREA);
            // 如果使用校准的结果,绘制有效的ROI(感兴趣区域)
            if( useCalibrated )
            {
                // 计算并圆整ROI区域用于显示
                Rect vroi(cvRound(validRoi[k].x*sf), cvRound(validRoi[k].y*sf),
                          cvRound(validRoi[k].width*sf), cvRound(validRoi[k].height*sf));
                // 绘制显示有效ROI区域的矩形框
                rectangle(canvasPart, vroi, Scalar(0,0,255), 3, 8);
            }
        }


        // 在画布上绘制用于辅助对齐的线条
        if( !isVerticalStereo )
            // 对于水平摄像机布局,在水平方向画线
            for( j = 0; j < canvas.rows; j += 16 )
                line(canvas, Point(0, j), Point(canvas.cols, j), Scalar(0, 255, 0), 1, 8);
        else
            // 对于垂直摄像机布局,在垂直方向画线
            for( j = 0; j < canvas.cols; j += 16 )
                line(canvas, Point(j, 0), Point(j, canvas.rows), Scalar(0, 255, 0), 1, 8);
        // 显示校正后的图像
        imshow("rectified", canvas);
        // 等待按键事件
        char c = (char)waitKey();
        // 如果按下ESC或'q'/'Q'键,退出循环
        if( c == 27 || c == 'q' || c == 'Q' )
            break;
    }
    // 函数结尾
}


// 声明一个静态函数readStringList,用于从文件中读取字符串列表
static bool readStringList( const string& filename, vector<string>& l )
{
    // 初始化字符串列表大小为0
    l.resize(0);
    // 打开文件
    FileStorage fs(filename, FileStorage::READ);
    // 如果打开失败,返回false
    if( !fs.isOpened() )
        return false;
    // 读取文件的第一个节点
    FileNode n = fs.getFirstTopLevelNode();
    // 如果节点类型不是序列,返回false
    if( n.type() != FileNode::SEQ )
        return false;
    // 遍历节点,将每个元素添加到l列表中
    FileNodeIterator it = n.begin(), it_end = n.end();
    for( ; it != it_end; ++it )
        l.push_back((string)*it);
    return true;
}


int main(int argc, char** argv)
{
    // 定义棋盘格子的尺寸和其他参数
    Size inputBoardSize;
    string imagelistfn;
    bool showRectified;
    // 使用命令行参数解析器解析输入参数
    cv::CommandLineParser parser(argc, argv, "{w|9|}{h|6|}{t|chessboard|}{s|1.0|}{ms|0.5|}{ad|DICT_4X4_50|}{adf|None|}{nr||}{help||}{@input|stereo_calib.xml|}");
    if (parser.has("help"))
        return print_help(argv); // 如果请求帮助,打印帮助信息
    showRectified = !parser.has("nr"); // 是否显示校正后图像,默认为显示
    imagelistfn = samples::findFile(parser.get<string>("@input")); // 解析并获得图像列表文件路径
    inputBoardSize.width = parser.get<int>("w");   // 解析棋盘宽度
    inputBoardSize.height = parser.get<int>("h");  // 解析棋盘高度
    string type = parser.get<string>("t");         // 解析棋盘类型
    float squareSize = parser.get<float>("s");     // 解析棋盘格大小
    float markerSize = parser.get<float>("ms");    // 解析标记大小
    string arucoDictName = parser.get<string>("ad"); // 解析aruco字典名
    string arucoDictFile = parser.get<string>("adf"); // 解析文件路径,没有默认值


    // 根据名字解析预定义的aruco字典类型
    cv::aruco::PredefinedDictionaryType arucoDict;
    // 具体的字典名与类型匹配的代码(以下为名字与类型之间的映射)
    if (arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; }
    else if (arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; }
    else if (arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; }
    else if (arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; }
    else if (arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; }
    else if (arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; }
    else if (arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; }
    else if (arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; }
    else if (arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; }
    else if (arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; }
    else if (arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; }
    else if (arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; }
    else if (arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; }
    else if (arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; }
    else if (arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; }
    else if (arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; }
    else if (arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; }
    else if (arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; }
    else if (arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; }
    else if (arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; }
    else if (arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; }
    else {
        cout << "incorrect name of aruco dictionary \n";
        return 1;
    }


    // 检查命令行参数是否正确
    if (!parser.check())
    {
        parser.printErrors();
        return 1;
    }
    // 读取图像列表
    vector<string> imagelist;
    bool ok = readStringList(imagelistfn, imagelist);
    if(!ok || imagelist.empty())
    {
        // 如果无法打开图像列表文件或列表为空,则输出错误信息
        cout << "can not open " << imagelistfn << " or the string list is empty" << endl;
        return print_help(argv);
    }


    // 调用StereoCalib函数进行立体校准
    StereoCalib(imagelist, inputBoardSize, type, squareSize, markerSize, arucoDict, arucoDictFile, false, true, showRectified);
    return 0; // 主程序结束,返回0表示正常退出
}

这段代码是一个用于执行立体视觉系统校准的应用程序的主函数main。它按以下步骤执行:

初始化用于指定棋盘尺寸、图像列表文件名、是否展示校正结果等参数的变量。

解析命令行输入的参数,其中包括棋盘的宽度、高度、类型、格子大小、Aruco标记大小、Aruco字典名称、额外的字典文件名等。

根据参数中指定的Aruco字典名称,设置相应的Aruco字典类型。如果参数中指定的Aruco字典名称不正确,则打印错误并退出程序。

检查提供的命令行参数是否存在错误,如果有,则打印出错信息并退出。

读取图像列表文件,这个文件包含了用于立体校准的一组图像路径。

使用读取的参数和图像列表调用StereoCalib函数来进行立体视觉系统的校准。

其中,StereoCalib函数需要执行的步骤包括图像的读取、提取特征点、立体校准和参数保存等。如果图像列表文件无法打开或为空,或者命令行参数有误,程序将打印帮助信息并退出。

ini 复制代码
cameraMatrix[0] = initCameraMatrix2D(objectPoints,imagePoints[0],imageSize,0);
objectivec 复制代码
double rms = stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
                 cameraMatrix[0], distCoeffs[0],
                 cameraMatrix[1], distCoeffs[1],
                 imageSize, R, T, E, F,
                 CALIB_FIX_ASPECT_RATIO +
                 CALIB_ZERO_TANGENT_DIST +
                 CALIB_USE_INTRINSIC_GUESS +
                 CALIB_SAME_FOCAL_LENGTH +
                 CALIB_RATIONAL_MODEL +
                 CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,
                 TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-5) );
css 复制代码
undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);
css 复制代码
computeCorrespondEpilines(imgpt[k], k+1, F, lines[k]);
css 复制代码
stereoRectify(cameraMatrix[0], distCoeffs[0],
              cameraMatrix[1], distCoeffs[1],
              imageSize, R, T, R1, R2, P1, P2, Q,
              CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);
ini 复制代码
F = findFundamentalMat(Mat(allimgpt[0]), Mat(allimgpt[1]), FM_8POINT, 0, 0);
css 复制代码
stereoRectifyUncalibrated(Mat(allimgpt[0]), Mat(allimgpt[1]), F, imageSize, H1, H2, 3);
markdown 复制代码
initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
markdown 复制代码
remap(img, rimg, rmap[k][0], rmap[k][1], INTER_LINEAR);
css 复制代码
Rect vroi(cvRound(validRoi[k].x*sf), cvRound(validRoi[k].y*sf),
           cvRound(validRoi[k].width*sf), cvRound(validRoi[k].height*sf));
相关推荐
Mintopia1 小时前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮1 小时前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬1 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia2 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区2 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两5 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪5 小时前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain
strayCat232555 小时前
Clawdbot 源码解读 7: 扩展机制
人工智能·开源
王鑫星5 小时前
SWE-bench 首次突破 80%:Claude Opus 4.5 发布,Anthropic 的野心不止于写代码
人工智能