OpenCV获取相机的内参矩阵和畸变矩阵

发布时间 2023-04-07 18:29:29作者: XnobodyT

实验室任务要截止了,赶紧来上传一下学习成果,终极目的是获取视频每帧的旋转矩阵和平移矩阵,但没办法一口吃个胖子,所以先写一下相机内参矩阵和畸变矩阵的求解办法
先上代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
 
int main(){
    // 棋盘格的行数与列数
    int row = 6, col = 9;
 
    // 准备用于标定的点坐标容器,每个元素表示一个标定板在3D空间中的坐标
    vector<Point3f> objp;
    for(int i=0;i<row;++i){
        for(int j=0;j<col;++j){
            objp.push_back(Point3f(j,i,0));
        }
    }
 
    // 存储所有图片的棋盘格角点坐标,以及相应的图像坐标
    vector<vector<Point3f>> objpoints; // 3D points in real world space
    vector<vector<Point2f>> imgpoints; // 2D points in image plane.
 
    // 枚举每张标定图片,获取棋盘格角点坐标
    vector<String> fn;
    glob("left*.jpg", fn, false);
    for(size_t i=0;i<fn.size();++i){
        Mat img = imread(fn[i]);
        Mat gray;
        cvtColor(img,gray,COLOR_BGR2GRAY);
 
        // 查找棋盘格角点
        vector<Point2f> corners;
        bool success = findChessboardCorners(gray, Size(row, col), corners);
 
        // 如果找到棋盘格角点,则存储这张图片的点信息
        if (success) {
            objpoints.push_back(objp);
            cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),
                             TermCriteria(cv::TermCriteria::EPS+cv::TermCriteria::MAX_ITER, 30, 0.1));
            imgpoints.push_back(corners);
 
            // 显示角点信息
            drawChessboardCorners(img, Size(row, col), corners, success);
            imshow("corners",img);
            waitKey(500);
        }
    }
 
    // 获取相机原始尺寸
    Mat img = imread(fn[0]);
    Size img_shape = img.size();
 
    // 标定相机,并计算相机内参矩阵和畸变系数等参数
    Mat camera_matrix, dist_coeffs;
    vector<Mat> rvecs, tvecs;
    calibrateCamera(objpoints, imgpoints, img_shape, camera_matrix, dist_coeffs, rvecs, tvecs);
 
    // 保存相机内参矩阵和畸变系数等参数
    FileStorage fs("cam_params.yml", FileStorage::WRITE);
    fs << "camera_matrix" << camera_matrix;
    fs << "dist_coeffs" << dist_coeffs;
    fs.release();
 
    return 0;
}

担心大家和我一样都是小白,先讲解一些可能没见过的函数

(1)glob:

glob 函数是一个用于匹配文件名的函数,可以根据指定的模式匹配符合条件的文件名,并将其返回到指定的容器中。glob 函数通常在文件批量处理和数据读取等场景中使用,可以帮助我们快速地获取一组符合特定条件的文件名。
在 OpenCV 库中,glob 函数被封装在 cv::glob 函数中,可以方便地使用。

void glob(const String& pattern, std::vector<String>& result, bool recursive = false);
  • pattern:表示要匹配的文件名模式,可以是绝对路径或相对路径(相对于当前目录),支持通配符匹配。
  • result:表示匹配成功后将文件名保存到这个容器中。
  • recursive:是否递归搜索子目录,默认值为 false。
    在本次代码中用于分析多张标定图片,获取畸变矩阵和内参矩阵的准确值

(2)FileStorage:

FileStorage 是 OpenCV 库中用于序列化和反序列化数据的类,提供了将数据保存到 XML、JSON、YAML 等格式的文件中,以及从文件中读取数据到内存中的函数。FileStorage 可以用于从文件中读取或写入任意数据类型,包括图像、矩阵、数组、向量、标量、字符串等。
关于FileStorge函数的具体用法非常多,这里不展开讲,后续我会为其的用法专门写一篇博客,这里这要记住它能将我们的最终结果保存到一个文件中即可

(3)findChessboardCorners

findChessboardCorners 是 OpenCV 库中用于检测棋盘格角点的函数,该函数可以用于多种相机标定方法中。
findChessboardCorners 函数可以从一张包含了棋盘格图案的图像中找到所有的内角点坐标,以便后续进行相机标定。该函数通常与 drawChessboardCorners 函数一起使用,可视化地展示出检测到的内角点,以帮助用户确认是否正确检测到了所有的内角点。

bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners,
                           int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE);

  • image:输入图像,必须是灰度图。
  • patternSize:棋盘格内角点的尺寸,即内角点沿 x 和 y 方向的数量。
  • corners:输出参数,包含了检测到的内角点坐标。
  • flags:用于调整棋盘格检测的方法,包括下面几种:
    • CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值来进行二值化。
    • CALIB_CB_NORMALIZE_IMAGE:将图像归一化到 0~255 的范围内。
    • CALIB_CB_FILTER_QUADS:通过考虑四边形质量来筛选掉不好的角点。

该函数将返回一个布尔类型的值,指示是否成功检测到所有的内角点。如果成功检测到, corners 中将包含所有内角点的坐标。

(4)drawChessboardCorners

这个和上个函数通常结合使用,简单来说结果就是把找到的内角点坐标选出来

void drawChessboardCorners(InputOutputArray image, Size patternSize, InputArray corners,
                            bool patternWasFound);
  • image:输入图像,输出绘制了角点的图像。
  • patternSize:棋盘格内角点的尺寸,即内角点沿 x 和 y 方向的数量。
  • corners:从 findChessboardCorners 函数得到的角点位置信息。
  • patternWasFound:布尔变量,表示棋盘格是否已被完全找到。

(5)consuubPix

cornerSubPix 是 OpenCV 库中用于亚像素级别的角点坐标精确化的函数,可以提高角点坐标的精度,从而更准确地进行相机标定和目标跟踪等应用。
在使用 findChessboardCorners 函数检测到棋盘格内角点的坐标后,这些坐标通常只是整数像素的位置。但是,实际上它们可能不是完全准确的位置,因此需要对其进行亚像素级别的精确化。
cornerSubPix 函数通过对图像局部区域进行拟合来计算出亚像素级别的角点坐标。该函数使用迭代方法,迭代次数由 criteria 参数指定。

cv::cornerSubPix(image, corners, winSize, zeroZone, criteria);
  • image:输入图像,单通道浮点类型。
  • corners:输入和输出变量,包含了待校正的角点坐标和输出精确化后的坐标。
  • winSize:搜索窗口大小。
  • zeroZone:搜索区域零区域大小,在搜索区域中心不计算梯度值,用于避免误差增大的情况。
  • criteria:迭代终止条件。

(6)标定图片

相机的旋转矩阵和平移矩阵是用于描述相机在空间中的姿态和位置的参数。在进行相机标定时,需要利用多张不同的图片来获取相机的内参矩阵和畸变系数等参数,并推导出旋转矩阵和平移矩阵。
为大多时候要使用标定图片对相机进行标定。举个例子
image


上面只是简单介绍用到函数和概念,具体用法并没有说明,想要深入了解可以自行搜索。

分步骤讲解一下代码思路

1. 准备标定的点坐标容器

    int row = 6, col = 9; //我这里的标定图片内部点的分布是6x9格式

    vector<Point3f> objp;
    for(int i=0;i<row;++i){
        for(int j=0;j<col;++j){
            objp.push_back(Point3f(j,i,0));  //记录图片里每个点的坐标,每个元素表示一个标定板在3D空间中的坐标
        }
    }

2.存储格角点坐标以及这些点在图像里的坐标

vector<vector<Point3f>> objpoints; // 3D points in real world space
vector<vector<Point2f>> imgpoints;

3.

vector<String> fn;
    glob("left*.jpg", fn, false); //我准备了14张图片,所以这里用glob把这些图像名称存储在fn变量里
    for(size_t i=0;i<fn.size();++i){
        Mat img = imread(fn[i]);
        Mat gray;
        cvtColor(img,gray,COLOR_BGR2GRAY);

        vector<Point2f> corners;
        bool success = findChessboardCorners(gray, Size(row, col), corners);//存储所有格角点的坐标在concern

        if (success) {
            objpoints.push_back(objp);
            cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),
                     TermCriteria(cv::TermCriteria::EPS+cv::TermCriteria::MAX_ITER, 30, 0.1)); 

            imgpoints.push_back(corners);//存储这些图片的所有点信息

            drawChessboardCorners(img, Size(row, col), corners, success);//画出所以格角点
            imshow("corners",img);
            waitKey(500);
        }
    }

4.获取相机的内参矩阵和畸变矩阵

//通过图片获取相机尺寸
Mat img = imread(fn[0]);
Size img_shape = img.size();
 
// 标定相机,并计算相机内参矩阵和畸变系数等参数
Mat camera_matrix, dist_coeffs;
vector<Mat> rvecs, tvecs;
calibrateCamera(objpoints, imgpoints, img_shape, camera_matrix, dist_coeffs, rvecs, tvecs);

5.保存得到的矩阵

FileStorage fs("cam_params.yml", FileStorage::WRITE);
fs << "camera_matrix" << camera_matrix;
fs << "dist_coeffs" << dist_coeffs;
fs.release();

效果:
image