写在题前:这篇文章磨磨蹭蹭了好久,曾经两次接近完稿而丢失。我想任何事情在起步时都会有类似的囧境,还好我还有恒心继续下去。

摄像头标定的目的有两个。第一,要还原摄像头成像的物体在真实世界的位置就需要知道世界中的物体到计算机图像平面是如何变换的,摄像头标定的目的之一就是为了搞清楚这种变换关系,求解内外参数矩阵。第二,针孔摄像头的发明使得摄像头变成了亲民物品,大行于世,但是针孔摄像头有个很大的问题——畸变。摄像头标定的另一个目的就是求解畸变系数,然后用于计算求解正确的成像。

  • 数学原理

    • 映射矩阵-内部参数(intrinsic parameters)和外部参数(extrinsic parameters)

前面所说的真实世界到成像平面的变换过程牵扯到四个坐标系,变换矩阵可以大致分为两组参数——内部参数和外部参数。下面依次根据四种坐标系的关系来推导内外参数的形式。

计算机坐标系和成像平面坐标系

计算机坐标系是指数字图像在计算机中的保存形式-二维数组的坐标形式。成像平面坐标系是摄像机镜头的成像平面上建立的坐标系,一般是位于感光器件上(如CCD)所建立的以镜头光轴与成像平面交点为原点的二维坐标系。由于二维数组以离散的形式保存,所以计算机坐标系的长度单位为1,一般感光器件大小约为指甲盖大小,上面又密集的分别很多感光单元,每个感光单元采集的图像都会计算成计算机坐标系中的像素值,所以一般长度单位为微米。具体二者关系见下图,

图中坐标系O-uv为计算机坐标系,其坐标轴方向从右上到左下递增。坐标系Q-xy为成像平面坐标系,其坐标原点Q在计算机坐标系的坐标为(u0,v0)。假设有一点P,其在计算机坐标系中的坐标为(u,v),在成像平面坐标系中的坐标为(x,y),并设像素单元在x,y方向的尺寸分别为a,b。则有如下等式成立,

整理成齐次变换矩阵的形式如下,

成像平面坐标系和摄像机坐标系

摄像机坐标系是一个以摄像机镜头的光心为原点而建立的空间三维坐标系。成像平面坐标系可以看成摄像机坐标系在其Zc轴上投影而成的(其x和y轴与成像平面坐标系方向一致)。其与成像平面坐标系的关系如下图,

这里需要详细解释一下上图怎样理解。图中Oc-XcYcZc为摄像机坐标系,M点为物点,而Q-xy为成像平面坐标系。实际情况下成像平面与物体应该分居摄像头两侧,但是在这里我们将成像平面以摄像头坐标系原点为中心对称点对称过来。这样的好处是本来倒立的像变成正立的,且大小不变。如上一篇博客写到的那样,中心对称的后的像点和物点连线必定过光心。我们要记住小孔成像的性质是,成像平面到镜头的距离始终等于焦距f。由相似三角形,有如下等式成立,

整理成齐次变换矩阵的形式如下,

摄像机坐标系和世界坐标系

世界坐标系是区别于摄像机坐标系的一个空间坐标系,其依附于我们标定时要使用的物点而存在。既我们观察的物点相对于世界坐标系的位置是固定的,如张正友标定法中就是选的世界坐标系为以chessboard平面为XOY平面的相对于chessboard不动的空间坐标系。下图是我费了老大劲绘制的摄像机坐标系和世界坐标系之间的位置关系,

我们知道空间中的两个坐标系可以通过平移旋转变换而重合,那么现在我们来简单粗暴解释一下物点M在两个坐标系下坐标的代数关系是什么样的。假设摄像机坐标系可以通过旋转平移变换与世界坐标系重合,其齐次变换矩阵形式如下,

也就是说摄像机坐标系上的每个点(在世界坐标系中的坐标)通过上述旋转平移变换后与世界坐标系中的相应点重合。我们可以理解的是,对摄像机坐标系上的每个点和物点M(在世界坐标系下的坐标)做相同的旋转平移变换后其相对位置不会改变。也就是说物点M在摄像机坐标系中的坐标不会改变。我们考察一下变换完后是什么情况。这个时候世界坐标系和摄像机坐标系重合,且物点M相对于摄像机坐标系的坐标没有改变,那么这个时候物点M在世界坐标系中的坐标应该变成了变换前其在摄像机坐标系下的坐标。所以有如下关系成立,

综合上面的三组关系,我们可以推导出世界坐标系和计算机坐标系的直接代数关系,

进一步化简,

我们记,

A矩阵式刻画摄像机的内部参数,包括焦距f、成像中心的位置、及成像单元尺寸,因此称为内参矩阵。M描述的摄像头的运动关系,所以称为外参矩阵。其中R有三个相关参数,分别是绕x,y,z轴的旋转角度。T也有三个相关参数,分别是在三个坐标轴上的平移量Tx、Ty、Tz。

  • 摄像头的畸变参数(distortion parameters)

摄像头的畸变是由于成像模型的不精确造成的。人们为了提高光通量用透镜代替小孔来成像,由于这种代替不能完全符合小孔成像的性质,因此畸变就产生了。另外这里再插句额外的话,现在大量使用的透镜为球面镜,原因是其廉价易得。但是真正的完全符合理想光学系统的透镜实际是个四次曲面(很好证明,依据光程不变),制造成本很大哦。

畸变可以分为两大类,径向畸变和切向畸变。详细的畸变介绍可以参考工程光学的相关课程,下面简单介绍相关畸变及其修正。

径向畸变(radial distortion)

径向畸变的效应有两种,一种是枕形效应,另一种是桶形效应,具体见下图(图片来自互联网),

径向畸变可用下面公式修正,

切向畸变(tangential distortion)

径向畸变是由于透镜与成像平面不严格的平行,其可以用如下公式修正,

这样又引入了五个畸变参数,

  • 小结

我们记fx=f/a,fy=f/b。通过上面的介绍,我们了解到,摄像机标定共有4个内参,6个外参和五个畸变参数要求。下面就介绍怎么基于OpenCV函数库标定求得这三组参数。其求解原理放在以后的博客中叙述。

  • 基于OpenCV的标定程序

OpenCV中有标定实例哦,写的很好,功能很完善。一个是基于命令行标定参数读入的标定程序,另一个是基于xml文件参数读入的标定程序。它们的位置分别为,

...\opencv\sources\samples\cpp\calibration.cpp
...\opencv\sources\samples\cpp\tutorial_code\calib3d\camera_calibration\ camera_calibration.cpp

但是为了更深入的理解OpenCV的标定方法库,我自己写了一个简单粗暴易读的标定程序。下面简单介绍一下标定的过程。

  • 标定过程简介

标定过程如下,

  1. 图像获取
  2. 角点检测,如果没有检测到角点重复第一步
  3. 亚像素检测以提高角点检测精度
  4. 标记检测出的角点
  5. 如果成功检测到角点图片小于预设的数目,重复上面四个步骤
  6. 标定
  7. 标定结果保存

