• 转载请注明作者和出处:http://blog.csdn.net/u011475210
  • 代码地址:https://github.com/WordZzzz/fisheye_calibration
  • 软件版本:VS2013+OPENCV2.4.13 OR VS2013+OPENCV3.4.0
  • 编  者:WordZzzz

  • 我的代码
  • 可供参考资料
    • FishEye模型的畸变校正
    • 普通相机模型的畸变校正

我的代码

  最近在整理自己以前做过的一些东西,这是基于opencv的鱼眼摄像头畸变校正程序的github地址。

其中:

  • normal_calibrate:基于OPENCV2与OPENCV3通用的函数实现,可实现USB摄像头实时畸变校正;
  • fishey_calibrate:基于OPENCV3独有的fishyey结构体实现,可实现USB摄像头实时畸变校正;
  • fishey_calibrate_img:基于OPENCV3独有的fishyey结构体实现,可实现单张图片畸变校正;

  opencv1.0 2.0版只有一种摄像机标定模型,就是普通的小孔成像模型,在cv::空间下。而从opencv3.0开始,新增了一种鱼眼相机标定模型,在fisheye::空间下。两种模型的主要区别在于像与物的投影关系不同,具体的文献资料依然是数不胜数,这里就不赘述。根据opencv官方文档的建议,在畸变程度较大的广角镜头(比如:鱼眼镜头)上进行摄像机标定和畸变校正,最好是用fisheye模型,该模型在图像边缘畸变程度很大的地方比普通相机模型的效果要好。

  当然,还是要贴上官方文档的:

  • Camera calibration With OpenCV2;
  • Camera calibration With OpenCV3;

华丽的分割线


可供参考资料

  以下是在写这篇博客的时候偶然发现的对opencv两个版本标定过程的讲解,这里直接copy过来并稍微做了一下排版,因为自己忙于为找工作做准备,实在是没时间自己整理了。原文链接。

  图像算法中会经常用到摄像机的畸变校正,有必要总结分析OpenCV中畸变校正方法,其中包括普通针孔相机模型和鱼眼相机模型fisheye两种畸变校正方法。

  普通相机模型畸变校正函数针对OpenCV中的cv::initUndistortRectifyMap(),鱼眼相机模型畸变校正函数对应OpenCV中的cv::fisheye::initUndistortRectifyMap()。两种方法算出映射Mapx和Mapy后,统一用cv::Remap()函数进行插值得到校正后的图像。

FishEye模型的畸变校正。

  方便起见,直接贴出OpenCV源码,我在里面加了注释说明。建议参考OpenCV官方文档看畸变模型原理会更清楚。

  简要流程就是:

  • 1.求内参矩阵的逆,由于摄像机坐标系的三维点到二维图像平面,需要乘以旋转矩阵R和内参矩阵K。那么反向投影回去则是二维图像坐标乘以 K*R的逆矩阵。

  • 2.将目标图像中的每一个像素点坐标(j,i),乘以1中求出的逆矩阵iR,转换到摄像机坐标系(_x,_y,_w),并归一化得到z=1平面下的三维坐标(x,y,1)。

  • 3.求出平面模型下像素点对应鱼眼半球模型下的极坐标(r, theta)。

  • 4.利用鱼眼畸变模型求出拥有畸变时像素点对应的theta_d。

  • 5.利用求出的theta_d值将三维坐标点重投影到二维图像平面得到(u,v),(u,v)即为目标图像对应的畸变图像中像素点坐标。

  • 6.使用cv::Remap()函数,根据mapx,mapy取出对应坐标位置的像素值赋值给目标图像,一般采用双线性插值法,得到畸变校正后的目标图像。

#include <opencv2\opencv.hpp>void cv::fisheye::initUndistortRectifyMap( InputArray K, InputArray D, InputArray R, InputArray P,const cv::Size& size, int m1type, OutputArray map1, OutputArray map2 )
{CV_Assert( m1type == CV_16SC2 || m1type == CV_32F || m1type <=0 );map1.create( size, m1type <= 0 ? CV_16SC2 : m1type );map2.create( size, map1.type() == CV_16SC2 ? CV_16UC1 : CV_32F );CV_Assert((K.depth() == CV_32F || K.depth() == CV_64F) && (D.depth() == CV_32F || D.depth() == CV_64F));CV_Assert((P.empty() || P.depth() == CV_32F || P.depth() == CV_64F) && (R.empty() || R.depth() == CV_32F || R.depth() == CV_64F));CV_Assert(K.size() == Size(3, 3) && (D.empty() || D.total() == 4));CV_Assert(R.empty() || R.size() == Size(3, 3) || R.total() * R.channels() == 3);CV_Assert(P.empty() || P.size() == Size(3, 3) || P.size() == Size(4, 3));//从内参矩阵K中取出归一化焦距fx,fy; cx,cycv::Vec2d f, c;if (K.depth() == CV_32F){Matx33f camMat = K.getMat();f = Vec2f(camMat(0, 0), camMat(1, 1));c = Vec2f(camMat(0, 2), camMat(1, 2));}else{Matx33d camMat = K.getMat();f = Vec2d(camMat(0, 0), camMat(1, 1));c = Vec2d(camMat(0, 2), camMat(1, 2));}//从畸变系数矩阵D中取出畸变系数k1,k2,k3,k4Vec4d k = Vec4d::all(0);if (!D.empty())k = D.depth() == CV_32F ? (Vec4d)*D.getMat().ptr<Vec4f>(): *D.getMat().ptr<Vec4d>();//旋转矩阵RR转换数据类型为CV_64F,如果不需要旋转,则RR为单位阵cv::Matx33d RR  = cv::Matx33d::eye();if (!R.empty() && R.total() * R.channels() == 3){cv::Vec3d rvec;R.getMat().convertTo(rvec, CV_64F);RR = Affine3d(rvec).rotation();}else if (!R.empty() && R.size() == Size(3, 3))R.getMat().convertTo(RR, CV_64F);//新的内参矩阵PP转换数据类型为CV_64Fcv::Matx33d PP = cv::Matx33d::eye();if (!P.empty())P.getMat().colRange(0, 3).convertTo(PP, CV_64F);//关键一步:新的内参矩阵*旋转矩阵,然后利用SVD分解求出逆矩阵iR,后面用到cv::Matx33d iR = (PP * RR).inv(cv::DECOMP_SVD);//反向映射,遍历目标图像所有像素位置,找到畸变图像中对应位置坐标(u,v),并分别保存坐标(u,v)到mapx和mapy中for( int i = 0; i < size.height; ++i){float* m1f = map1.getMat().ptr<float>(i);float* m2f = map2.getMat().ptr<float>(i);short*  m1 = (short*)m1f;ushort* m2 = (ushort*)m2f;//二维图像平面坐标系->摄像机坐标系double _x = i*iR(0, 1) + iR(0, 2),_y = i*iR(1, 1) + iR(1, 2),_w = i*iR(2, 1) + iR(2, 2);for( int j = 0; j < size.width; ++j){//归一化摄像机坐标系,相当于假定在Z=1平面上double x = _x/_w, y = _y/_w;//求鱼眼半球体截面半径rdouble r = sqrt(x*x + y*y);//求鱼眼半球面上一点与光心的连线和光轴的夹角Thetadouble theta = atan(r);//畸变模型求出theta_d,相当于有畸变的角度值double theta2 = theta*theta, theta4 = theta2*theta2, theta6 = theta4*theta2, theta8 = theta4*theta4;double theta_d = theta * (1 + k[0]*theta2 + k[1]*theta4 + k[2]*theta6 + k[3]*theta8);//利用有畸变的Theta值,将摄像机坐标系下的归一化三维坐标,重投影到二维图像平面,得到(j,i)对应畸变图像中的(u,v)double scale = (r == 0) ? 1.0 : theta_d / r;double u = f[0]*x*scale + c[0];double v = f[1]*y*scale + c[1];//保存(u,v)坐标到mapx,mapyif( m1type == CV_16SC2 ){int iu = cv::saturate_cast<int>(u*cv::INTER_TAB_SIZE);int iv = cv::saturate_cast<int>(v*cv::INTER_TAB_SIZE);m1[j*2+0] = (short)(iu >> cv::INTER_BITS);m1[j*2+1] = (short)(iv >> cv::INTER_BITS);m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1)));}else if( m1type == CV_32FC1 ){m1f[j] = (float)u;m2f[j] = (float)v;}//这三条语句是上面 ”//二维图像平面坐标系->摄像机坐标系“的一部分,是矩阵iR的第一列,这样写能够简化计算_x += iR(0, 0);_y += iR(1, 0);_w += iR(2, 0);}}
}

普通相机模型的畸变校正

  同样建议参考OpenCV官方文档阅读代码。

  主要流程和上面Fisheye模型差不多,只有第4部分的畸变模型不一样,普通相机的畸变模型如下:

  同样把源代码贴上,并加上注解:

#include <opencv2\opencv.hpp>void cv::initUndistortRectifyMap( InputArray _cameraMatrix, InputArray _distCoeffs,InputArray _matR, InputArray _newCameraMatrix,Size size, int m1type, OutputArray _map1, OutputArray _map2 )
{Mat cameraMatrix = _cameraMatrix.getMat(), distCoeffs = _distCoeffs.getMat();Mat matR = _matR.getMat(), newCameraMatrix = _newCameraMatrix.getMat();if( m1type <= 0 )m1type = CV_16SC2;CV_Assert( m1type == CV_16SC2 || m1type == CV_32FC1 || m1type == CV_32FC2 );_map1.create( size, m1type );Mat map1 = _map1.getMat(), map2;if( m1type != CV_32FC2 ){_map2.create( size, m1type == CV_16SC2 ? CV_16UC1 : CV_32FC1 );map2 = _map2.getMat();}else_map2.release();Mat_<double> R = Mat_<double>::eye(3, 3);Mat_<double> A = Mat_<double>(cameraMatrix), Ar;if( !newCameraMatrix.empty() )Ar = Mat_<double>(newCameraMatrix);elseAr = getDefaultNewCameraMatrix( A, size, true );if( !matR.empty() )R = Mat_<double>(matR);if( !distCoeffs.empty() )distCoeffs = Mat_<double>(distCoeffs);else{distCoeffs.create(14, 1, CV_64F);distCoeffs = 0.;}CV_Assert( A.size() == Size(3,3) && A.size() == R.size() );CV_Assert( Ar.size() == Size(3,3) || Ar.size() == Size(4, 3));//LU分解求新的内参矩阵Ar与旋转矩阵R乘积的逆矩阵iRMat_<double> iR = (Ar.colRange(0,3)*R).inv(DECOMP_LU);const double* ir = &iR(0,0);//从旧的内参矩阵中取出光心位置u0,v0,和归一化焦距fx,fydouble u0 = A(0, 2),  v0 = A(1, 2);double fx = A(0, 0),  fy = A(1, 1);//尼玛14个畸变系数,不过大多用到的只有(k1,k2,p1,p2),最多加一个k3,用不到的置为0CV_Assert( distCoeffs.size() == Size(1, 4) || distCoeffs.size() == Size(4, 1) ||distCoeffs.size() == Size(1, 5) || distCoeffs.size() == Size(5, 1) ||distCoeffs.size() == Size(1, 8) || distCoeffs.size() == Size(8, 1) ||distCoeffs.size() == Size(1, 12) || distCoeffs.size() == Size(12, 1) ||distCoeffs.size() == Size(1, 14) || distCoeffs.size() == Size(14, 1));if( distCoeffs.rows != 1 && !distCoeffs.isContinuous() )distCoeffs = distCoeffs.t();const double* const distPtr = distCoeffs.ptr<double>();double k1 = distPtr[0];double k2 = distPtr[1];double p1 = distPtr[2];double p2 = distPtr[3];double k3 = distCoeffs.cols + distCoeffs.rows - 1 >= 5 ? distPtr[4] : 0.;double k4 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[5] : 0.;double k5 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[6] : 0.;double k6 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[7] : 0.;double s1 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[8] : 0.;double s2 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[9] : 0.;double s3 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[10] : 0.;double s4 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[11] : 0.;double tauX = distCoeffs.cols + distCoeffs.rows - 1 >= 14 ? distPtr[12] : 0.;double tauY = distCoeffs.cols + distCoeffs.rows - 1 >= 14 ? distPtr[13] : 0.;//tauX,tauY这个是什么梯形畸变,用不到的话matTilt为单位阵// Matrix for trapezoidal distortion of tilted image sensorcv::Matx33d matTilt = cv::Matx33d::eye();cv::detail::computeTiltProjectionMatrix(tauX, tauY, &matTilt);for( int i = 0; i < size.height; i++ ){float* m1f = map1.ptr<float>(i);float* m2f = map2.empty() ? 0 : map2.ptr<float>(i);short* m1 = (short*)m1f;ushort* m2 = (ushort*)m2f;//利用逆矩阵iR将二维图像坐标(j,i)转换到摄像机坐标系(_x,_y,_w)double _x = i*ir[1] + ir[2], _y = i*ir[4] + ir[5], _w = i*ir[7] + ir[8];for( int j = 0; j < size.width; j++, _x += ir[0], _y += ir[3], _w += ir[6] ){//摄像机坐标系归一化,令Z=1平面double w = 1./_w, x = _x*w, y = _y*w;//这一部分请看OpenCV官方文档,畸变模型部分double x2 = x*x, y2 = y*y;double r2 = x2 + y2, _2xy = 2*x*y;double kr = (1 + ((k3*r2 + k2)*r2 + k1)*r2)/(1 + ((k6*r2 + k5)*r2 + k4)*r2);double xd = (x*kr + p1*_2xy + p2*(r2 + 2*x2) + s1*r2+s2*r2*r2);double yd = (y*kr + p1*(r2 + 2*y2) + p2*_2xy + s3*r2+s4*r2*r2);//根据求取的xd,yd将三维坐标重投影到二维畸变图像坐标(u,v)cv::Vec3d vecTilt = matTilt*cv::Vec3d(xd, yd, 1);double invProj = vecTilt(2) ? 1./vecTilt(2) : 1;double u = fx*invProj*vecTilt(0) + u0;double v = fy*invProj*vecTilt(1) + v0;//保存u,v的值到Mapx,Mapy中if( m1type == CV_16SC2 ){int iu = saturate_cast<int>(u*INTER_TAB_SIZE);int iv = saturate_cast<int>(v*INTER_TAB_SIZE);m1[j*2] = (short)(iu >> INTER_BITS);m1[j*2+1] = (short)(iv >> INTER_BITS);m2[j] = (ushort)((iv & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (iu & (INTER_TAB_SIZE-1)));}else if( m1type == CV_32FC1 ){m1f[j] = (float)u;m2f[j] = (float)v;}else{m1f[j*2] = (float)u;m1f[j*2+1] = (float)v;}}}
}

