欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

详细介绍Opencv实现张正友法相机标定

程序员文章站 2023-12-27 09:02:51
...

本文基于:张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)
这篇博客写得虽然很详细,但是其代码和结果有些明显的不当之处,所以本文进行了更为详细准确的介绍,若有问题欢迎指正。


一.本程序基于以下配置:
- Visual Studio 2015
- OpenCV 3.1.0
开发环境配置参考:OpenCV3.1.0+VS2013开发环境配置


二.本程序准备工作:

本程序使用的标定板是10行14列棋盘格图案,示意图如下:
详细介绍Opencv实现张正友法相机标定

在不同视角拍摄这样的照片14张,依次命名为chess1、chess2、…、chess14,并将其放在当前.cpp文件目录下面,如下图所示:
详细介绍Opencv实现张正友法相机标定

其中calibdata文本文档的内容如下:
详细介绍Opencv实现张正友法相机标定


三.完整源程序如下:

#include"opencv2/core/core.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include"opencv2/calib3d/calib3d.hpp"
#include"opencv2/highgui/highgui.hpp"
#include<iostream>
#include<fstream>

using namespace std;
using namespace cv;


int main()
{
    ifstream fin("calibdata.txt");//标定所用图像文件的路径
    ofstream fout("calibration_result.txt");//保存标定结果

    //读取每一幅图像,从中提取出内角点,然后对角点进行亚像素精确化
    cout<<"开始提取特征点..........";
    int image_count = 0;//图像的数量
    Size image_size;//图像的尺寸
    Size board_size = Size(13,9);//标定板上每行、列的内角点数,一般地,行列数不要相同
    vector<Point2f> image_points_buf;//用于存储检测到的内角点图像坐标位置
    vector<vector<Point2f>> image_points_seq;//保存检测到的所有角点
    string filename;//图像名
    int count = 0;//保存角点数
    while(getline(fin,filename))//从文本文档中依次读取待标定图片名
    {
        image_count++;
        cout<<"image_count = "<<image_count<<endl;

        Mat imageInput = imread(filename);//依次读取当前目录下图片

        if(image_count == 1)//读入第一张图片时获取图像宽高信息
        {
            image_size.width = imageInput.cols;
            image_size.height = imageInput.rows;

            cout<<"image_size.width = "<<image_size.width<<endl;
            cout<<"image_size.height = "<<image_size.height<<endl;
        }

        //提取标定板的内角点,输入必须是8位的灰度或者彩色图像
        if(0 == findChessboardCorners(imageInput,board_size,image_points_buf))

        {
            cout<<"Cannot find chessboard corners!\n";
            exit(1);
        }
        else
        {
            Mat view_gray;
            cvtColor(imageInput,view_gray,CV_RGB2GRAY);//转灰度图
            //find4QuadCornerSubpix(view_gray,image_points_buf,Size(5,5));
            //亚像素精确化方法一
            //image_points_buf初始化的角点坐标向量,同时作为亚像素坐标位置的输出,浮点型数

            cornerSubPix(view_gray,image_points_buf,Size(5,5),Size(-1,-1),
           //亚像素精确化方法二
             //Size(5,5)是搜索窗口的大小,Size(-1,-1)表示没有死区
            //第四个参数定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合
            count += image_points_buf.size();
            image_points_seq.push_back(image_points_buf);//保存亚像素角点
            drawChessboardCorners(view_gray,board_size,image_points_buf,false);
            //用于绘制被成功标定的角点,输入8位灰度或者彩色图像
            //第四个参数是标志位,用来指示定义的棋盘内角点是否被完整的探测到
            //false表示有未被探测到的内角点,这时候函数会以圆圈标记出检测到的内角点
            imshow("Camera Calibration",view_gray);//显示图片
            waitKey(500);//暂停0.5S
        }
        cout << "count = " << count<< endl;//显示角点累加后的值
    }

    int total = image_points_seq.size();//图片总数
    cout << "total = " << total << endl;
    int CornerNum = board_size.width*board_size.height;//每张图片上总的内角点数

    for(int ii = 0;ii<total;ii++)
    {
        cout << "第" << ii + 1 << "张图片的数据:" << endl;
        for(int jj = 0;jj<CornerNum;jj++)
        {
            if (0 == jj % 3)
                cout << endl;//每三个角点坐标之后换行
            else
                cout.width(10);//输出格式控制

            cout << "(" << image_points_seq[ii][jj].x << "," << image_points_seq[ii][jj].y << ")";
        }
        cout << endl;
    }

    cout<<"角点提取完成!\n";


    cout<<"开始标定............";

    Size square_size = Size(10,10);//设置棋盘格子的实际边长,单位为mm
    vector<vector<Point3f>> object_points;//保存标定板上角点的三维坐标
    Mat cameraMatrix = Mat(3,3,CV_32FC1,Scalar::all(0));//相机内参数矩阵
    vector<int> point_counts;//每幅图像中角点的数量
    Mat distCoeffs = Mat(1,5,CV_32FC1,Scalar::all(0));//摄像机的5个畸变系数:k1,k2,k3,p1,p2
    vector<Mat> tvecsMat;//每幅图像的平移向量
    vector<Mat> rvecsMat;//每幅图像的旋转向量

    //初始化标定板上角点的三维坐标
    int i,j,t;
    for(t = 0;t<image_count;t++)
    {
        vector<Point3f> temPointSet;
        for(i = 0;i<board_size.height;i++)
        {
            for(j = 0;j<board_size.width;j++)
            {
                Point3f realPoint;

                //假设标定板放在世界坐标系中的z = 0平面上
                //需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标
                realPoint.x = i*square_size.width;
                realPoint.y = j*square_size.height;
                realPoint.z = 0;
                temPointSet.push_back(realPoint);

            }
        }
        object_points.push_back(temPointSet);
    }

    //初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板
    for(i = 0;i<image_count;i++)
    {
        point_counts.push_back(board_size.width*board_size.height);
    }

    calibrateCamera(object_points,image_points_seq,image_size,
        cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
/*  object_points 世界坐标系中角点的三维坐标,image_points_seq 每个内角点对应的图像坐标点
    image_size 图像的像素尺寸大小,cameraMatrix 输出,内参数矩阵,distCoeffs 输出,畸变系数
    rvecsMat 输出,旋转向量,tvecsMat 输出,位移向量,0标定时采用的算法
    在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,
    标定的结果是生成相机的内参数矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像
    都会生成属于自己的平移向量和旋转向量
*/
    cout<<"标定完成!"<<endl;

    cout<<"开始输出标定结果....."<<endl;
    double total_err = 0.0; //所有图像的平均误差的总和,初始化为0.0
    double err = 0.0; //每幅图像的平均误差
    vector<Point2f> image_points2; //保存重新计算得到的投影点

    cout<<"每幅图像的标定误差:\n";
    fout<<"每幅图像的标定误差:\n";

    for(i = 0;i<image_count;i++)
    {
        vector<Point3f> tempPointSet = object_points[i];//取出每幅图像角点的三维空间坐标
        projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],
            cameraMatrix,distCoeffs,image_points2);
        //通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点

        vector<Point2f> tempImagePoint = image_points_seq[i];//原来每幅图像中角点图像坐标
        Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
        //用于将原图像坐标点存储成一行多列的Mat,由两个通道浮点型数据构成
        Mat image_points2Mat = Mat(1,image_points2.size(),CV_32FC2);
        //用于将重投影坐标点存储成一行多列的Mat,以便于计算重投影误差

        for(int j = 0;j <tempImagePoint.size(); j++)
        {
            image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x,image_points2[j].y);
            tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x,tempImagePoint[j].y);
        }//赋值
        //Vec2f表示的是2通道float类型的Vector,mat.at<Vec2f>(y, x)是访问图像的一种方式

        err = norm(image_points2Mat,tempImagePointMat,NORM_L2);
        //计算每张图片重投影坐标和亚像素角点坐标之间的偏差
        total_err += err /= point_counts[i];//累加误差
        cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;
        fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;

    }

    cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;
    fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;
    cout<<"评价完成!"<<endl;

    cout<<"开始保存标定结果............"<<endl;
    Mat rotation_matrix = Mat(3,3,CV_32FC1,Scalar::all(0));//保存每幅图像的旋转矩阵
    fout<<"相机内参数矩阵:"<<endl;
    fout<<cameraMatrix<<endl<<endl;
    fout<<"畸变系数:\n";
    fout<<distCoeffs<<endl<<endl<<endl;

    for(i = 0; i<image_count;i++)
    {
        fout<<"第"<<i+1<<"幅图像的旋转向量:"<< endl;
        fout<<rvecsMat[i]<<endl;
        Rodrigues(rvecsMat[i],rotation_matrix);//将旋转向量转换为相应的旋转矩阵
        fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<< endl;
        fout<<rotation_matrix<< endl << endl;
        fout<<"第"<<i+1<<"幅图像的平移向量:"<<  endl;
        fout<<tvecsMat[i]<< endl <<endl;

    }
    cout<<"完成保存"<< endl;
    fout<< endl;


    //显示标定结果
    Mat mapx = Mat(image_size,CV_32FC1);//输出的X坐标重映射参数
    Mat mapy = Mat(image_size,CV_32FC1);//输出的Y坐标重映射参数
    Mat R = Mat::eye(3,3,CV_32F);
    cout<<"保存矫正图像"<<endl;

    string imageFileName;
    std::stringstream StrStm;
    for(int i = 0;i < image_count;i++)
    {
        cout<<"Frame # "<<i+1<<"....."<<endl;
        initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix,
            image_size, CV_32FC1, mapx, mapy);//用来计算畸变映射

        StrStm.clear();//清除缓存
        imageFileName.clear();
        string filePath = "chess";
        StrStm<<i+1;
        StrStm>>imageFileName;
        filePath += imageFileName;
        filePath += ".bmp";
        //获取图片路径
        Mat imageSource = imread(filePath);//读取图像
        Mat newimage = imageSource.clone();//拷贝图像

       remap(imageSource,newimage,mapx,mapy,INTER_LINEAR);//把求得的映射应用到图像上
       //与initUndistortRectifyMap结合使用,为矫正方法之一

       //undistort(imageSource,newimage,cameraMatrix,distCoeffs);//矫正方法二
       //第五个参数newCameraMatrix=noArray(),默认跟cameraMatrix保持一致,故可省

        imageFileName += "_d.jpg";//矫正后图片命名
        imwrite(imageFileName,newimage);//保存矫正后的图片

    }
    cout<<"保存结束"<<endl;

    fin.close();
    fout.close();
    getchar();//等待输入以退出

    return 0;
}