我写的标定程序如下,

  1 /*
  2     Writer: Wang Xianshun
  3     Email:    german_iris@outlook.com
  4 */
  5 #include <iostream>
  6 #include <stdio.h>
  7 #include <time.h>
  8 #include <string.h>
  9
 10 #include <cv.hpp>
 11 #include <highgui\highgui.hpp>
 12 #include <calib3d\calib3d.hpp>
 13 #include <imgproc\imgproc.hpp>
 14 #include <core\core.hpp>
 15
 16 using namespace std;
 17 using namespace cv;
 18
 19 static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners)
 20 {
 21     corners.resize(0);
 22     for (int i = 0; i < boardSize.height; i++)        //height和width位置不能颠倒
 23     for (int j = 0; j < boardSize.width; j++)
 24     {
 25         corners.push_back(Point3f(j*squareSize, i*squareSize, 0));
 26     }
 27 }
 28
 29 int main(int argc, char** argv)
 30 {
 31     int success = 0;
 32     int cameraId = 0;
 33     int nFrames = 10;
 34     int w = 6;
 35     int h = 9;
 36     clock_t prevTimestamp = 0;
 37     int delay = 1000;
 38
 39     //相关参数初始化
 40     Size boardSize, imageSize;
 41     boardSize.width = w;
 42     boardSize.height = h;
 43     vector<vector<Point2f>> imagePoints;
 44     float squareSize = 1.f;
 45     Mat intrMatrix, distCoeffs;
 46     vector<Mat> rvecs, tvecs;
 47
 48     //标定参数读取
 49     if (argc < 5)
 50     {
 51         cout << "参数不足" << endl;
 52         return 0;
 53     }
 54
 55     for (int i = 1; i < argc; i++)
 56     {
 57         if (!strcmp(argv[i], "-w"))
 58         {
 59             if (!sscanf(argv[++i], "%u", &boardSize.width))
 60             {
 61                 return fprintf(stderr, "无效的标定角点宽度\n"), -1;
 62             }
 63         }
 64         else if (!strcmp(argv[i], "-h"))
 65         {
 66             if (!sscanf(argv[++i], "%u", &boardSize.height))
 67             {
 68                 return fprintf(stderr, "无效的标定角点高度\n"), -1;
 69             }
 70         }
 71         else if (!strcmp(argv[i], "-s"))
 72         {
 73             if (!sscanf(argv[++i], "%f", &squareSize) != 1 || squareSize <= 0)
 74             {
 75                 return fprintf(stderr, "无效的方格尺寸\n"), -1;
 76             }
 77         }
 78         else
 79             return fprintf(stderr, "未知参数\n"), -1;
 80     }
 81
 82     //图像采集
 83     VideoCapture capture;
 84     capture.open(cameraId);
 85     namedWindow("Image View", 1);
 86
 87     if (!capture.isOpened())
 88     {
 89         cout << "无法打开摄像头,(づ ̄3 ̄)づ╭❤~……" << endl;
 90         return -1;
 91     }
 92
 93     for (int i = 0; success < nFrames; i++)
 94     {
 95         string msg = "CAPTURING";
 96         Mat viewGray, view;
 97         capture >> view;
 98         imageSize = view.size();
 99         vector<Point2f> pointBuf;
100         cvtColor(view, viewGray, COLOR_BGR2GRAY);
101
102         //寻找角点
103         bool found = findChessboardCorners(view, boardSize, pointBuf,
104             CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
105
106         if (found)
107         {
108             //亚像素检测以提高精度
109             cornerSubPix(viewGray, pointBuf, Size(11, 11),
110                 Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
111             //标记出检测到的角点
112             drawChessboardCorners(view, boardSize, Mat(pointBuf), found);
113         }
114
115         //等待用户改变姿态
116         if (found && clock() - prevTimestamp > delay*1e-3*CLOCKS_PER_SEC)
117         {
118             imagePoints.push_back(pointBuf);
119             prevTimestamp = clock();
120             success = success + 1;
121             bitwise_not(view, view);
122         }
123
124         imshow("Image View", view);
125         waitKey(20);
126     }
127
128     cout << "图像采集完成,开始标定……" << endl;
129
130     //标定
131     vector<vector<Point3f>> ObjectPoints(1);
132     calcChessboardCorners(boardSize, squareSize, ObjectPoints[0]);
133     ObjectPoints.resize(imagePoints.size(), ObjectPoints[0]);
134     calibrateCamera(ObjectPoints, imagePoints, imageSize, intrMatrix,
135         distCoeffs, rvecs, tvecs);
136     bool ok = checkRange(intrMatrix) && checkRange(distCoeffs);
137
138     if (!ok)
139     {
140         cout << "标定失败,再来一次" << endl;
141         return -3;
142     }
143
144     //标定结果保存
145     FileStorage fs("caliResult.xml", FileStorage::WRITE);
146     fs << "cameraId" << cameraId;
147     fs << "intrinsic_parameters" << intrMatrix;
148     fs << "distortion_parametes" << distCoeffs;
149     fs.release();
150
151     return 0;
152 }

该程序有三个参数输入,基于命令行读入参数

-w    #标定板一个方向上的角点数

-h    #标定板另一个方向上的角点数

-s    #标定板上正方形的边长,默认为1

另,发表下-s参数设置的观点。之所以该参数在很多标定实例程序中设置为默认1,是因为该参数的改变确实是会影响到标定结果,但是不会影响到摄像头的矫正。因为标定和矫正类似一个逆运算过程,单位定义对其没有影响。

  • 实验结果

标定过程,

标定结果,

<?xml version="1.0"?>
<opencv_storage>
<cameraId>0</cameraId>
<intrinsic_parameters type_id="opencv-matrix"><rows>3</rows><cols>3</cols><dt>d</dt><data>7.7881772950073355e+002 0. 3.1562441595543476e+002 0.7.8624564811643825e+002 2.5630331974129393e+002 0. 0. 1.</data></intrinsic_parameters>
<distortion_parametes type_id="opencv-matrix"><rows>1</rows><cols>5</cols><dt>d</dt><data>-7.2660835182078581e-002 2.0765291395491934e+0005.9477659924542790e-004 -8.2981148319346263e-004-7.0307616798578119e+000</data></distortion_parametes>
</opencv_storage>

转载于:https://www.cnblogs.com/german-iris/p/5074602.html

双摄像头立体成像(二)-摄像头标定相关推荐

  1. 双摄像头立体成像(三)-畸变矫正与立体校正

    双摄像头立体成像(三)-畸变矫正与立体校正 畸变矫正是上一篇博文的遗留问题,当畸变系数和内外参数矩阵标定完成后,就应该进行畸变的矫正,以达到消除畸变的目的,此其一. 在该系列第一部分的博文中介绍的立体 ...

  2. 双摄像头立体成像(一)-成像原理

    刚刚拿到毕设题目,基于双摄像头的3D-Reconstruction.毕竟是毕设,先不论难易与否,还是毕业最重要.所以数字图像处理的学习博客暂时告一段落,恰巧前些天由于身体原因好长时间没有更新,停了这么 ...

  3. android后置双摄像头,双后置摄像头_手机Android频道-中关村在线

    处理器方面,HTC One M8装备的是一颗高通骁龙801系列四核心处理器,支持4G LTE网络,拥有四颗Krait 400微架构CPU,单颗CPU主频高达2.5GHz,集成Adreno 330 GP ...

  4. opencv基础:结构光立体成像原理及标定

    原文链接:https://zhuanlan.zhihu.com/p/78512354(建议参考原文链接) https://mp.weixin.qq.com/s?__biz=MzU1MjY4MTA1MQ ...

  5. linux驱动双摄像头,详解linux 摄像头驱动编写

    对于现代嵌入式设备,特别是手机来说,摄像头是很重要的一个设备.很多同学买手机,一看颜值,第二就看摄像头拍照如何.所以,从某个角度来说,摄像头是各个厂家主打的应用功能.那么,linux是如何支持摄像头的 ...

  6. linux motion 双摄像头,树莓派+motion 搭建摄像头监控系统

    motion是Linux下一款开源的摄像头监控软件,用命名行运行,只保存有运动物体的图像. sudo apt install motion sudo cp/etc/motion/motion.conf ...

  7. 双摄像头测距的OpenCV实现

    虽然最近注意力已经不可遏制地被神经科学.大脑记忆机制和各种毕业活动吸引过去了,但是还是觉得有必要把这段时间双目视觉方面的进展总结一下.毕竟从上一篇博文发表之后,很多同仁发E-mail来与我讨论,很多原 ...

  8. 双目摄像头立体成像(二)畸变矫正与立体校正

    一.立体校正的原因 **原因一:**当畸变系数和内外参数矩阵标定完成后,就应该进行畸变矫正,以达到消除畸变的目的. **原因二:**在立体成像原理中提到,要通过两幅图像估计物点的深度信息,就必须在两幅 ...

  9. 双眼可以测距和建立立体环境,双摄像头可以吗?

    编辑丨3D视觉工坊 点击进入->3D视觉工坊学习交流群 观点一 作者|robot9野生程序猿 https://www.zhihu.com/question/23418797/answer/395 ...

最新文章

  1. CS本科毕业生能拿到45万年薪?
  2. MDI接口原理图设计
  3. 《机器人与数字人:基于MATLAB的建模与控制》——2.2节李群和李代数
  4. linux内核之accept实现
  5. C#中代理的简单应用
  6. springmvc中@RequestMapping的使用
  7. aix ssh服务??
  8. Linux 性能监测:介绍
  9. Maven——安装(二)
  10. mysql temp table_新特性解读 | MySQL 8.0 Temptable 引擎介绍
  11. Python3迅雷vip账号批量抓取导入excel中
  12. 计算机设计大赛软件开发类作品填写模板
  13. SpringBoot中使用MyBatis-Plus是如何解决Invalid bound statement (not found)这个异常的
  14. 微信 语音转文字 java,微信语音转文字怎么操作?手把手教你,一秒钟搞定!
  15. 192.168.0.1登录入口
  16. 华为mstp配置实例
  17. 浙里办完整开发流程(仅前端)
  18. 一个瑞典游戏工作室决定离开索尼,之前和之后都发生了什么?
  19. 【NLP】第4章 从头开始预训练 RoBERTa 模型
  20. 《PyTorch深度学习实践》 课堂笔记 Lesson7 神经网络多维特征输入的原理推导与实现

热门文章

  1. shell中各种括号()、(())、[]、[[]]、{}的作用和区别
  2. 【JAVAFX 构建中国地图2021最新版】
  3. django mysql 教程_Django 入门教程
  4. 智能资产构建去中心化的资产管理系统
  5. IPGuard客户端冲突处理方法
  6. 监听浏览器tab切换
  7. 87、固体燃烧的形式
  8. C++核心编程(二)
  9. 美国SigmasTek泰克蓄电池特点-VRLA蓄电池及其应用
  10. 2、cas4.0 单点登录 之 cas-client