单目相机标定C++与Matlab实现

 

一、相机标定的原理

相机标定的目的?

找出三维空间与二维图像的对应关系。

1.坐标系

在这里总共涉及到四个坐标系的变化。这四个坐标系分别是:(1)像素坐标系;(2)成像坐标系;(3)相机坐标系;(4)世界坐标系。

1.像素坐标系

像素坐标系是一个直角坐标系,其坐标轴为(O_{0}-uv),其定义方式是:原点位于图像的左上角,u轴向右与x轴平行,v轴向下与y轴平行,其反映了相机CCD/CMOS芯片中像素的排列情况,在像素坐标系中单位是像素。像素坐标系与成像平面之间,相差了一个缩放和一个原点的平移

2.成像坐标系

成像坐标系是用来表示图像的物理位置的坐标系,定义此坐标轴为(O_{1}-XY),则此坐标系中的坐标(x,y)(x,y)表示图像上点的位置坐标。(x,y)对应的成像坐标系中的物理坐标的单位是毫米

3.相机坐标系

相机坐标系是一个三维直角坐标系,其坐标轴为(O_{c}-X_{c}Y_{c}Z_{c}),原点位于镜头光心处,其中Z_{c}轴称为相机的光轴,垂直于图像平面,X_{c}轴平行于成像坐标系的X轴,Y_{c}轴平行于成像坐标系的Y轴。

4.世界坐标系

世界坐标系作为一个基准,选择世界坐标系没有固定的方式,具体情况具体来考虑。其可以用来描述待测物体与相机的相对位置关系。其坐标轴为(O-X_{w}Y_{w}C_{w})

2.坐标系与坐标系之间的关系

1.世界坐标系与相机坐标系

设在相机坐标系下有一个点P,它的坐标为(X_{c},Y_{c},Z_{c}){_{}}^{T}P在世界坐标系下对应的坐标为(X_{w},Y_{w},Z_{w}){_{}}^{T}

因此,世界坐标系与相机坐标系中的坐标存在如下的关系:

\begin{bmatrix} X_{c} \\ Y_{c} \\ Z_{c} \\ 1 \end{bmatrix}=\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}\begin{bmatrix} X_{w} \\ Y_{w} \\ Z_{w} \\ 1 \end{bmatrix}

其中,R是旋转矩阵,为3x3的正交单位矩阵;t是一个平移矩阵,t=(t_{1},t_{2},t_{3})^{T}

同时这里用了一个数学技巧(《视觉SLAM14讲》第43页):我们在一个三维向量的末尾添加1,将其变成了四维向量,称为齐次坐标。

其中\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}称为外参数矩阵。

2.像素坐标系与成像坐标系

在二维成像平面坐标系中,相机光轴与图像平面的交点即为原点O_{1},该点称为图像的主点,如果我们不去考虑畸变,图像的主点就是图像的中心点,但是由于相机系统加工,外力作用等原因会产生一定的偏移。设主点O_{1}在像素坐标系中的坐标为(u_{0},v_{0}),则像素坐标系与成像坐标系对应关系如下:

\begin{bmatrix}u \\ v \\ 1 \end{bmatrix}=\begin{bmatrix}1/dx& 0&u_{0}\\ 0&1/dy&v_{0}\\0&0&1 \end{bmatrix}\begin{bmatrix} x\\ y\\ 1\end{bmatrix}

3.线性模型

相机的线性模型,即是设空间中的任意一点在图像上所成的像可以近似认为是通过小孔成像原理在像平面成像的,所以又称作针孔模型。用简单的针孔相机模型来对这种映射关系进行建模。同时,由于相机镜头上的透镜的存在,使得光线投影到成像平面的过程会产生畸变

单目相机标定C++与Matlab实现_第1张图片

按照这个模型,记空间中的一点P在相机坐标系中的坐标为(X_{c},Y_{c},Z_{c}),在成像平面坐标系中的坐标为(x,y),相机的焦距为f,则根据比例关系:

x=f*(x_{c}/z_{c})

y=f*(y_{c}/z_{c})

可以得到投影矩阵:

Z_{c}\begin{bmatrix}x \\ y \\ 1 \end{bmatrix}=\begin{bmatrix}f& 0&0&0\\ 0&f&0&0\\0&0&1&0 \end{bmatrix}\begin{bmatrix} X_{c}\\ Y_{c}\\ Z_{c}\\1\end{bmatrix}

根据世界坐标系与相机坐标系的坐标存在如下的关系:

\begin{bmatrix} X_{c} \\ Y_{c} \\ Z_{c} \\ 1 \end{bmatrix}=\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}\begin{bmatrix} X_{w} \\ Y_{w} \\ Z_{w} \\ 1 \end{bmatrix}