四.运行结果:

详细介绍Opencv实现张正友法相机标定
详细介绍Opencv实现张正友法相机标定
详细介绍Opencv实现张正友法相机标定

图片太多,中间省略几张。。。

详细介绍Opencv实现张正友法相机标定
详细介绍Opencv实现张正友法相机标定


calibration_result.txt的结果如下:


每幅图像的标定误差:
第1幅图像的平均误差:0.0316613像素
第2幅图像的平均误差:0.0389023像素
第3幅图像的平均误差:0.0382147像素
第4幅图像的平均误差:0.0370629像素
第5幅图像的平均误差:0.0385986像素
第6幅图像的平均误差:0.0411222像素
第7幅图像的平均误差:0.0301272像素
第8幅图像的平均误差:0.0356303像素
第9幅图像的平均误差:0.0376777像素
第10幅图像的平均误差:0.0327415像素
第11幅图像的平均误差:0.0257617像素
第12幅图像的平均误差:0.0254189像素
第13幅图像的平均误差:0.0288391像素
第14幅图像的平均误差:0.0281152像素
总体平均误差:0.0335624像素

相机内参数矩阵:
[2468.47496640925, 0, 1048.733444731282;
0, 2466.303729383499, 781.2776671184165;
0, 0, 1]

畸变系数:
[-0.337153976094791, 0.5625790549241111, -0.0004451387687732284, 5.767871907399249e-05, -1.615195594149361]

第1幅图像的旋转向量:
[1.832204013190426;
-1.97952864389898;
-0.4381686980068267]
第1幅图像的旋转矩阵:
[-0.05552643342962338, -0.8675821507778331, -0.4941842033516397;
-0.9950941432290062, 0.088674995984375, -0.04386788345134665;
0.08188077492253884, 0.4893239793213493, -0.868249723270445]

第1幅图像的平移向量:
[42.96524585005136;
38.32952150925843;
222.9912740841345]

第2幅图像的旋转向量:
[1.584731236327221;
-2.207688033499483;
-0.6043175791111356]
第2幅图像的旋转矩阵:
[-0.3091732926321815, -0.798262176337548, -0.5169036399096227;
-0.9502349038328671, 0.2811852708390378, 0.1341211057250835;
0.03828188423676798, 0.5326465444288723, -0.8454715583905021]

第2幅图像的平移向量:
[69.74394948874316;
5.086864923950324;
294.4591241780354]

第3幅图像的旋转向量:
[-1.473949748410117;
2.31855971511969;
1.035999692169115]
第3幅图像的旋转矩阵:
[-0.4803079766576785, -0.8563844330649897, -0.1894992093993095;
-0.7124954115441877, 0.2549531410021226, 0.6537194998021941;
-0.5115217845678576, 0.4490040074399208, -0.7326260063739247]

第3幅图像的平移向量:
[61.33952853487049;
-9.017935885808466;
338.6033873028243]

第4幅图像的旋转向量:
[-0.7465736599001761;
2.506508904972995;
0.600457192725938]
第4幅图像的旋转矩阵:
[-0.7500133063059861, -0.5919424240016485, 0.2951003338375199;
-0.3939740782147958, 0.7581903860462892, 0.5195495781942097;
-0.5312856727399724, 0.2734072149268043, -0.8018628491002705]

第4幅图像的平移向量:
[72.59290680319951;
-26.81723942274018;
284.8320214867886]

