机器视觉学习笔记(5)——基于OpenCV的单目摄像机标定
本文CameraCalibrator类源代码来自于OpenCV2
 计算机视觉编程手册(Robert Laganiere 著 张静 译)
强烈建议阅读机器视觉学习笔记(4)——单目摄像机标定参数说明之后再阅读本文
1.单目摄像机标定目的
单目摄像机标定的目的就是使摄像机实际状态无限接近理论推导的理想状态。单目摄像机标定最终将确定9个参数,摄像机内参数有4个,透镜畸变参数5个。
2.单目摄像机标定流程
- 制作标定板
 - 使用摄像机拍摄不同角度的标定板
 - 将照片放置于预设的文件夹中
 - 编写程序计算摄像机内参数和透镜畸变参数
 - 保存9个参数
 
3.关键源代码说明
3.1bool findChessboardCorners((InputArray image, Size patternSize,
 OutputArray corners)
OutputArray corners)
Finds the positions of internal corners of the chessboard.
(寻找棋盘格标定板的角点)
- 三个参数依次代表输入图像,角点数目,存储角点的变量
 - 检测到角点以后,常常需要用
void drawChessboardCorners()函数将其画出来 - 如果找到的角点数目和输入的角点数目相同,就会用彩色圆圈画出角点,否则只用红色圆圈画出角点
 
示例程序如下:
void test()
{
    vector<Point2f> imageCorners;
    Size boardSize(9, 6);
    Mat image = imread("left01.jpg");
    bool found = findChessboardCorners(image, boardSize, imageCorners);
    //绘制角点
    drawChessboardCorners(image, boardSize, imageCorners, found);
    namedWindow("test");
    imshow("test", image);//角点如未全部检测出来只是红色圆圈画出角点
    waitKey();
}
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 

3.2Class CameraCalibrator
class CameraCalibrator{
    //输入点
    std::vector<std::vector<cv::Point3f>> objectPoints;//世界坐标系下的点
    std::vector<std::vector<cv::Point2f>> imagePoints;//像素坐标系下的点
    //输出矩阵
    cv::Mat cameraMatrix;//摄像机内参数矩阵
    cv::Mat distCoeffs;//透镜畸变系数矩阵
    //标定方式
    int flag;
    //用于图像去畸变
    cv::Mat map1,map2;
    bool mustInitUndistort;
  public:
    CameraCalibrator() : flag(0), mustInitUndistort(true) {};
    //导入标定图片提取角点
    int addChessboardPoints(const std::vector<std::string>& filelist, cv::Size & boardSize);
    //添加场景点与对应的图像点
    void addPoints(const std::vector<cv::Point2f>& imageCorners, const std::vector<cv::Point3f>& objectCorners);
    //标定相机
    double calibrate(cv::Size &imageSize);
    //设置标定方式
    void setCalibrationFlag(bool radial8CoeffEnabled=false, bool tangentialParamEnabled=false);
    //消除透镜畸变(标定之后调用有效)
    cv::Mat CameraCalibrator::remap(const cv::Mat &image);
    // 获取矩阵
    cv::Mat getCameraMatrix() { return cameraMatrix; }
    cv::Mat getDistCoeffs()   { return distCoeffs; }
};
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 - 21
 - 22
 - 23
 - 24
 - 25
 - 26
 - 27
 - 28
 - 29
 - 30
 - 31
 - 32
 
