基于同心圆的单目相机标定

   网上绝大部分博客关于相机标定的讲解全都关于理论上的,很少有代码的实现。因此,打算写这篇博客。博客并不涉及相关公式推导,假设大家都已经懂三大世界坐标系、内外参等名词含义。

:相机标定的作用

        (1):求解内外参数。 

        (2):用于处理畸变矫正。

二:标定的流程

       (1):准备若干张标定图片(至少四张)

       (2):图像预处理,清除图像上无关的轮廓信息

       (3):提取角点信息。(同心圆的圆心即为角点)

       (4):调用相机标定函数

       (5):计算重投影误差

2.1:

扫描二维码关注公众号,回复: 15429447 查看本文章

实验所用的靶标图像如下所示,在相机标定实验中一般要求图片最少为四张。在文章最后,我会给出本实验所用的图像,方便大家使用。

           

 2.2:

         下图为输入的靶标图像Canny后的效果,可以看到,图像上有许多干扰的边缘轮廓,我们仅仅是对图像上11*9的同心圆轮廓感兴趣,因此需要进行图像预处理操作。具体的操作打算在另一篇博客里进行阐述,此处简单了解一下。

                                 

    2.3:

           在图像预处理之后,利用opencv中的fitEllipse椭圆拟合函数,得到同心圆的中心坐标。并在原始图像上画了出来,我已经把此次标定实验所提取的中心坐标存放到了文本当中,会附录在百度云盘里。

                            

      2.4:

        其实,opencv中已经封装好了相机标定函数,我们仅仅是搬运工,会调用函数,知道其中参数的含义即可。利用calibrateCamera函数计算出所需的内外参数。

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

  2.5:

 重投影误差主要是用来评价所求的内外参数的精度,误差越小,说明求出的内外参数比较靠谱,能作为下一步的输入。

the 1 image of average error: 0.0143862pix

the 2 image of average error: 0.0155335pix

the 3 image of average error: 0.0138766pix

the 4 image of average error: 0.0140222pix

the 5 image of average error: 0.0144393pix

the 6 image of average error: 0.0135796pix

the 7 image of average error: 0.0147223pix

the 8 image of average error: 0.0143567pix

上面为本次实验重投影的平均误差,误差越小越好。

三:代码部分

#include "widget.h"
#include <QApplication>
#include<stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
#include<opencv2/calib3d/calib3d.hpp>
#include<opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

void convert_float(char name[],double temp[2])
{

    char filename[20];  char tt[10];
    int i=0,num=0,k=0;

    for(k;name[k]!='\0';k++)
    {
        filename[k]=name[k];
    }
    filename[k]='\0';

    temp[0]=atof(filename);

    for(i;filename[i]!=' ';i++);

    for(i;filename[i]!='\0';i++)
    {
        tt[num]=filename[i];
        num++;
    }
    tt[num]='\0';
    temp[1]=atof(tt);
}


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    char filename[100];
    string infilename = "H:/Image/cc/center4.txt";  //注意改下各自电脑上的路径
    cv::Size imageSize;
    imageSize.width=1280;
    imageSize.height=1024;
    //标定板上每行每列的角点数
    cv::Size boardSize=cv::Size(11,9);

    //缓存每幅图片上检测到的角点
    std::vector<Point2f> imagePointsBuf;
    //保存检测到的所有角点
    std::vector<std::vector<Point2f>> imagePointsSeq;
    ifstream fin(infilename);

    if(fin.is_open())
    {
        while(!fin.eof())
        {
            fin.getline(filename,sizeof(filename)/sizeof(char));

            if(filename[0]=='#')
            {
               imagePointsSeq.push_back(imagePointsBuf);

               imagePointsBuf.clear();
               continue;
            }
            Point2f temp_coordinate; double temp[2];
            convert_float(filename,temp);
            temp_coordinate.x=temp[0];
            temp_coordinate.y=temp[1];
            imagePointsBuf.push_back(temp_coordinate);
        }
    }

    for(int i=0;i<imagePointsSeq.size();i++)
    {

        string imagePath ="H:/Image/cc/"+to_string(i+1)+".bmp";
        Mat image=imread(imagePath);
        vector<Point2f> temp=imagePointsSeq[i];
        for(int j=0;j<temp.size();j++)
        {
            Point2f tt=temp[j];
           circle(image,tt,2,Scalar(0,0,255),2,8);
        }
        imshow(to_string(i+1),image);
    }

    //保存标定板上角点的三维坐标
    vector<vector<Point3f>> objectPoints;
    //相机内参数矩阵 M=[fx γ u0,0 fy v0,0 0 1]
    Mat cameraMatrix=cv::Mat(3,3,CV_64F,Scalar::all(0));
    //相机的五个畸变系数 k1 k2 p1 p2 p3
    Mat distCoeffs=Mat(1,5,CV_64F,Scalar::all(0));
    //每幅图片的旋转向量
    vector<Mat> tvecsMat;
    //每幅图片的平移向量
    vector<Mat> rvecsMat;

    //初始化标定板上角点的三维坐标  给出每个角点的世界坐标系下坐标
    int i,j,t;
    for(t=0;t<8;t++)  //我只用八张图像进行标定
    {
        vector<Point3f> tempPointSet;
        //行数
        for(i=0;i<boardSize.height;i++)  // 9
        {
            //列数
            for(j=0;j<boardSize.width;j++) // 11
            {
                Point3f realPoint;   //每一幅图片上有11*9 个角点
                //假定标定板放在世界坐标系中Z=0的平面上
                realPoint.x=i*30.0;
                realPoint.y=j*30.0;
                realPoint.z=0;

               tempPointSet.push_back(realPoint);
            }
        }
        objectPoints.push_back(tempPointSet);
    }


//    //开始标定
calibrateCamera(objectPoints,imagePointsSeq,imageSize,cameraMatrix,distCoeffs,rvecsMat,tvecsMat);


    cout<<cameraMatrix<<endl;// 输出相机内参数矩阵

    // 输出每张图片的旋转矩阵、平移向量
    for(int i=0;i<8;i++)
    {
        cout<<i+1<<" "<<"picture"<<endl;
        cout<<rvecsMat[i]<<endl;
        cout<<tvecsMat[i]<<endl;
        cout<<endl;
    }

    cout<<"calibration is over"<<endl;
    cout<<"start to  estimate result "<<endl;

    //所有图像的平均误差总和
    double totalErr=0.0;
    //每幅图像的平均误差
    double err=0.0;
    //保存重新计算得到的投影点
    vector<Point2f> imagePoints2;

    for(i=0;i<8;i++)
    {
    vector<Point3f> tempPointSet=objectPoints[i];  //每幅图片 角点的世界坐标
    //通过对得到的相机内外参数 对空间的三维点进行重新投影计算,得到新的投影点 imagePoints2(在像素坐标系下的点坐标)
    //就是利用 得到的内外参数 世界坐标系下角点坐标 计算出每个角点的像素坐标
    //每幅图片的 所有角点世界坐标 旋转矩阵    平移向量       内参数矩阵   畸变系数    保存计算到的每幅图片 角点的二维坐标
    projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,imagePoints2);

    //计算 新投影点和 旧投影点之间的误差
    vector<Point2f> tempImagePoint=imagePointsSeq[i];

    Mat tempImagePointMat=Mat(1,tempImagePoint.size(),CV_32FC2);  //原始二维点
    Mat imagePoints2Mat=Mat(1,imagePoints2.size(),CV_32FC2);     //新计算出来的二维点

    for( j=0;j<tempImagePoint.size();j++)
    {
        imagePoints2Mat.at<cv::Vec2f>(0,j)=cv::Vec2f(imagePoints2[j].x,imagePoints2[j].y);
        tempImagePointMat.at<cv::Vec2f>(0,j)=cv::Vec2f(tempImagePoint[j].x,tempImagePoint[j].y);
    }
     //计算误差
    err=norm(imagePoints2Mat,tempImagePointMat,NORM_L2);
    err/=99;
    cout<<"the "<<i+1<<" image of average error: "<<err<<"pix"<<endl;

    }

    return a.exec();
}

代码部分大多加了注释,一步一步的看,还是能够看到明白的。当然,这只是基础的单目相机标定,仅仅适合入门。想要提高精度,任重而道远。

若有问题,敬请指出。

另外,附上靶标图像以及保存椭圆中心的文本center4.txt.

链接:https://pan.baidu.com/s/1XcZzLUWPHSNQ_f6bxABEnw 
提取码:6tg8

猜你喜欢

转载自blog.csdn.net/qq_42027706/article/details/121696702