可以推出

Z_{c}\begin{bmatrix}x \\ y \\ 1 \end{bmatrix}=\begin{bmatrix}f& 0&0&0\\ 0&f&0&0\\0&0&1&0 \end{bmatrix}\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}\begin{bmatrix} X_{w} \\ Y_{w} \\ Z_{w} \\ 1 \end{bmatrix}

再根据像素坐标系与成像坐标系存在如下关系:

\begin{bmatrix}u \\ v \\ 1 \end{bmatrix}=\begin{bmatrix}1/dx& 0&u_{0}\\ 0&1/dy&v_{0}\\0&0&1 \end{bmatrix}\begin{bmatrix} x\\ y\\ 1\end{bmatrix}

可以推出

Z_{c}\begin{bmatrix}u \\ v \\ 1 \end{bmatrix}=Z_{c}\begin{bmatrix}1/dx& 0&u_{0}\\ 0&1/dy&v_{0}\\0&0&1 \end{bmatrix}\begin{bmatrix} x\\ y\\ 1\end{bmatrix}

=\begin{bmatrix}1/dx& 0&u_{0}\\ 0&1/dy&v_{0}\\0&0&1 \end{bmatrix}\begin{bmatrix}f& 0&0&0\\ 0&f&0&0\\0&0&1&0 \end{bmatrix}\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}\begin{bmatrix} X_{w} \\ Y_{w} \\ Z_{w} \\ 1 \end{bmatrix}

f_{u}=f/dxf_{v}=f/dy

K=\begin{bmatrix}f/dx& 0&u_{0}\\ 0&f/dy&v_{0}\\0&0&1 \end{bmatrix},则有

Z_{c}\begin{bmatrix}u\\ v\\1\end{bmatrix}=K\begin{bmatrix}R& t\\ 0^{T}&1 \end{bmatrix}\begin{bmatrix} X_{w} \\ Y_{w} \\ Z_{w} \\ 1 \end{bmatrix}=KTP_{w}

其中,K为3x3矩阵称为相机内参数矩阵,包含4个未知数;T称为相机外参数矩阵包含了一个Rt

这里我们可以看到将一个3x4的矩阵记为了K,变成了一个3x3的矩阵。后面的T为一个4x4的矩阵,显然是不能进行计算的。那么,为什么我们这里会这样考虑呢?具体可以看高博十四讲中的一些数学技巧,主要是关于齐次坐标和非齐次坐标的转化。

二、Matlab具体标定步骤

在开始标定之前,需要准备好标定板。这个可以从网上下载打印。

我使用的Matlab版本是R2017b。打开Matlab后在APP中找到camera calibration。

单目相机标定C++与Matlab实现_第2张图片

注意:matlab打开camera calibration后可能找不到摄像头,这时候需要注册账号然后去安装驱动。我所使用的是罗技C270USB摄像头,如果只是为了理解其整个原理及标定的步骤,也可以使用笔记本电脑自带的摄像头进行操作。

安装好其驱动过后,就可以找到摄像头了。如下图所示。

下面开始添加照片,总共有两种办法,一种是直接从文件读取照片(这个也需要提前用准备标定的相机拍摄),另外一种办法是直接调用摄像头进行拍摄,然后再用照片。

标定的图片最少不能低于2张,最好控制在10到20张左右。张数比较少,可能存在的偏差比较大。按道理讲,张数越多也就是样本越多应该越准确才对。但是事实不是越多越好。据我猜测,应该是后面计算的畸变系数,涉及到有

注意:在这里我犯了一个很笨的错误,我用自己的手机去拍摄标定板的照片,然后直接用手机拍摄的照片进行标定操作。这样导致最后得出来的标定结果是我自己手机的参数。

单目相机标定C++与Matlab实现_第3张图片

然后添加完图片之后会出来一个窗口,这里需要输入你的标定板真实的尺寸,由于实验室有一块非常大的标定板,在这里,我直接进行使用。该标定板的是9cmx9cm的正方形。

单目相机标定C++与Matlab实现_第4张图片

点击确定过后,会出现下面这张图。

单目相机标定C++与Matlab实现_第5张图片

单目相机标定C++与Matlab实现_第6张图片

 单目相机标定C++与Matlab实现_第7张图片

然后点击Export Camera Parameters

单目相机标定C++与Matlab实现_第8张图片

点击确定。会看到如下结果。

cameraParams = 

  cameraParameters - 属性:

   Camera Intrinsics
                    IntrinsicMatrix: [3×3 double]
                        FocalLength: [813.8289 813.8800]
                     PrincipalPoint: [344.9927 248.5357]
                               Skew: 0
                   RadialDistortion: [0.0201 0.3590]
               TangentialDistortion: [0 0]
                          ImageSize: [480 640]

   Camera Extrinsics
                   RotationMatrices: [3×3×10 double]
                 TranslationVectors: [10×3 double]

   Accuracy of Estimation
              MeanReprojectionError: 0.2204
                 ReprojectionErrors: [99×2×10 double]
                  ReprojectedPoints: [99×2×10 double]

   Calibration Settings
                        NumPatterns: 10
                        WorldPoints: [99×2 double]
                         WorldUnits: 'millimeters'
                       EstimateSkew: 0
    NumRadialDistortionCoefficients: 2
       EstimateTangentialDistortion: 0

然后再Matlab中输入以下三行命令即可得到我们要标定的一些参数。

>> cameraParams.IntrinsicMatrix

ans =

  813.8289         0         0
         0  813.8800         0
  344.9927  248.5357    1.0000

>> cameraParams.RadialDistortion

ans =

    0.0201    0.3590

>> cameraParams.TangentialDistortion

ans =

     0     0

到这里,对罗技C270摄像头的标定就完成了。

三、C++实现

主要参考博客

张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)

代码参考:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 
using namespace cv;
using namespace std;

int main()
{
    ifstream fin("calibdata.txt"); /* 标定所用图像文件的路径 */
    ofstream fout("caliberation_result.txt");  /* 保存标定结果的文件 */
    //读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化
    cout<<"开始提取角点………………";
    int image_count=0;  /* 图像数量 */
    Size image_size;  /* 图像的尺寸 */
    Size board_size = Size(7,7);    /* 标定板上每行、列的角点数 */
    vector image_points_buf;  /* 缓存每幅图像上检测到的角点 */
    vector > image_points_seq; /* 保存检测到的所有角点 */
    string filename;
    int count= -1 ;//用于存储角点个数。
    cv::namedWindow("test",CV_WINDOW_AUTOSIZE);
    while (getline(fin,filename))
    {
        image_count++;
        // 用于观察检验输出
        cout<<"image_count = "<count = "< 第 "< : "<"<"< >object_points; /* 保存标定板上角点的三维坐标 */
    /*内外参数*/
    Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); /* 摄像机内参数矩阵 */
    vector point_counts;  // 每幅图像中角点的数量
    Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
    vector tvecsMat;  /* 每幅图像的平移向量 */
    vector rvecsMat; /* 每幅图像的旋转向量 */
    /* 初始化标定板上角点的三维坐标 */
    int i,j,t;
    for (t=0;t tempPointSet;
        for (i=0;i image_points2; /* 保存重新计算得到的投影点 */
    cout<<"\t每幅图像的标定误差:\n";
    fout<<"每幅图像的标定误差:\n";
    for (i=0;i tempPointSet=object_points[i];
        /* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
        projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);
        /* 计算新的投影点和旧的投影点之间的误差*/
        vector tempImagePoint = image_points_seq[i];
        Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
        Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
        for (int j = 0 ; j < tempImagePoint.size(); j++)
        {
            image_points2Mat.at(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
            tempImagePointMat.at(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
        }
        err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
        total_err += err/=  point_counts[i];
        std::cout<<"第"<>imageFileName;
        filePath+=imageFileName;
        filePath+=".bmp";
        Mat imageSource = imread(filePath);
        Mat newimage = imageSource.clone();
        //另一种不需要转换矩阵的方式
        //undistort(imageSource,newimage,cameraMatrix,distCoeffs);
        remap(imageSource,newimage,mapx, mapy, INTER_LINEAR);
        StrStm.clear();
        filePath.clear();
        StrStm<>imageFileName;
        imageFileName += "_d.jpg";
        imwrite(imageFileName,newimage);
    }
    std::cout<<"保存结束"<

使用CMake的编译方法

CMakelists编写

cmake_minimum_required(VERSION 2.8)

project(calib)
set(OpenCV_DIR /opt/ros/kinetic/share/OpenCV-3.3.1-dev)

find_package(OpenCV REQUIRED)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} "main.cpp")

TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${OpenCV_LIBS})

终端输入 

cmake .
make

经过调试发现与输入的图片格式无关,可能编译不成功和IDE有关,故采取CMake的方法,编译成功。

 

 

以上有的地方理解的并不是特别深刻,所以难免会有错误。

大家多多交流,相互学习。

参考资料

  • 摄像机标定方法的研究
  • 《视觉SLAM十四讲 从理论到实践》高翔 张涛 等著

你可能感兴趣的:(标定,杂七杂八)