原文:
http://blog.csdn.net/u010784534/article/details/50474371
先看一张鱼眼相机拍摄出来的结果:
这与普通镜头的成像模型没有区别。两者之间的区别主要体现在畸变系数,鱼眼相机的畸变系数为{ k1,k2,k3,k4 },畸变系数不同,就导致鱼眼相机的投影关系也发生了变化,主要变化发生在考虑畸变情况下的投影关系转化:
设(X,Y,Z)为空间中一个三维点,它在成像平面内的成像坐标为(u,v),在考虑畸变的情况下,
a=xc/zc,b=yc/zc
r2=a2+b2
θ=atan(r)
θ′=θ(1+k1θ2+k2θ4+k3θ6+k4θ8)
x′=(θ′/r)xc
y′=(θ′/r)yc
u=fxx′+cx
v=fyy′+cy
从图中可以看出很明显的畸变。对鱼眼相机标定,有时候也可以用普通相机的标定方法对其进行标定,但是却不能保证去畸变后的效果是最好的。因此对于Gopro等鱼眼镜头拍摄出来的图像去畸变,最好的方法就是采用鱼眼相机标定方法进行标定。
鱼眼相机模型
鱼眼相机的内参模型依然可以表示为:
⎧⎩⎨⎪⎪fx000fy0cxcy1⎫⎭⎬⎪⎪
这与普通镜头的成像模型没有区别。两者之间的区别主要体现在畸变系数,鱼眼相机的畸变系数为{ k1,k2,k3,k4 },畸变系数不同,就导致鱼眼相机的投影关系也发生了变化,主要变化发生在考虑畸变情况下的投影关系转化:
设(X,Y,Z)为空间中一个三维点,它在成像平面内的成像坐标为(u,v),在考虑畸变的情况下,
⎧⎩⎨⎪⎪xcyczc⎫⎭⎬⎪⎪=R∗⎧⎩⎨⎪⎪XYZ⎫⎭⎬⎪⎪+T
a=xc/zc,b=yc/zc
r2=a2+b2
θ=atan(r)
θ′=θ(1+k1θ2+k2θ4+k3θ6+k4θ8)
x′=(θ′/r)xc
y′=(θ′/r)yc
u=fxx′+cx
v=fyy′+cy
OpenCV实现鱼眼相机标定
利用opencv实现鱼眼相机的标定和普通相机标定的标定流程基本一致,具体流程如下:
- 检测角点
cv::findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int
flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE}
获得棋盘标定板的角点位置,使用
cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone,
获取角点更精细的检测结果
TermCriteria criteria) - 初始化标定板上角点的三维坐标
- 开始标定
double fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
const Size& image_size, InputOutputArray K, InputOutputArray D,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0,
TermCriteria criteria=TermCriteria(TermCriteria::COUNT + TermCriteria::
EPS, 100, DBL_EPSILON))
注意:K,D 分别表示内参矩阵和畸变系数向量,在定义时要定义为double型,这里推荐使用Matx33d和Vec4d数据类型,更为方便简单。objectPoints,imagePoints可以是float型,也可以是double型,但是再stereorectify中需要时double型。flags的可选项有很多,其中需要注意的是必须要指定CALIB_FIX_SKEW,代表求解时假设内参中 fx=fy .
4.评定误差(可选项)
以上就是鱼眼相机标定的基本流程,部分代码片段如下:
<code class="hljs cpp has-numbering"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i != image_count; i++) { <span class="hljs-built_in">cout</span> << <span class="hljs-string">"Frame #"</span> << i + <span class="hljs-number">1</span> << <span class="hljs-string">"..."</span> << endl; <span class="hljs-built_in">string</span> image_Name; <span class="hljs-built_in">stringstream</span> stream; stream << (i + startNum); stream >> image_Name; image_Name = path_ChessboardImage + image_Name + <span class="hljs-string">".jpg"</span>; cv::Mat image = imread(image_Name); Mat image_gray; cvtColor(image, image_gray, CV_RGB2GRAY); <span class="hljs-stl_container"><span class="hljs-built_in">vector</span><Point2f></span> corners; <span class="hljs-keyword">bool</span> patternFound = findChessboardCorners(image_gray, board_size, corners, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK); <span class="hljs-keyword">if</span> (!patternFound || fullcornersNum != corners.size()) { <span class="hljs-built_in">cout</span> << <span class="hljs-string">"can not find chessboard corners!\n"</span>; <span class="hljs-keyword">continue</span>; } <span class="hljs-keyword">else</span> { cornerSubPix(image_gray, corners, Size(<span class="hljs-number">11</span>, <span class="hljs-number">11</span>), Size(-<span class="hljs-number">1</span>, -<span class="hljs-number">1</span>), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, <span class="hljs-number">30</span>, <span class="hljs-number">0.1</span>)); count = count + corners.size(); corners_Seq.push_back(corners); successImageNum = successImageNum + <span class="hljs-number">1</span>; image_Seq.push_back(image); } } <span class="hljs-comment">/************************************************************************ 摄像机定标 *************************************************************************/</span> <span class="hljs-stl_container"><span class="hljs-built_in">vector</span><<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><Point3f></span>></span> object_Points; <span class="hljs-comment">/**** 保存定标板上角点的三维坐标 ****/</span> Mat image_points = Mat(<span class="hljs-number">1</span>, count, CV_32FC2, Scalar::all(<span class="hljs-number">0</span>)); <span class="hljs-comment">/***** 保存提取的所有角点 *****/</span> <span class="hljs-stl_container"><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>></span> point_counts; <span class="hljs-comment">/* 初始化定标板上角点的三维坐标 */</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> t = <span class="hljs-number">0</span>; t<successImageNum; t++) { <span class="hljs-stl_container"><span class="hljs-built_in">vector</span><Point3f></span> tempPointSet; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i<board_size.height; i++) { <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j<board_size.width; j++) { <span class="hljs-comment">/* 假设定标板放在世界坐标系中z=0的平面上 */</span> Point3f tempPoint; tempPoint.x = i*square_size.width; tempPoint.y = j*square_size.height; tempPoint.z = <span class="hljs-number">0</span>; tempPointSet.push_back(tempPoint); } } object_Points.push_back(tempPointSet); } <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i< successImageNum; i++) { point_counts.push_back(board_size.width*board_size.height); } <span class="hljs-comment">/* 开始定标 */</span> Size image_size = image_Seq[<span class="hljs-number">0</span>].size(); cv::Matx33d intrinsic_matrix; <span class="hljs-comment">/***** 摄像机内参数矩阵 ****/</span> cv::Vec4d distortion_coeffs; <span class="hljs-comment">/* 摄像机的4个畸变系数:k1,k2,k3,k4*/</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::Vec3d></span> rotation_vectors; <span class="hljs-comment">/* 每幅图像的旋转向量 */</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::Vec3d></span> translation_vectors; <span class="hljs-comment">/* 每幅图像的平移向量 */</span> <span class="hljs-keyword">int</span> flags = <span class="hljs-number">0</span>; flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; flags |= cv::fisheye::CALIB_CHECK_COND; flags |= cv::fisheye::CALIB_FIX_SKEW; fisheye::calibrate(object_Points, corners_Seq, image_size, intrinsic_matrix, distortion_coeffs, rotation_vectors, translation_vectors, flags, cv::TermCriteria(<span class="hljs-number">3</span>, <span class="hljs-number">20</span>, <span class="hljs-number">1e-6</span>));</code>
标定结果: