C++/opencv实现DLT(直接线性变换法)标定相机

背景

老师布置的作业是利用已知点的3维坐标信息和成像信息,标定相机的内外参数,并计算误差。已知点的信息如下:
已知点信息
其中,前三列是XYZ世界坐标,最后两列是成像平面的u v坐标。
建议先吃透理论知识,理论知识可以参考:

C++实现

头文件和全局变量

#include <stdio.h>
#include <iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<fstream>
#include <string>
#include <sstream>

using namespace std;
using namespace cv;

float **Points=nullptr;//全局指针,存放坐标数组
int Points_count=0;

读取txt文件里的坐标信息

//读取txt文件
void readfile(string filename)
{
    ifstream file("/Users/yunyi/Desktop/Calibration_Points.txt");
    string Line;
    string buf;
    int i=0,j=0;
    
    //动态申请二维数组存放坐标
    Points=(float**)malloc(sizeof(float*)*30);
    for(int k=0;k<30;k++){
        *(Points+k)=(float*)malloc(sizeof(float)*5);
    }
    
    //打开文件,把坐标存入数组
    if(file)
    {
        while (getline(file, Line)) {//按行读取
            stringstream split(Line);//按空格分割
            while(split>>buf){
                Points[i][j]=stof(buf);
                j++;
            }
            i++;
            j=0;//不能忘了清零列标!
            Points_count++;
        }
    }
    file.close();
}

main函数

选取标定点

由理论知识可知,6个不共面的点即可完成对相机的标定。剩余的点可以用来测试标定结果。

//从文件中读取坐标点
    string filename="/Users/yunyi/Desktop/Calibration_Points.txt";
    readfile(filename);
    //显示28个坐标点
    cout<<"全部坐标点:"<<endl;
    for(int l=0;l<Points_count;l++)
    {
        for (int m=0; m<5; m++) {
            cout<<Points[l][m]<<" ";
        }
        cout<<endl;
    }
    
    //选取6个标定点
    float Points_cali[6][5];
    //int index[6]={0,3,5,8,15,27};
    int index[6]={0,8,14,19,23,27};
    int ii=0;
    for (int i=0; i<6; i++) {
        for (int j=0; j<5; j++) {
            Points_cali[i][j]=Points[index[ii]][j];
        }
        ii++;
    }
    
    //其余的点
    float Points_remain[22][5];
    int index_remain[22]={1,2,3,4,5,6,7,9,10,11,12,13,15,16,17,18,20,21,22,24,25,26};
    ii=0;
    for(int i=0;i<22;i++){
        for (int j=0; j<5; j++) {
            Points_remain[i][j]=Points[index_remain[ii]][j];
        }
        ii++;
    }

构造A矩阵

公式
A和U矩阵是求解L矩阵的关键。A矩阵即是:
A矩阵

//构造A矩阵
    ii=0;
    float a[12][11]={0};
    for (int i=0; i<12; i++) {
        if(i%2==0)
        {
            a[i][0]=Points_cali[ii][0];
            a[i][1]=Points_cali[ii][1];
            a[i][2]=Points_cali[ii][2];
            a[i][3]=1;
            a[i][8]=-Points_cali[ii][0]*Points_cali[ii][3];
            a[i][9]=-Points_cali[ii][1]*Points_cali[ii][3];
            a[i][10]=-Points_cali[ii][2]*Points_cali[ii][3];
        }
        else{
            a[i][4]=Points_cali[ii][0];
            a[i][5]=Points_cali[ii][1];
            a[i][6]=Points_cali[ii][2];
            a[i][7]=1;
            a[i][8]=-Points_cali[ii][0]*Points_cali[ii][4];
            a[i][9]=-Points_cali[ii][1]*Points_cali[ii][4];
            a[i][10]=-Points_cali[ii][2]*Points_cali[ii][4];
            ii++;
        }
    }
    cout<<endl<<"A矩阵:"<<endl;
    Mat A(12, 11, CV_32F,a);//存放A矩阵
    cout<<A<<endl;

构造U矩阵

U矩阵:
U矩阵
其中,P34=1,因此U矩阵其实就是标定点的u和v坐标堆在一起。

	//构造U矩阵
    float u[12]={0};
    ii=0;
    for(int i=0;i<12;i=i+2){
        u[i]=Points_cali[ii][3];
        u[i+1]=Points_cali[ii][4];
        ii++;
    }
    Mat U(12,1,CV_32F,u);
    cout<<endl<<"U矩阵:"<<endl;
    cout<<U<<endl;

计算L矩阵

根据上面L和U、A矩阵的关系,计算L矩阵。
在这里插入图片描述
在这里插入图片描述
L矩阵把内外参数综合在了一起。

	//计算得到L矩阵
    Mat L=Mat::zeros(11, 1, CV_32F);
    L=(((A.t())*A).inv())*(A.t())*U;
    cout<<endl<<"L矩阵:"<<endl<<L<<endl;

求解内参数

公式我就不贴了,可以参考文章开头的参考链接。

	//计算u0 v0
	float u0=((L.at<float>(0,0))*(L.at<float>(8,0))+(L.at<float>(1,0))*(L.at<float>(9,0))+(L.at<float>(2,0))*(L.at<float>(10,0)))/(pow(L.at<float>(8,0), 2)+pow(L.at<float>(9,0), 2)+pow(L.at<float>(10,0), 2));
    float v0=((L.at<float>(4,0))*(L.at<float>(8,0))+(L.at<float>(5,0))*(L.at<float>(9,0))+(L.at<float>(6,0))*(L.at<float>(10,0)))/(pow(L.at<float>(8,0), 2)+pow(L.at<float>(9,0), 2)+pow(L.at<float>(10,0), 2));
    
    //计算fu fv
    //一种计算方式
    //float fu=sqrt(((pow(L.at<float>(0,0),2))+(pow(L.at<float>(0,1),2))+(pow(L.at<float>(0,2),2)))/(pow(L.at<float>(0,8), 2)+pow(L.at<float>(0,9), 2)+pow(L.at<float>(0,10), 2))-pow(u0, 2));
    //float fv=sqrt(((pow(L.at<float>(0,4),2))+(pow(L.at<float>(0,5),2))+(pow(L.at<float>(0,6),2)))/(pow(L.at<float>(0,8), 2)+pow(L.at<float>(0,9), 2)+pow(L.at<float>(0,10), 2))-pow(v0, 2));
    //另一种计算方式
    float fu=sqrt((pow(u0*L.at<float>(8,0)-L.at<float>(0,0), 2)+pow(u0*L.at<float>(9,0)-L.at<float>(1,0), 2)+pow(u0*L.at<float>(10,0)-L.at<float>(2,0), 2))/(pow(L.at<float>(0,8), 2)+pow(L.at<float>(0,9), 2)+pow(L.at<float>(0,10), 2)));
    float fv=sqrt((pow(v0*L.at<float>(8,0)-L.at<float>(4,0), 2)+pow(v0*L.at<float>(9,0)-L.at<float>(5,0), 2)+pow(v0*L.at<float>(10,0)-L.at<float>(6,0), 2))/(pow(L.at<float>(0,8), 2)+pow(L.at<float>(0,9), 2)+pow(L.at<float>(0,10), 2)));
    
    Mat K=(Mat_<float>(3, 3)<<fu,0,u0,0,fv,v0,0,0,1);//内参数矩阵
    cout<<endl<<"内参数矩阵:"<<endl<<K<<endl;
    

计算得到的数值应该都是非负数。

求解外参数

	//把L矩阵转化成3*4形式,最后一个l12取1
	Mat L_34=(Mat_<float>(3, 4)<<L.at<float>(0,0),L.at<float>(1,0),L.at<float>(2,0),L.at<float>(3,0),L.at<float>(4,0),L.at<float>(5,0),L.at<float>(6,0),L.at<float>(7,0),L.at<float>(8,0),L.at<float>(9,0),L.at<float>(10,0),1);//L矩阵的3*4形式
    cout<<endl<<"外参数矩阵:"<<endl<<(K.inv())*L_34/sqrt(pow(L.at<float>(0,8), 2)+pow(L.at<float>(0,9), 2)+pow(L.at<float>(0,10), 2))<<endl;

计算重投影误差

	//计算重投影误差
    //计算用于标定的坐标点重投影误差
    cout<<endl<<"标定点的误差计算:";
    float err_caliPoints[6]={0};
    float err_caliPoints_mean=0;
    float err_relative=0;
    float u_restruct,v_restruct;
    for (int i=0; i<6; i++) {
        u_restruct=(Points_cali[i][0]*L.at<float>(0,0)+Points_cali[i][1]*L.at<float>(1,0)+Points_cali[i][2]*L.at<float>(2,0)+L.at<float>(3,0))/(Points_cali[i][0]*L.at<float>(8,0)+Points_cali[i][1]*L.at<float>(9,0)+Points_cali[i][2]*L.at<float>(10,0)+1);
        v_restruct=(Points_cali[i][0]*L.at<float>(4,0)+Points_cali[i][1]*L.at<float>(5,0)+Points_cali[i][2]*L.at<float>(6,0)+L.at<float>(7,0))/(Points_cali[i][0]*L.at<float>(8,0)+Points_cali[i][1]*L.at<float>(9,0)+Points_cali[i][2]*L.at<float>(10,0)+1);
        err_caliPoints[i]=sqrt(pow(u_restruct-Points_cali[i][3], 2)+pow(v_restruct-Points_cali[i][4], 2));
        cout<<endl<<"标定点"<<i+1<<"的重投影误差:"<<err_caliPoints[i]<<"   相对误差:"<<abs(err_caliPoints[i])/sqrt(pow(Points_cali[i][3],2)+pow(Points_cali[i][4], 2))<<endl;
        err_caliPoints_mean=err_caliPoints_mean+err_caliPoints[i];
        err_relative=err_relative+abs(err_caliPoints[i])/sqrt(pow(Points_cali[i][3],2)+pow(Points_cali[i][4], 2));
    }
    cout<<"均值:"<<err_caliPoints_mean/6<<"   "<<err_relative/6<<endl;

其余点的重投影误差计算步骤和上述代码大同小异。

收尾

由于用了一个动态二维数组存储坐标点,因此程序结尾要释放空间。

	//释放空间
    for(int i = 0; i < 30;i++){
        free(*(Points+i));
    }

运行结果

在这里插入图片描述
其中,外参数矩阵是下图的M矩阵(即L矩阵,下图是老师的ppt,采用的是M矩阵的叫法)中m34未归一化的结果。上面的步骤中,L矩阵的最后一个值L12取1就是把右下角的m34提取出来的结果。但其实m34=1/|m3|,并不是1。
在这里插入图片描述

代码仓库地址:https://github.com/Lguanghui/openCV_DLP

  • 21
    点赞
  • 117
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
双目相机标定计算机视觉中的一个重要步骤,它可以通过计算双目相机之间的相对位置和姿态,将两个相机的图像进行联合,实现三维重构和深度测量等功能。OpenCV提供了一套完整的双目相机标定工具,下面是一个简单的标定流程: 1.采集双目图像数据,包括左右相机的内矩阵、畸变系数、图像尺寸等信息; 2.通过对图像数据进行预处理,包括去畸变、矫正等操作,使得标定结果更加精确; 3.提取双目图像中的特征点,并进行匹配,计算出左右相机之间的基础矩阵和本质矩阵; 4.通过标定板上的特征点的三维坐标和它们在相机图像中的对应点的二维坐标,计算出左右相机之间的外矩阵; 5.对标定结果进行评估,包括重投影误差、立体重建误差等指标,以判断标定结果的准确性和可靠性。 下面是一个基于OpenCV的双目相机标定示例代码: ```c #include <opencv2/opencv.hpp> #include <iostream> #include <vector> using namespace cv; using namespace std; int main() { //读取标定板图像 vector<vector<Point3f>> objectPoints; //标定板上的三维坐标 vector<vector<Point2f>> imagePoints1, imagePoints2; //左右相机上对应的二维图像点 Size imageSize; //图像尺寸 Mat cameraMatrix1, distCoeffs1; //左相机矩阵和畸变系数 Mat cameraMatrix2, distCoeffs2; //右相机矩阵和畸变系数 Mat R, T, E, F; //左右相机之间的旋转矩阵、平移矩阵、本质矩阵、基础矩阵 //设置标定数 Size boardSize(9, 6); //标定板内部角点数目 float squareSize = 30; //标定板内部边长,单位毫米 //生成标定板上的三维坐标 vector<Point3f> corners; for (int i = 0; i < boardSize.height; i++) { for (int j = 0; j < boardSize.width; j++) { corners.push_back(Point3f(j * squareSize, i * squareSize, 0)); } } //读取标定板图像 vector<String> filenames1, filenames2; glob("left/*.jpg", filenames1); //左相机图像文件夹 glob("right/*.jpg", filenames2); //右相机图像文件夹 for (int i = 0; i < filenames1.size(); i++) { Mat image1 = imread(filenames1[i]); Mat image2 = imread(filenames2[i]); imageSize = image1.size(); //提取标定板上的角点 vector<Point2f> corners1, corners2; bool found1 = findChessboardCorners(image1, boardSize, corners1); bool found2 = findChessboardCorners(image2, boardSize, corners2); if (found1 && found2) { //亚像素精确化角点位置 Mat gray1, gray2; cvtColor(image1, gray1, COLOR_BGR2GRAY); cvtColor(image2, gray2, COLOR_BGR2GRAY); cornerSubPix(gray1, corners1, Size(11, 11), Size(-1, -1), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1)); cornerSubPix(gray2, corners2, Size(11, 11), Size(-1, -1), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1)); //保存角点坐标 objectPoints.push_back(corners); imagePoints1.push_back(corners1); imagePoints2.push_back(corners2); } } //标定相机 double rms = stereoCalibrate(objectPoints, imagePoints1, imagePoints2, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T, E, F, CALIB_FIX_INTRINSIC + CALIB_USE_INTRINSIC_GUESS + CALIB_SAME_FOCAL_LENGTH + CALIB_RATIONAL_MODEL + CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 100, 1e-5)); cout << "Stereo calibration done with RMS error = " << rms << endl; //保存标定结果 FileStorage fs("stereo_calib.xml", FileStorage::WRITE); fs << "cameraMatrix1" << cameraMatrix1; fs << "distCoeffs1" << distCoeffs1; fs << "cameraMatrix2" << cameraMatrix2; fs << "distCoeffs2" << distCoeffs2; fs << "R" << R; fs << "T" << T; fs << "E" << E; fs << "F" << F; fs.release(); return 0; } ``` 以上代码仅供考,实际应用中需要根据具体情况进行修改和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

々云逸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值