第5幅图像的旋转向量:
[-0.7283682830866786;
2.544530908621899;
0.6357133676594682]
第5幅图像的旋转矩阵:
[-0.7762623488644779, -0.5737245311930547, 0.2612602687794558;
-0.3834367786036743, 0.7586505858595253, 0.5267119947267545;
-0.500392848219717, 0.3086898943637603, -0.8088989717932951]

第5幅图像的平移向量:
[100.1209866961551;
-32.29634238993099;
306.584645572055]

第6幅图像的旋转向量:
[-0.5237302004887391;
2.886335271763946;
-0.3575089401502572]
第6幅图像的旋转矩阵:
[-0.9204005991990246, -0.3207721518269676, 0.2235351507176743;
-0.365616894748324, 0.9087093140932435, -0.2014241017226591;
-0.1385172309449962, -0.2671190915911554, -0.9536563152618697]

第6幅图像的平移向量:
[73.10631401051033;
-61.20027337080823;
322.5830605558965]

第7幅图像的旋转向量:
[1.713568861903209;
2.054266570651059;
0.1275839829491686]
第7幅图像的旋转矩阵:
[-0.1189475689363267, 0.9084901241809632, 0.4006209806160879;
0.9510798800820409, 0.2201192678099205, -0.2167823093380896;
-0.2851289840533027, 0.35523682551853, -0.8902293301437832]

第7幅图像的平移向量:
[-68.77620078523513;
-50.33338630124742;
279.3099139868805]

第8幅图像的旋转向量:
[1.818225297581961;
2.416158251961891;
0.6448996550355059]
第8幅图像的旋转矩阵:
[-0.3075469299716437, 0.9081606873844775, 0.284005372756168;
0.9288944014598498, 0.2218260808714645, 0.296560922546706;
0.2063251724864512, 0.35501740201652, -0.9118073082970308]

第8幅图像的平移向量:
[-61.68795969126989;
-48.39125620237197;
259.9247597410303]

第9幅图像的旋转向量:
[-1.847647793051378;
-2.434372024849804;
-0.5641186320132542]
第9幅图像的旋转矩阵:
[-0.2927069486806816, 0.9372808759138351, 0.1892807487311871;
0.9250022564756624, 0.2273995446169124, 0.3044014990484665;
0.242267347591411, 0.2641855536440529, -0.9335483520079759]

第9幅图像的平移向量:
[-34.9737442953684;
-41.07623681991357;
262.4502152811082]

第10幅图像的旋转向量:
[1.69102969291048;
2.015536806640728;
-0.08921956607969511]
第10幅图像的旋转矩阵:
[-0.1002242520458908, 0.9377989307560487, 0.3323977508567916;
0.9047607130849443, 0.2248971882948798, -0.361703340812717;
-0.4139603258282911, 0.264488979297568, -0.871023781805219]

第10幅图像的平移向量:
[-42.13169665197236;
-41.61090361839386;
288.3911563121005]

第11幅图像的旋转向量:
[1.724430894665768;
2.33520070621025;
-0.7246472524282326]
第11幅图像的旋转矩阵:
[-0.3281780053536973, 0.9307450708250076, -0.1612848720029889;
0.8585432544884051, 0.222686147487633, -0.461859675539632;
-0.3939577096269682, -0.2900422259810942, -0.8721655979075336]

第11幅图像的平移向量:
[-50.94693146467624;
-44.76151448070497;
303.7724473124102]

第12幅图像的旋转向量:
[1.696368490203205;
2.153193672447314;
-0.4174155271926968]
第12幅图像的旋转矩阵:
[-0.2093204256487086, 0.9725161681105834, 0.1019669660711291;
0.8639645737210686, 0.2327734149230861, -0.446521838950814;
-0.4579849066958599, -0.005370295015354715, -0.8889437469099114]

第12幅图像的平移向量:
[-58.7176317481046;
-45.7774147598785;
250.7908690340958]

第13幅图像的旋转向量:
[1.746182820818435;
2.38376805814103;
0.8197327892115064]
第13幅图像的旋转矩阵:
[-0.349578928430417, 0.8640106590061456, 0.3623260326297403;
0.9041163433351445, 0.2096799568728731, 0.3723008909471983;
0.2456994312401408, 0.4577334342273422, -0.8544658522601795]

第13幅图像的平移向量:
[-24.35541784964123;
-38.59140556730291;
171.2705769754649]

第14幅图像的旋转向量:
[1.644152775376657;
2.007535689228716;
0.5936125402120078]
第14幅图像的旋转矩阵:
[-0.167199482507626, 0.7761523420058212, 0.6079735808800089;
0.9819782500137466, 0.1862065131653021, 0.03233961896435755;
-0.08810816959352724, 0.6024240005624765, -0.7932983511877354]

第14幅图像的平移向量:
[-41.58825298021998;
-44.96941216039689;
169.3603333851799]


第一次写博客~

上一篇:

下一篇: