OpenCV相机标定与3D重建(8)相机标定函数calibrateCamera()的使用

发布于:2024-11-29 ⋅ 阅读:(26) ⋅ 点赞:(0)
  • 操作系统:ubuntu22.04
  • OpenCV版本:OpenCV4.9
  • IDE:Visual Studio Code
  • 编程语言:C++11

算法描述

从校准图案的多个视图中找到相机的内参和外参参数.
cv::calibrateCamera 是 OpenCV 中用于相机标定的一个非常重要的函数。它通过一系列已知的世界坐标点(通常是棋盘格角点)和它们在图像中的对应点来计算相机的内参矩阵 cameraMatrix 和畸变系数 distCoeffs。

函数原型


double cv::calibrateCamera
(
	InputArrayOfArrays 	objectPoints,
	InputArrayOfArrays 	imagePoints,
	Size 	imageSize,
	InputOutputArray 	cameraMatrix,
	InputOutputArray 	distCoeffs,
	OutputArrayOfArrays 	rvecs,
	OutputArrayOfArrays 	tvecs,
	OutputArray 	stdDeviationsIntrinsics,
	OutputArray 	stdDeviationsExtrinsics,
	OutputArray 	perViewErrors,
	int 	flags = 0,
	TermCriteria 	criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) 
)		

