利用棋盘格进行相机标定

利用棋盘格进行相机标定

前言

一、为什么要进行相机标定?

简单来说,相机标定的目的主要就是通过构建方程来求解相机内参数,相机的内参数包括Fx,Fy,Cx,Cy以及畸变参数。通过求解得到相机的内参数可以实现2D到3D转换的(当然,这只是一个例子)。具体的一些原理或者名词这里就不做太多描述,大家可以自己看。接下来主要是如何利用C++和Opencv实现。

二、实现

代码如下(示例):

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    // Input the calibration picture text
    std::ifstream fin ("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/img.txt");

    // Calibration result out file
    std::ofstream fout("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/out.txt");

    if(!fin)
    {
        std::cerr << "Related file not found !" << std::endl;

        return EOF;
    }

    // Init the number of calibration images
    int ImgNums = 0;

    // Input file path
    std::string FileName;

    // Image size
    cv::Size ImgSize;

    // Calibration board size
    cv::Size BoardSize = cv::Size(11,8);

    // Storage the corner of board
    std::vector<cv::Point2f> img_points_buf;

    // Storage all corner of board
    std::vector<std::vector<cv::Point2f>> img_points_seq;

    while (getline(fin,FileName))
    {
        ImgNums++;

        std::cout << "processing: " << FileName << std::endl;

        // The currently processed image path
        FileName = "/home/chen//QtProject-II/Camera-Calibrations/Checkerboard-Calibration/SrcImage/"+FileName;

        std::cout<< " img_nums = "<< ImgNums << std::endl;

        cv::Mat img = cv::imread(FileName);

        cv::Mat ColorImg = img.clone();

        const int kenerl=3;

        medianBlur(img,img,kenerl);

        // Output image size through the first image
        if(ImgNums == 1)
        {
            ImgSize.height = img.rows;

            ImgSize.width = img.cols;

            std::cout << "img_size.height = " << ImgSize.height << std::endl;

            std::cout << "img_size.width = " << ImgSize.width << std::endl;
        }

        // Determine whether a corner is detected
        if(cv::findChessboardCorners(img,BoardSize,img_points_buf) == 0)
        {
            std::cout << "No." << ImgNums  << "Could not detect Corner !" << " "<< std::endl;

            cv::imshow("detect fail picture", img);

            cv::waitKey(0);
        }
        else
        {
            cv::Mat ImgGray;                                                                // 创建一个存储灰度图像的矩阵

            cv::cvtColor(img,ImgGray,CV_RGB2GRAY);

            cv::cornerSubPix(ImgGray,img_points_buf,cv::Size(5,5),cv::Size(-1,-1),cv::TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));

            img_points_seq.push_back(img_points_buf);

            drawChessboardCorners(ColorImg,BoardSize,img_points_buf,true);

            namedWindow("F",cv::WINDOW_NORMAL);

            imshow("F",ColorImg);

            cv::waitKey(2000);
        }
    }

    // Corner detection and extraction completed
    std::cout << "Corner detection and extraction completed !" << std::endl;

    // Start Camera Calibration
    std::cout << " Start calibrate..."<< std::endl;

    // Lattice size
    cv::Size squre_size = cv::Size(15,15);

    std::vector<std::vector<cv::Point3f>> object_points;

    // Internal parameter matrix 3*3
    cv::Mat cameraMatrix = cv::Mat(3,3,CV_32FC1,cv::Scalar::all(0));

    // Distortion matrix 1*5
    cv::Mat distCoeffs = cv::Mat(1,5,CV_32FC1,cv::Scalar::all(0));

    // rotation matrix
    std::vector<cv::Mat> rotationMat;

    // translation matrix
    std::vector<cv::Mat> translationMat;

    int i,j;

    for (int t=0;t < ImgNums;++t)
    {
        std::vector<cv::Point3f> tempPointSet;

        for (i=0;i < BoardSize.height;++i)
        {
            for (j=0;j < BoardSize.width;j++)
            {
                cv::Point3f realPoint;

                realPoint.x = i*squre_size.width;

                realPoint.y = j*squre_size.height;

                realPoint.z = 0;

                tempPointSet.push_back(realPoint);
            }
        }

        object_points.push_back(tempPointSet);
    }

    std::vector<int> point_counts;

    for (i=0; i < ImgNums; ++i)
    {
        point_counts.push_back(BoardSize.width * BoardSize.height);
    }

    // Calibrate
    calibrateCamera(object_points, img_points_seq, ImgSize, cameraMatrix, distCoeffs,rotationMat, translationMat, 0);     // 拥有八个参数的标定函数

    std::cout << " Calibrate done!" << std::endl << std::endl;

    // Evaluate the calibration results
    double total_err = 0.0;

    double err = 0.0;

    std::vector<cv::Point2f> img_pointsre;

    std::cout<< "\t每幅图像的标定误差: " << std::endl;

    fout << "每幅图像的标定误差: " << std::endl;

    for (int i = 0;i < ImgNums; ++i)
    {
        std::vector<cv::Point3f> tempPointSet = object_points[i];                                                                   // 将角点的三维坐标存放到一个新的容器中

        cv::projectPoints(tempPointSet,rotationMat[i],translationMat[i],cameraMatrix,distCoeffs,img_pointsre);                 // 通过之前标定得到的相机的内参和外参,对三维点进行重投影

        std::vector<cv::Point2f> tempImagePoint = img_points_seq[i];                                                                // 二维图像下亚像素的角点

        cv::Mat tempImagePointMat = cv::Mat(1,tempImagePoint.size(),CV_32FC2);                                                     // 创建亚像素角点矩阵

        cv::Mat img_pointsreMat = cv::Mat(1,img_pointsre.size(),CV_32FC2);                                                         // 创建重投影后的角点矩阵

        for (int j = 0;j < tempImagePoint.size();j++)
        {
            tempImagePointMat.at<cv::Vec2f>(0,j) = cv::Vec2f(tempImagePoint[j].x,tempImagePoint[j].y);

            img_pointsreMat.at<cv::Vec2f>(0,j) = cv::Vec2f(img_pointsre[j].x,tempImagePoint[j].y);
        }

        err = norm(img_pointsreMat,tempImagePointMat,cv::NORM_L2);

        total_err += err/= point_counts[i];

        std::cout << "No." << i+1 << " picture's Error: " << err << " pixel " << std::endl;

        fout << "No." << i+1 << " picture's Error: " << err << " pixel " << std::endl;
    }

    std::cout<<"Overall mean error: "<< total_err/ImgNums << std::endl;

    fout << "Overall mean error: "<< total_err/ImgNums << std::endl;

    std::cout << "Assessment completed !" << std::endl;

    // Write the calibration results to a .txt file
    std::cout << "Start save the result....." << std::endl;

    cv::Mat rotate_Mat = cv::Mat(3,3,CV_32FC1,cv::Scalar::all(0));                                          // 保存旋转矩阵

    std::cout << "Camera matrix: " << std::endl << cameraMatrix << std::endl << std::endl;

    fout << " Camera matrix: " << std::endl;

    fout << cameraMatrix << std::endl << std::endl;

    fout << "Distortion parameter: " << std::endl;

    fout << distCoeffs << std::endl << std::endl << std::endl;

    std::cout << "Distortion parameter: " << std::endl << distCoeffs << std::endl << std::endl << std::endl;

    for (int i = 0;i<ImgNums;i++)
    {
        Rodrigues(rotationMat[i],rotate_Mat);                                                   // 将旋转向量通过罗德里格斯公式转换为旋转矩阵

        fout << "No." << i+1<< "picture's rotation matrix is: " << std::endl;

        fout << rotate_Mat << std::endl;

        fout << "No."<<i+1<<"picture's translation vector " << std::endl;

        fout << translationMat[i] << std::endl;
    }

    std::cout << "Save done!" << std::endl;

    fout << std::endl;

    return 0;
}
















2.说明

如需要直接使用代码,则文中几处需要进行更改:
1、std::ifstream fin ("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/img.txt"); std::ofstream fout("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/out.txt");
这里需要注意修改为自己的路径,这里采用的是绝对路径
2、cv::Size BoardSize = cv::Size(11,8);这里是需要根据自己实际使用的棋盘格来修改,注意自己棋盘格格子的实际大小也就是cv::Size squre_size = cv::Size(15,15);要注意是否修改,我的棋盘格格子大小是15mm*15mm。
3、FileName = "/home/chen//QtProject-II/Camera-Calibrations/Checkerboard-Calibration/SrcImage/"+FileName;此处也需要根据自己图片的实际位置来进行修改。
我的实际项目工程组成大概是这样子的:
利用棋盘格进行相机标定_第1张图片SrcImage文件夹中是标定板图像,img.txt是图片名称,是需要自己创建的
利用棋盘格进行相机标定_第2张图片最终的标定输出结果可通过终端或者输出out.txt中查看。

总结

没啥大问题的话相机标定也就结束了,通过标定结果可以得到相机的内参数、畸变参数、以及标定的误差等。如果,文中有些地方表达不准确或者存在错误,欢迎指正。


你可能感兴趣的:(opencv,计算机视觉,c++)