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

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

:相机标定的作用

        (1):求解内外参数。 

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

二:标定的流程

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

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

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

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

       (5):计算重投影误差

2.1:

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

           

 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

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 我可以为您提供一个示例,该示例使用 OpenCV 在 Python 中实现基于霍夫变换的同心圆检测:importcv2importnumpyasnpimg=cv2.imread('circles.png',0)output=img.copy()cimg=cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)circles=cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=0)circles=np.uint16(np.around(circles))foriinrange(circles.shape[1]):cv2.circle(output,(circles[0][i][0],circles[0][i][1]),circles[0][i][2],(0,255,0),3)cv2.circle(cimg,(circles[0][i][0],circles[0][i][1]),circles[0][i][2],(0,255,0),3)cv2.imshow('detected circles',cimg)cv2.waitKey(0)cv2.destroyAllWindows() ### 回答2: 霍夫变换是一种图像处理算法,可以用于检测图像中的圆形。Python提供了很多库和工具,可以方便地实现基于霍夫变换的同心圆检测。 下面是一个简单的示例来描述基于霍夫变换的同心圆检测的Python代码: 首先,我们需要导入相关的库: import cv2 import numpy as np 然后,我们加载图像并将其转换为灰度图像: image = cv2.imread('image.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 接下来,我们可以应用霍夫变换来检测同心圆。在此之前,我们需要定义一些参数,如最小半径、最大半径和圆心的最小距离: circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=30, param1=100, param2=30, minRadius=0, maxRadius=0) 最后,我们可以在图像上绘制出检测到的圆: if circles is not None: circles = np.uint16(np.around(circles)) for circle in circles[0, :]: center = (circle[0], circle[1]) radius = circle[2] cv2.circle(image, center, radius, (0, 255, 0), 2) 最后,我们可以显示图像并保存结果: cv2.imshow("Detected Circles", image) cv2.waitKey(0) cv2.destroyAllWindows() cv2.imwrite("result.jpg", image) 这是一个基本的示例,您可以根据需要进行调整和优化。同时,还有其他参数可以控制霍夫变换的性能和检测结果的质量。 总结来说,我们可以使用Python中的OpenCV库来实现基于霍夫变换的同心圆检测。只需几行简单的代码,我们就可以轻松地应用这个强大的图像处理算法。 ### 回答3: 基于霍夫变换的同心圆检测是一种在图像中找到同心圆方法。Python提供了丰富的图像处理库和算法库,可以很方便地实现基于霍夫变换的同心圆检测。 首先,我们需要导入所需的库。在Python中,我们可以使用OpenCV库进行图像处理和霍夫变换。 ```python import cv2 import numpy as np ``` 接下来,我们需要读入待处理的图像,并将其转换为灰度图像。 ```python image = cv2.imread('image.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ``` 然后,我们可以使用霍夫变换函数`HoughCircles`来检测同心圆。该函数需要传入灰度图像、检测方法、输入图像分辨率和最小/最大半径等参数。 ```python circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=50, param2=30, minRadius=0, maxRadius=0) ``` 最后,我们可以将检测到的同心圆标记在原图像上。 ```python if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: cv2.circle(image, (x, y), r, (0, 255, 0), 4) ``` 完成以上步骤后,我们可以将结果保存或显示。 ```python cv2.imshow("Detected Circles", image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 以上就是使用Python实现基于霍夫变换的同心圆检测的简要步骤。根据实际需求,还可以对参数进行调优以达到更好的检测效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值