OpenCV联合C++/Qt 学习笔记(二十二)----相机模型与投影及单目相机标定

一、相机模型与投影

1、单目相机模型

1.1.1 单目相机成像原理

现实中的相机本质上可以看成一个:

  • 小孔成像模型(Pinhole Camera Model)
  • 即:光线通过一个针孔投影到成像平面上
1.1.2 小孔成像模型

设:空间三维点:P(X,Y,Z) 成像点:p(x,y) 焦距:f

根据相似三角形关系:

可以发现:

  • 距离相机越远(Z越大)
  • 成像越小
1.1.3 相机坐标系

(1)世界坐标系

用于描述真实世界中的物体位置:

(2)相机坐标系

以相机光心为原点:

  • Z轴:朝前
  • X轴:向右
  • Y轴:向下

(3)图像坐标系

单位通常为:

  • mm
  • 或物理长度

成像平面中心一般为原点。

(4)像素坐标系

OpenCV 最终使用的是像素坐标:(u,v)

  • 左上角为原点
  • u向右
  • v向下
1.1.4 相机内参

相机从"物理成像平面"转换到"像素坐标"时,需要内参矩阵。

相机内参矩阵:

:水平方向焦距(单位:像素)

:垂直方向焦距(单位:像素)

:主点坐标(光轴中心),即相机光轴与图像平面的交点,通常接近图像中心

内参矩阵作用:将"归一化成像平面坐标"转换为"像素坐标"。

1.1.5 单目相机完整投影模型

世界坐标 → 相机坐标:

相机坐标 → 成像平面:

成像平面 → 像素坐标:

2、相机畸变

相机畸变是指:真实镜头成像时,图像发生几何变形的现象。

畸变数学表示:

3、模型投影函数

cpp 复制代码
/* 用途:用于将三维空间中的点,根据相机参数投影到二维图像平面中。 */
void cv::projectPoints( InputArray objectPoints,
                                 InputArray rvec, InputArray tvec,
                                 InputArray cameraMatrix, InputArray distCoeffs,
                                 OutputArray imagePoints,
                                 OutputArray jacobian = noArray(),
                                 double aspectRatio = 0 );
/*
objectPoints:世界坐标系中3D点的三位坐标,可以是Point3f或Point3d类型
rvec:旋转向量,表示世界坐标系到相机坐标系的旋转关系
tvec:平移向量,表示世界坐标系到相机坐标系的平移关系
cameraMatrix:相机内参矩阵,即相机标定得到的K矩阵
distCoeffs:相机畸变系数,用于描述镜头畸变,对应畸变参数[k1, k2, p1, p2, k3]
imagePoints:输出投影后的二维图像坐标点
jacobian:输出雅可比矩阵,一般用于优化计算,默认情况下不使用
aspectRatio:固定纵横比参数,默认值为0,表示不固定比例
*/

4、示例代码

cpp 复制代码
    /*输入计算得到的内参矩阵和畸变矩阵*/
    Mat cameraMatrix = (Mat_<float>(3, 3) << 532.016297, 0, 332.172519,
        0, 531.565159, 233.388075,
        0, 0, 1);
    Mat distCoeffs = (Mat_<float>(1, 5) << -0.285188, 0.080097, 0.001274,
        -0.002415, 0.106579);
    /*第一张图像相机坐标系与世界坐标系之间的关系*/
    Mat rvec = (Mat_<float>(1, 3) << -1.977853, -2.002220, 0.130029);
    Mat tvec = (Mat_<float>(1, 3) << -26.88155, -42.79936, 159.19703);
    /*生成第一张图像中内角点的三维世界坐标*/
    Size boardSize = Size(9, 6);
    Size squareSize = Size(10, 10);/*棋盘格每个方格的真实尺寸*/
    vector<Point3f> PointSets;
    for (int j = 0; j < boardSize.height; j++)
    {
        for (int k = 0; k < boardSize.width; k++)
        {
            Point3f realPoint;
            /*假设标定版为世界坐标系的z平面,即z=0*/
            realPoint.x = j * squareSize.width;
            realPoint.y = k * squareSize.height;
            realPoint.z = 0;
            PointSets.push_back(realPoint);
        }
    }
    /*根据三维坐标和相机与世界坐标系时间的关系估计内角点像素坐标*/
    vector<Point2f> imagePoints;
    projectPoints(PointSets, rvec, tvec, cameraMatrix, distCoeffs, imagePoints);
    for (int i = 0; i < imagePoints.size(); i++)
    {
        cout << to_string(i) << ": " << imagePoints[i] << endl;
    }
    waitKey(0);

二、单目相机标定

1、单目标定原理简介

2.1.1 标定的本质问题

单目相机标定的核心目标是:已知三维空间点与其二维图像点的对应关系,求解相机的内参与外参,以及畸变参数。 本质上是一个参数反求问题

2.1.2 标定输入与输出

(1)输入数据(已知量)