希望对需要的人能有所帮助,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

鱼眼摄像头标定与畸变校正(双OPENCV版本)相关推荐

  1. 鱼眼摄像头标定与畸变校正(OPENCV版)

    转载请注明作者和出处:http://blog.csdn.net/u011475210 代码地址:https://github.com/WordZzzz/fisheye_calibration 软件版本 ...

  2. 手机广角相机标定和畸变校正

    摄像头或者相机会因为镜片的光学特性而发生有规律的变形或者畸变,包括桶型畸变,枕型畸变和线性畸变.普通相机的这些畸变十分轻微,人的肉眼几乎分辨不出,所以这时可以不需要校正.对于广角相机,鱼眼相机,由于视 ...

  3. 用OpenCV+Python对广角(包括鱼眼)镜头进行实时畸变校正

    用普通摄像头的标定方式(cv2.calibrateCamera()或cv2.calibrateCameraExtended())去获取摄像头内参(包括畸变参数)已不适用于广角或鱼眼摄像头了. 此文主要 ...

  4. OpenCV相机标定与畸变校正

    点击我爱计算机视觉标星,更快获取CVML新技术 本文转载自OpenCV学堂. OpenCV单目相机标定,图像畸变校正 相机标定定义与原理 01 在图像测量过程以及机器视觉应用中,为确定空间物体表面某点 ...

  5. python opencv 摄像头标定_(五)单目摄像头标定与畸变矫正(C++,opencv)

    本文将梳理一种单目摄像头标定和矫正的方法,在梳理的过程中,首先使用网上离线的图片数据跑通流程,然后接入自己的camera,手动采集标定图像,实时矫正相机的畸变,然后输出矫正后的图像.全文基于Openc ...

  6. 鱼眼镜头标定及畸变校正

    鱼眼摄像头畸变校正的方法: 棋盘矫正法 经纬度矫正法. 离线图片实现摄像头标定和矫正 1)Cmakelist 配置Opencv //要求cmake最低版本 cmake_minimum_required ...

  7. 广角镜头的标定、畸变校正和基于ROS的实时视频输出

    参考网址: 1.棋盘格标定 https://blog.csdn.net/u011475210/article/details/79185543 2.镜头参数调整 https://blog.csdn.n ...

  8. 【计算机视觉】摄像机标定与畸变校正

    任务1:利用OpenCV实现摄像机参数标定 设置好棋盘格参数(边长.行列数等),打印方形棋盘格并粘贴,拍摄多张标定图像: 估计标定参数,可视化标定结果.        任务2:根据标定参数进行畸变校正 ...

  9. 摄像头标定与畸变矫正

    1.基础知识 成像的过程实质上是几个坐标系的转换.首先空间中的一点由 世界坐标系 转换到 摄像机坐标系 ,然后再将其投影到成像平面 ( 图像物理坐标系 ) ,最后再将成像平面上的数据转换到图像平面 ( ...

最新文章

  1. AAAI22奖项公布,9000投稿选出唯一杰出论文,中科院自动化所获Distinguished论文奖...
  2. DropDownList--下拉菜单
  3. 机器学习基础专题:评估指标
  4. PHP仿金蝶云ERP进销存V8网络多仓版源码
  5. SAP License:SAP权限对象文集
  6. 运营商回应 5G 商用放号推迟;iPhone 或推出渐变色;GoLand 2019.2.1 发布 | 极客头条...
  7. 阿里云ECS Ubuntu 实例开放防火墙端口仍无法访问问题解决(安全组规则应用)...
  8. 部署git服务器(Windows Server 2008)
  9. 用户空间缺页异常pte_handle_fault()分析--(上)
  10. 31、栈的压入、弹出序列(Python)
  11. 好用的linux连接工具
  12. 微信小程序或微信网页里关注公众号
  13. python除法运算定律有哪些_运算定律有哪些
  14. 顶着“顶流”光环面对舆论质疑,为何威震天向左,玲娜贝儿向右?
  15. GUI 图片显示(SDL 多媒体开发库)——基于 rt-smart 微内核操作系统
  16. 二叉查找树的平衡(DSW)
  17. 数学建模Word排版——样式
  18. 读完《筚路蓝缕——世纪工程决策建设记述》
  19. 8月份,留言精选赠书!
  20. Spring-day1

热门文章

  1. pandas更改更换调整列位置,移动列
  2. python指定路径斜杠与反斜杠的问题
  3. 干翻一切的王者语言到底是谁?
  4. 将数据写入json文件,并且读取json文件中的数据
  5. mybatis-plus 存储布尔值true、false
  6. 关于通视域分析和日照分析
  7. oj-杭电 No Brainer
  8. POJ 2017 No Brainer(超级水题)
  9. 安卓:ListView组件实现微信通讯录效果(我的王者队友们)
  10. 瀚高股份吕新杰:创新开源双驱动 躬耕国产数据库