4.单目标定实例
int main()
{
    cv::namedWindow("Image");
    cv::Mat image;
    std::vector<std::string> filelist;//存放标定图片路径
    //生成路径,此处表示图片放在工程根目录下的chessboards文件夹
    for (int i=1; i<=20; i++)
    {
        std::stringstream str;
        str << "chessboards/chessboard" << std::setw(2) << std::setfill('0') << i << ".jpg";//图片的相对路径
        std::cout << str.str() << std::endl;
        filelist.push_back(str.str());
        image= cv::imread(str.str());
        cv::imshow("Image",image);
        cv::waitKey(100);
    }
    CameraCalibrator cameraCalibrator;
    //从棋盘格添加角点
    cv::Size boardSize(6, 4);
    cameraCalibrator.addChessboardPoints(
        filelist,   //图片路径
        boardSize); //角点数目
    //标定相机
    cameraCalibrator.calibrate(image.size());
    //选取某张图片,消除透镜畸变
    image = cv::imread(filelist[6]);
    cv::Mat uImage= cameraCalibrator.remap(image);
    imshow("Original Image", image);
    imshow("Undistorted Image", uImage);
    //打印相机内参数矩阵(3*3矩阵)
    Mat cameraMatrix = cameraCalibrator.getCameraMatrix();
    std::cout << " 相机内参数矩阵:" << cameraMatrix.rows << "x" << cameraMatrix.cols << std::endl;
    for (int i=0; i<cameraMatrix.rows; i++)
        for (int j=0; j<cameraMatrix.cols; j++)
            {
                cout<<setw(10)<<cameraMatrix.at<double>(i, j);
                if (j==2)
                    cout<<endl;
            }
    //打印畸变系数矩阵(1*5矩阵)
    Mat distCoeffs = cameraCalibrator.getDistCoeffs();
    std::cout << "畸变系数矩阵:" << distCoeffs.rows << "x" << distCoeffs.cols << std::endl;
    for (int i=0; i<distCoeffs.rows; i++)
        for (int j=0; j<distCoeffs.cols; j++)
            cout<<distCoeffs.at<double>(i, j)<<"\t";
    waitKey(0);
}  
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 - 21
 - 22
 - 23
 - 24
 - 25
 - 26
 - 27
 - 28
 - 29
 - 30
 - 31
 - 32
 - 33
 - 34
 - 35
 - 36
 - 37
 - 38
 - 39
 - 40
 - 41
 - 42
 - 43
 - 44
 - 45
 - 46
 - 47
 - 48
 - 49
 - 50
 - 51
 
- 畸变校正之前 

 - 畸变校正之后 

 - 相机内参数矩阵为 
⎡⎣⎢167.156000178.0970155.89119.3721⎤⎦⎥ - 透镜畸变系数矩阵(分别表示k1,k2,p1,p2,k3)为 
[−0.34560.1319−0.0004−0.0034−0.0227] 
5.总结
- 尽管核心函数都是OpenCV库函数,但是通过面向对象思想把相关函数和变量整合起来定义一个类是非常棒的方式,这样就可以专注于逻辑思考而不是一些变量和语法
 - 标定结果是否准确可以通过相机内参数矩阵大致推算出来。笔者自己的1280*720分辨率相机标定的x0,y0分别是622pix,370pix,恰好是分辨率的一半左右,符合其物理意义,可以断定标定正确(精度另说)
 - 本文实例中的x0,y0分别是156pix,119pix,由此我们可以推断作者相机的分辨率是312*234左右,由于视频分辨率常见的也就那几种,所以可以断定实例程序相机的分辨率是320*240
 
转自:http://blog.csdn.net/xuelabizp/article/details/50327393
机器视觉学习笔记(5)——基于OpenCV的单目摄像机标定的更多相关文章
- opencv单目摄像机标定(一)
		
#include <string> #include <iostream> #include <cv.h> #include <highgui.h> u ...
 - opencv单目摄像机标定
		
#include <cv.h> #include <highgui.h> #include <iostream> #include <stdio.h> ...
 - opencv单目摄像机标定(二)
		
// 引入实际标定板方格宽度的标定程序 #include <string> #include <iostream> #include <cv.h> #include ...
 - Django学习笔记(五)—— 表单
		
疯狂的暑假学习之 Django学习笔记(五)-- 表单 參考:<The Django Book> 第7章 1. HttpRequest对象的信息 request.path ...
 - OpenCV 学习笔记 02 使用opencv处理图像
		