单目标定依赖于多组 3D--2D 对应点:

  • 三维点(世界坐标系)
    • 标定板上角点的真实物理坐标
    • 通常设定在 Z = 0 平面(棋盘格平面)
  • 二维点(图像坐标系)
    • 图像中检测到的角点像素坐标

(2)输出参数(未知量)

标定求解以下相机参数:

  • 内参矩阵 K:
  • 畸变参数:通常包括径向畸变:k1,k2,k3,切向畸变:p1,p2
  • 外参(每张图像一组):旋转向量:rvec,平移向量:tvec
2.1.3 标定的数学本质

单目标定可以抽象为一个优化问题:

:第 i 张图像第 j 个角点的实际像素坐标

:通过相机模型投影得到的像素坐标

K:内参矩阵

D:畸变参数

:第 i 张图像外参

2、标定版角点提取

  • 标定版角点提取
cpp 复制代码
/* 返回值:bool类型,true表示成功检测到完整棋盘角点,false表示检测失败
   用途:用于检测棋盘格标定板中的内角点位置,是相机标定流程中的关键步骤 */
bool cv::findChessboardCorners( InputArray image, Size patternSize, 
        OutputArray corners,
        int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
/*
image:含有棋盘标定板的图像,图像必须是CV_8U的灰度图像或者彩色图像
patternSize:棋盘格内角点数量,Size(cols, rows),指"每行/每列内角点数",不是方格数
corners:输出检测到的角点坐标,按棋盘扫描顺序排列的2D点集
flags:检测算法标志位,用于提升检测稳定性,
    常见选项包括:
       CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值处理图像
       CALIB_CB_NORMALIZE_IMAGE:归一化图像增强对比度
       CALIB_CB_FILTER_QUADS:过滤不符合四边形结构的区域
       CALIB_CB_FAST_CHECK:快速初步检测(提高速度,可能降低准确性)
*/
  • 圆形标定板中心提取
cpp 复制代码
/* 返回值:bool类型,true表示成功检测到完整圆点阵列,false表示检测失败
   用途:用于检测圆点标定板中的圆心位置,是相机标定中的重要函数之一。 */
bool cv::findCirclesGrid( InputArray image, Size patternSize,
                            OutputArray centers, int flags,
                            const Ptr<FeatureDetector> &blobDetector,
                            const CirclesGridFinderParameters& parameters);
/*
image:输入含有圆形网格的图像,图像必须是CV_8U的灰度图像或者彩色图像
patternSize:圆点阵列的尺寸,Size(cols, rows),表示每行和每列圆形的数目
centers:输出检测到的圆心坐标,按照圆点阵列顺序排列
flags:圆点阵列检测方式标志,常见取值:
       CALIB_CB_SYMMETRIC_GRID:对称圆点阵列
       CALIB_CB_ASYMMETRIC_GRID:非对称圆点阵列
       CALIB_CB_CLUSTERING:使用聚类提高复杂场景鲁棒性
blobDetector:Blob检测器,用于检测图像中的圆形区域,一般使用SimpleBlobDetector
parameters:圆点阵列搜索参数,用于控制圆点搜索过程中的细节配置
*/
  • 角点位置优化
cpp 复制代码
/* 返回值:bool类型,true表示角点优化成功,false表示优化失败
   用途:用于对棋盘格等四边形结构角点进行亚像素级精确优化。
   该函数会根据角点附近的灰度变化,对原始角点位置进行进一步精细调整,
   从而获得更高精度的角点坐标 */
bool cv::find4QuadCornerSubpix( InputArray img, 
                            InputOutputArray corners, 
                            Size region_size );
/*
img:计算处内角点的图像
corners:输入和输出角点坐标,输入为初始检测到的角点,输出为亚像素级精确角点坐标
region_size:角点搜索区域大小,表示在角点周围进行亚像素优化的邻域窗口尺寸

*/
  • 绘制内角点提取结果
cpp 复制代码
/* 用途:用于在图像中可视化显示棋盘格角点检测结果。
       该函数会在检测到的角点位置绘制彩色圆点和连接关系,方便观察:
       1、棋盘格是否检测成功
       2、角点顺序是否正确
       3、角点定位是否准确 */
void cv::drawChessboardCorners( InputOutputArray image, Size patternSize,
                            InputArray corners, bool patternWasFound );
/*
image:需要绘制角点的目标图像,必须是CV_8U的彩色图像
patternSize:棋盘格内角点数量,Size(cols, rows),表示每行和每列内角点个数
corners:检测得到的棋盘格角点坐标集合,通常来自findChessboardCorners()
patternWasFound:棋盘格是否成功检测到的标志,true表示检测成功,false表示检测失败
*/
  • 相机标定函数
cpp 复制代码
/* 返回值:重投影误差(RMS误差),数值越小,标定结果通常越准确
   用途:用于根据多张标定板图像,计算相机的内参、畸变参数以及外参。 */
double cv::calibrateCamera( InputArrayOfArrays objectPoints,
                InputArrayOfArrays imagePoints, Size imageSize,
                InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
                OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
                int flags = 0, TermCriteria criteria = TermCriteria(
                TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );
/*
objectPoints:世界坐标系中的三维点集合,通常为棋盘格内角点的三维坐标
imagePoints:图像坐标系中的二维点集合,通常为棋盘格内角点在图像中的二维坐标
imageSize:输入图像尺寸,即图像宽度和高度
cameraMatrix:相机的内参矩阵,即相机标定后的K矩阵
distCoeffs:相机的畸变系数矩阵,包括径向畸变和切向畸变参数
rvecs:每张标定图像相机坐标系与世界坐标系之间对应的旋转向量
tvecs:每张标定图像相机坐标系与世界坐标系之间对应的平移向量
flags:相机标定控制标志,用于指定优化方式、固定参数等设置
criteria:迭代终止条件,用于控制优化迭代次数和精度
*/

3、示例代码

cpp 复制代码
    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    /*读取所有图像*/
    vector<Mat> imgs;
    string imageName;
    ifstream fin(s_imgPath + "/calibdata.txt");

    while (getline(fin, imageName))
    {
        string fullPath = s_imgPath + "/" + imageName;

        cv::Mat img = cv::imread(fullPath);/*left01.jpg left02.jpg left03.jpg left04.jpg*/

        if (img.empty())
        {
            qDebug() << "读取失败:" << QString::fromStdString(fullPath);
            continue;
        }
        imgs.push_back(img);
    }

    Size board_size = Size(9, 6);/*方格标定板内角点数目(行,列)*/
    vector<vector<Point2f>> imgPoints;
    for (int i = 0; i < imgs.size(); i++)
    {
        Mat img1 = imgs[i];
        Mat gray1;
        cvtColor(img1, gray1, COLOR_BGR2GRAY);
        vector<Point2f> img1_points;
        findChessboardCorners(gray1, board_size, img1_points);/*计算方格标定板角点*/
        find4QuadCornerSubpix(gray1, img1_points, Size(5, 5));/*细化方格标定板角点坐标*/
        bool pattern = true;
        drawChessboardCorners(img1, board_size, img1_points, pattern);
        imshow("img1", img1);
        waitKey(0);
        imgPoints.push_back(img1_points);
    }

    /*生成棋盘格每个内角点的空间三维坐标*/
    Size squareSize = Size(10, 10);/*棋盘格每个方格的真实尺寸*/
    vector<vector<Point3f>> objectPoints;
    for (int i = 0; i < imgPoints.size(); i++)
    {
        vector<Point3f> tempPointSet;
        for (int j = 0; j < board_size.height; j++)
        {
            for (int k = 0; k < board_size.width; k++)
            {
                Point3f realPoint;
                /*假设标定板为世界坐标系的z平面,即z=0*/
                realPoint.x = j * squareSize.width;
                realPoint.y = k * squareSize.height;
                realPoint.z = 0;
                tempPointSet.push_back(realPoint);
            }
        }
        objectPoints.push_back(tempPointSet);
    }
    /*初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板*/
    //vector<int> point_number;
    //for (int i = 0; i < imgPoints.size(); i++)
    //{
    //    point_number.push_back(board_size.width * board_size.height);
    //}
    /*图像尺寸*/
    Size imageSize;
    imageSize.width = imgs[0].cols;
    imageSize.height = imgs[0].rows;

    Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));/*摄像机内参矩阵*/
    Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));/*摄像机的5个畸变系数,k1,k2,p1,p2,k3*/
    vector<Mat> rvecs;/*每幅图像的旋转向量*/
    vector<Mat> tvecs;/*每幅图像的平移向量*/
    calibrateCamera(objectPoints, imgPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, 0);
    cout << "cameraMatrix: " << endl << cameraMatrix << endl;
    cout << "distCoeffs: " << endl << distCoeffs << endl;
    waitKey(0);
    destroyAllWindows();
相关推荐
咸甜适中2 小时前
rust语言学习笔记Trait(七) IntoIterator(由集合创建迭代器)
笔记·学习·rust
凌峰的博客2 小时前
T2SMark:在扩散模型噪声水印中寻找鲁棒性与多样性的平衡
人工智能·深度学习·计算机视觉
qq_525513753 小时前
第七章 指令微调学习(三)为指令数据集创建数据加载器;加载预训练的大语言模型
人工智能·学习·语言模型
小白|3 小时前
CANN目标检测实战:自定义检测算子开发(插件机制)
人工智能·目标检测·计算机视觉
计算机安禾3 小时前
【c++面向对象编程】第37篇:面向对象设计原则(一):单一职责与开闭原则
开发语言·c++·开闭原则
阿阳微客3 小时前
网易Buff游戏搬砖,长期可做!
笔记·学习·游戏
小明同学013 小时前
C++后端项目:统一大模型接入 SDK(三)
开发语言·c++
Brilliantwxx3 小时前
【C++】 继承与多态(下)
开发语言·c++
C+++Python3 小时前
C++考试语法知识
开发语言·c++