参数

  • 参数objectPoints:
    在新接口中,这是一个包含校准图案点在标定图案坐标空间中的向量的向量(例如 std::vector<std::vectorcv::Vec3f>)。外层向量包含的元素数量与图案视图的数量相同。如果每个视图中显示相同的标定图案且完全可见,则所有向量将相同。尽管可以使用部分遮挡的图案,甚至在不同的视图中使用不同的图案,在这种情况下,向量将不同。虽然这些点是3D的,但如果使用的标定图案是平面刚体,它们都位于标定图案的XY坐标平面上(因此Z坐标为0)。在旧接口中,来自不同视图的所有对象点向量被连接在一起。
  • 参数imagePoints:
    在新接口中,这是一个包含标定图案点投影的向量的向量(例如 std::vector<std::vectorcv::Vec2f>)。imagePoints.size() 和 objectPoints.size(),以及对于每个 i 的 imagePoints[i].size() 和 objectPoints[i].size() 必须分别相等。在旧接口中,来自不同视图的所有对象点向量被连接在一起。
  • 参数imageSize:
    图像尺寸,仅用于初始化相机内参矩阵。
  • 参数cameraMatrix:
    输入/输出 3x3 浮点相机内参矩阵 A = [ f x 0 c x 0 f y c y 0 0 1 ] A = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} A= fx000fy0cxcy1 。如果指定了 CALIB_USE_INTRINSIC_GUESS 和/或 CALIB_FIX_ASPECT_RATIO、CALIB_FIX_PRINCIPAL_POINT 或 CALIB_FIX_FOCAL_LENGTH,则必须在调用函数之前初始化 f_x、f_y、c_x 和 c_y 中的一个或全部。
  • 参数distCoeffs:
    输入/输出畸变系数向量 (k1, k2, p1, p2[, k3[, k4, k5, k6[, s1, s2, s3, s4[, τx, τy]]]]),包含 4、5、8、12 或 14 个元素。
  • 参数rvecs:
    输出旋转向量 (Rodrigues) 估计值,每个图案视图一个 (例如 std::vectorcv::Mat )。也就是说,每个第 i 个旋转向量和相应的第 i 个平移向量(见下一个输出参数描述)将标定图案从对象坐标空间(其中指定对象点)带到相机坐标空间。更技术地说,第 i 个旋转和平移向量的组合执行了从对象坐标空间到相机坐标空间的基变换。由于其对偶性,这个组合等同于标定图案相对于相机坐标空间的位置。
  • 参数tvecs:
    输出平移向量估计值,每个图案视图一个,参见上述参数描述。
  • 参数stdDeviationsIntrinsics:
    输出内参标准偏差估计值。偏差值的顺序:(fx, fy, cx, cy, k1, k2, p1, p2, k3, k4, k5, k6, s1, s2, s3, s4, τx, τy)。如果某个参数未估计,则其偏差为零。
  • 参数stdDeviationsExtrinsics:
    输出外参标准偏差估计值。偏差值的顺序:(R0, T0, …, RM−1, TM−1),其中 M 是图案视图的数量。Ri, Ti 是连接的 1x3 向量。
  • 参数perViewErrors:
    输出每个图案视图的 RMS 重投影误差估计值。
  • 参数flags:
    不同的标志,可以为零或以下值的组合:
    • CALIB_USE_INTRINSIC_GUESS: cameraMatrix 包含有效的初始值 fx, fy, cx, cy,这些值将进一步优化。否则,(cx, cy) 初始设置为图像中心(使用 imageSize),焦距通过最小二乘法计算。注意,如果已知内参参数,不需要使用此函数仅估计外参参数。应使用 solvePnP。
    • CALIB_FIX_PRINCIPAL_POINT: 主点在全局优化过程中不改变。它保持在中心或在设置了 CALIB_USE_INTRINSIC_GUESS 时指定的不同位置。
    • CALIB_FIX_ASPECT_RATIO: 函数仅考虑 fy 作为自由参数。fx/fy 的比例保持与输入 cameraMatrix 中的比例相同。当未设置 - CALIB_USE_INTRINSIC_GUESS 时,实际输入的 fx 和 fy 值被忽略,仅计算并使用它们的比例。
    • CALIB_ZERO_TANGENT_DIST: 切向畸变系数 (p1, p2) 设置为零并保持为零。
    • CALIB_FIX_FOCAL_LENGTH: 如果设置了 CALIB_USE_INTRINSIC_GUESS,则焦距在全局优化过程中不改变。
    • CALIB_FIX_K1, …, CALIB_FIX_K6: 相应的径向畸变系数在优化过程中不改变。如果设置了 CALIB_USE_INTRINSIC_GUESS,则使用提供的 distCoeffs 矩阵中的系数。否则,将其设置为 0。
    • CALIB_RATIONAL_MODEL: 启用系数 k4, k5 和 k6。为了提供向后兼容性,需要显式指定此额外标志以使标定函数使用有理模型并返回 8 个或更多系数。
    • CALIB_THIN_PRISM_MODEL: 启用系数 s1, s2, s3 和 s4。为了提供向后兼容性,需要显式指定此额外标志以使标定函数使用薄棱镜模型并返回 12 个或更多系数。
    • CALIB_FIX_S1_S2_S3_S4: 薄棱镜畸变系数在优化过程中不改变。如果设置了 CALIB_USE_INTRINSIC_GUESS,则使用提供的 - distCoeffs 矩阵中的系数。否则,将其设置为 0。
    • CALIB_TILTED_MODEL: 启用系数 τX 和 τY。为了提供向后兼容性,需要显式指定此额外标志以使标定函数使用倾斜传感器模型并返回 14 个系数。
    • CALIB_FIX_TAUX_TAUY: 倾斜传感器模型的系数在优化过程中不改变。如果设置了 CALIB_USE_INTRINSIC_GUESS,则使用提供的 distCoeffs 矩阵中的系数。否则,将其设置为 0。
  • 参数criteria:
    迭代优化算法的终止条件。

返回值

总体 RMS 重投影误差

该函数估计每个视图的相机内参和外参。算法基于 [314] 和 [37]。必须指定3D对象点及其在每个视图中的对应2D投影坐标。这可以通过使用具有已知几何形状且易于检测特征点的对象来实现。这种对象被称为校准装置或校准图案,OpenCV 内置支持棋盘格作为校准装置(见 findChessboardCorners)。目前,内参初始化(当未设置 CALIB_USE_INTRINSIC_GUESS 时)仅适用于平面校准图案(其中对象点的 Z 坐标必须全为零)。只要提供了初始的 cameraMatrix,也可以使用3D校准装置。