1 不同色彩空间的转换 opencv 中有数百种关于不同色彩空间的转换方法,但常用的有三种色彩空间:灰度.BRG.HSV(Hue-Saturation-Value) 灰度 - 灰度色彩空间是通过去除彩 ...
 - SpringMVC:学习笔记(5)——数据绑定及表单标签
		
SpringMVC——数据绑定及表单标签 理解数据绑定 为什么要使用数据绑定 基于HTTP特性,所有的用户输入的请求参数类型都是String,比如下面表单: 按照我们以往所学,如果要获取请求的所有参数 ...
 - Flutter学习笔记(13)--表单组件
		
如需转载,请注明出处:Flutter学习笔记(13)--表单组件 表单组件是个包含表单元素的区域,表单元素允许用户输入内容,比如:文本区域,下拉表单,单选框.复选框等,常见的应用场景有:登陆.注册.输 ...
 - Hadoop学习笔记(两)设置单节点集群
		
本文描写叙述怎样设置一个单一节点的 Hadoop 安装.以便您能够高速运行简单的操作,使用 Hadoop MapReduce 和 Hadoop 分布式文件系统 (HDFS). 參考官方文档:Hadoo ...
 - 学习笔记:使用opencv做双目测距(相机标定+立体匹配+测距).
		
最近在做双目测距,觉得有必要记录点东西,所以我的第一篇博客就这么诞生啦~ 双目测距属于立体视觉这一块,我觉得应该有很多人踩过这个坑了,但网上的资料依旧是云里雾里的,要么是理论讲一大堆,最后发现还不知道 ...
 
随机推荐
- 回溯和DFS效率分析
			
回溯和DFS效率分析 一.心得 多组数据记得初始化 两组样例,找圆点点的个数 6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# . ...
 - 公共域名服务DNS 114.114.114.114和8.8.8.8
			
一.两者的联系 114.114.114.114和8.8.8.8,这两个IP地址都属于公共域名解析服务DNS其中的一部分,而且由于不是用于商业用途的,这两个DNS都很纯净,不用担心因ISP运营商导致的D ...
 - [转]linux将一个服务器上的文件或者文件夹复制到另一台服务器上
			
本文转载自<linux 将一个服务器上的文件或者文件夹复制到另一台服务器上>,有时间实践一把 使用scp将一个Linux系统中的文件或文件夹复制到另一台Linux服务器上 复制文件或文件夹 ...
 - Xcode export/upload error: Your session has expired. Please log in  解决方法
			
问题: 突然打包账号不好使了 重登 重启 清缓存 一套都打完了 还是不好使 解决方法: 删除掉其他账号 重新登录 参考网址 http://stackoverflow.com/ques ...
 - shell编程实例1
			
1.vim hello.sh 2. #!bin/bash echo "hello world!" 3.chmod +x hello.sh 4.source hello.sh ls ...
 - 剑指offer--33.丑数
			
本来用数组做标志位,但是测试数据有第1500个,859963392,惹不起哦 ------------------------------------------------------------- ...
 - C++使用初始化列表的方式来初始化字段
			
几个月之前,接触Android recovery源代码的时候,看ScreenRecoveryUI类的时候,那时候C++基础还不是特别好,一直不明白以下的初始化方式: 下面这个是Recovery的一个构 ...
 - Leetcode 589. N-ary Tree Preorder Traversal
			
DFS,两种实现方式,递归和栈. """ # Definition for a Node. class Node: def __init__(self, val, chi ...
 - C++中strftime()的详细说明
			
我们可以使用strftime()函数将时间格式化为我们想要的格式.它的原型如下: size_t strftime( char *strDest, size_t maxsize, const char ...
 - Java进阶知识点6:并发容器背后的设计理念 - 锁分段、写时复制和弱一致性
			
一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...