算法执行以下步骤:

  • 计算初始内参(仅适用于平面校准图案)或从输入参数中读取。畸变系数最初全部设为零,除非指定了某些 CALIB_FIX_K?。
  • 估计初始相机姿态,假设内参已经已知。这是通过 solvePnP 完成的。
  • 运行全局 Levenberg-Marquardt 优化算法 以最小化重投影误差,即观测特征点 imagePoints 和投影(使用当前估计的相机参数和姿态)对象点 objectPoints 之间的总平方距离之和。有关详细信息,请参见 projectPoints。
  • 注意事项
    如果你使用非正方形(即非 N x N)网格并使用 findChessboardCorners 进行校准,并且 calibrateCamera 返回了不合理的值(例如,畸变系数为零,cx 和 cy 远离图像中心,或者 fx 和 fy 之间有较大的差异(比率超过 10:1 或更多)),那么你可能在 findChessboardCorners 中使用了 patternSize=cvSize(rows, cols) 而不是 patternSize=cvSize(cols, rows)。
    如果提供了不支持的参数组合或系统欠定,该函数可能会抛出异常。

代码示例

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

int main() {
    // 棋盘格参数
    int boardSize = 10; // 棋盘格的行数和列数
    double squareSize_mm = 10.0; // 每个方块的大小,以毫米为单位

    // 图像尺寸
    cv::Size imageSize;

    // 存储所有图像中的棋盘格角点
    std::vector<std::vector<cv::Point2f>> imagePoints;
    std::vector<std::vector<cv::Point3f>> objectPoints;

    // 生成对象点
    std::vector<cv::Point3f> obj;
    for (int y = 0; y < boardSize; ++y) {
        for (int x = 0; x < boardSize; ++x) {
            obj.push_back(cv::Point3f(x * squareSize_mm, y * squareSize_mm, 0));
        }
    }

    // 读取图像文件列表
    std::vector<std::string> imageFilenames = {"image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg"}; // 添加更多图像

    for (const auto &filename : imageFilenames) {
        cv::Mat image = cv::imread(filename);
        if (image.empty()) {
            std::cerr << "无法读取图像: " << filename << std::endl;
            continue;
        }

        if (imageSize.width == 0 || imageSize.height == 0) {
            imageSize = image.size();
        }

        std::vector<cv::Point2f> corners;
        bool found = cv::findChessboardCorners(image, cv::Size(boardSize, boardSize), corners);

        if (found) {
            // 亚像素精度调整
            cv::cornerSubPix(image, corners, cv::Size(11, 11), cv::Size(-1, -1),
                             cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.01));

            // 保存角点
            imagePoints.push_back(corners);
            objectPoints.push_back(obj);

            // 可视化角点
            cv::drawChessboardCorners(image, cv::Size(boardSize, boardSize), corners, found);
            cv::imshow("Chessboard Corners", image);
            cv::waitKey(500); // 显示图像,等待半秒
        } else {
            std::cerr << "无法找到棋盘格角点: " << filename << std::endl;
        }
    }

    // 标定相机
    cv::Mat cameraMatrix, distCoeffs;
    std::vector<cv::Mat> rvecs, tvecs;
    std::vector<float> perViewErrors;

    double rms = cv::calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,
                                     cv::noArray(), cv::noArray(), perViewErrors,
                                     cv::CALIB_FIX_ASPECT_RATIO | cv::CALIB_ZERO_TANGENT_DIST);

    std::cout << "Reprojection error: " << rms << std::endl;
    std::cout << "Camera matrix: " << cameraMatrix << std::endl;
    std::cout << "Distortion coefficients: " << distCoeffs << std::endl;

    // 使用标定结果进行图像校正
    for (const auto &filename : imageFilenames) {
        cv::Mat image = cv::imread(filename);
        if (image.empty()) {
            std::cerr << "无法读取图像: " << filename << std::endl;
            continue;
        }

        cv::Mat undistortedImage;
        cv::undistort(image, undistortedImage, cameraMatrix, distCoeffs);

        cv::imshow("Undistorted Image", undistortedImage);
        cv::waitKey(0);
    }

    return 0;
}