OpenCV中initUndistortRectifyMap ()函数与十四讲中去畸变公式的区别探究

文章目录

      • 1.十四讲中的去畸变公式
      • 2. OpenCV中的去畸变公式
      • 3. 4个参数和8个参数之间的区别
      • 4.initUndistortRectifyMap()函数源码

最近在使用OpenCV对鱼眼相机图像去畸变时发现一个问题,基于针孔模型去畸变时所使用的参数和之前十四讲以及视觉SLAM中的畸变系数有一点不一样。

1.十四讲中的去畸变公式

首先是十四讲或者视觉SLAM中的方法,针孔模型的畸变系数为[k1, k2, p1, p2],使用以下去畸变公式计算:

在这里插入图片描述

2. OpenCV中的去畸变公式

在OpenCV中可以通过initUndistortRectifyMap()函数获得原始图像和矫正图像之间的映射表,然后remap()函数根据映射表对整个图像进行映射处理实现去畸变。

 cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat(), K, imageSize, CV_16SC2, map1, map2);cv::remap(raw_image, undistortImg, map1, map2, cv::INTER_LINEAR, cv::BORDER_CONSTANT);

具体实现可以见文章《对鱼眼相机图像进行去畸变处理》

initUndistortRectifyMap()函数的声明如下:

void cv::initUndistortRectifyMap	
(	InputArray 	cameraMatrix,     // 原相机内参矩阵InputArray 	distCoeffs,       // 原相机畸变参数InputArray 	R,                // 可选的修正变换矩阵 InputArray 	newCameraMatrix,  // 新相机内参矩阵Size 	        size,             // 去畸变后图像的尺寸int 	        m1type,           // 第一个输出的映射(map1)的类型,CV_32FC1 or CV_16SC2OutputArray 	map1,             // 第一个输出映射OutputArray 	map2              // 第二个输出映射
)

有意思的是,这里的相机畸变参数是可选的,可以是4个参数k1, k2, p1, p2,可以是5个参数k1, k2, p1, p2, k3,也可以是8个参数k1, k2, p1, p2, k3, k4, k5, k6

后来检索了一下initUndistortRectifyMap()函数中的畸变公式,如下:
在这里插入图片描述

推导过程的核心是:
在这里插入图片描述
k3, k4, k5, k6以及s1, s2, s3, s4均为0的时候该去畸变公式和十四讲中的公式就一样了,即十四讲中的去畸变公式是该公式的一个简略版。

3. 4个参数和8个参数之间的区别

已经说过,initUndistortRectifyMap()函数中的去畸变参数可以是4个参数k1, k2, p1, p2,可以是5个参数k1, k2, p1, p2, k3,也可以是8个参数k1, k2, p1, p2, k3, k4, k5, k6

对于普通的广角相机图像,径向畸变和切向畸变一般都比较小,所以仅使用k1, k2, p1, p2就可以完成去畸变过程,对应十四讲中的去畸变公式。

对于鱼眼相机,一般会存在比较大的径向畸变,所以需要更高阶的径向畸变系数k3, k4, k5, k6,至于为什么是 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 1 + k 4 r 2 + k 5 r 4 + k 6 r 6 \frac{1+k_1r^2+k_2r^4+k_3r^6}{1+k_4r^2+k_5r^4+k_6r^6} 1+k4r2+k5r4+k6r61+k1r2+k2r4+k3r6这种比值形式,暂时为找到公式的设计原理,应该是基于对径向畸变的某种考量进行的设计。

根据标定工具和相机模型的不同,获取的鱼眼相机畸变系数可能有多种形式,需要知道的是都可以在OpenCV去畸变函数中使用。而且有时通过标定得到完整的8个去畸变参数k1, k2, p1, p2, k3, k4, k5, k6,这就使得在调用OpenCV函数去畸变事需要使用完整的参数,只使用k1, k2, p1, p2会得到失败的结果。

4.initUndistortRectifyMap()函数源码

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);//A为相机内参Mat_<double> A = Mat_<double>(cameraMatrix), Ar;//Ar 为摄像机坐标参数if( newCameraMatrix.data )Ar = Mat_<double>(newCameraMatrix);elseAr = getDefaultNewCameraMatrix( A, size, true );//R  为旋转矩阵if( matR.data )R = Mat_<double>(matR);//distCoeffs为畸变矩阵if( distCoeffs.data )distCoeffs = Mat_<double>(distCoeffs);else{distCoeffs.create(8, 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));//摄像机坐标系第四列参数  旋转向量转为旋转矩阵Mat_<double> iR = (Ar.colRange(0,3)*R).inv(DECOMP_LU);//ir IR矩阵的指针const double* ir = &iR(0,0);//获取相机的内参 u0  v0 为主坐标点   fx fy 为焦距double u0 = A(0, 2),  v0 = A(1, 2);double fx = A(0, 0),  fy = A(1, 1);CV_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));if( distCoeffs.rows != 1 && !distCoeffs.isContinuous() )distCoeffs = distCoeffs.t();//畸变参数计算double k1 = ((double*)distCoeffs.data)[0];double k2 = ((double*)distCoeffs.data)[1];double p1 = ((double*)distCoeffs.data)[2];double p2 = ((double*)distCoeffs.data)[3];double k3 = distCoeffs.cols + distCoeffs.rows - 1 >= 5 ? ((double*)distCoeffs.data)[4] : 0.;double k4 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[5] : 0.;double k5 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[6] : 0.;double k6 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[7] : 0.;//图像高度for( int i = 0; i < size.height; i++ ){//映射矩阵map1 float* m1f = (float*)(map1.data + map1.step*i);//映射矩阵map2float* m2f = (float*)(map2.data + map2.step*i);short* m1 = (short*)m1f;ushort* m2 = (ushort*)m2f;//摄像机参数矩阵最后一列向量转换成的3*3矩阵参数double _x = i*ir[1] + ir[2];double _y = i*ir[4] + ir[5];double _w = i*ir[7] + ir[8];//图像宽度for( int j = 0; j < size.width; j++, _x += ir[0], _y += ir[3], _w += ir[6] ){//获取摄像机坐标系第四列参数double w = 1./_w, x = _x*w, y = _y*w;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 u = fx*(x*kr + p1*_2xy + p2*(r2 + 2*x2)) + u0;double v = fy*(y*kr + p1*(r2 + 2*y2) + p2*_2xy) + v0;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;}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/102904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue-2.3v-model原理

原理&#xff1a;v-model本质上是一个语法糖&#xff0c;例如应用在输入框上&#xff0c;就是value属性和input事件的合写。 作用&#xff1a;提供数据的双向绑定 1&#xff09;数据变&#xff0c;视图跟着变:value 2&#xff09;视图变&#xff0c;数据跟着变input 注意&a…

把短信验证码储存在Redis

校验短信验证码 接着上一篇博客https://blog.csdn.net/qq_42981638/article/details/94656441&#xff0c;成功实现可以发送短信验证码之后&#xff0c;一般可以把验证码存放在redis中&#xff0c;并且设置存放时间&#xff0c;一般短信验证码都是1分钟或者90s过期&#xff0c;…

伦敦金的交易时间究竟多长?

接触过伦敦金交易的投资者&#xff0c;应该都知道自己根本不用担心市场上没有交易的机会&#xff0c;因为它全天的交易时间长达20多个小时&#xff0c;也就是在每一个正常的交易日&#xff0c;除去交易平台中途短暂的系统维护时间&#xff0c;投资者几乎全天都可以做盘。 伦敦金…

Maven导入程序包jakarta.servlet,但显示不存在

使用前提&#xff1a;&#xff08;Tomcat10版本&#xff09;已知tomcat10版本之后&#xff0c;使用jakart.servlet。而tomcat9以及之前使用javax.servlet。 问题描述&#xff1a;在maven仓库有导入了Jakarta程序包&#xff0c;但是界面仍然显示是javax。&#xff08;下图&…

Unity实现设计模式——适配器模式

Unity实现设计模式——适配器模式 适配器模式又称为变压器模式、包装模式&#xff08;Wrapper&#xff09; 将一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 在一个在役的项目期望在原有接口的基础…

一文带你上手自动化测试中的PO模式!

在UI的自动化测试中&#xff0c;我们需要把测试使用到的数据分离到文件中&#xff0c;如果单纯的写在我们的测试模块里面&#xff0c;不是一个好的设计&#xff0c;所以不管是什么类型的自动化测试&#xff0c;都是需要把数据分离出来的。当然分离到具体的文件里面&#xff0c;…

智能井盖传感器:数智赋能让城市管理更智慧

智能井盖传感器&#xff1a;数智赋能让城市管理更智慧 在城市化快速发展的今天&#xff0c;保护和增强城市基础设施生命线的需求至关重要。而井盖作为守护城市地下空间的安全门&#xff0c;其智能化管理与城市生命线安全工程建设息息相关。在这篇文章中将为大家详细介绍智能井…

OpenCV级联分类器识别车辆实践笔记

1. OpenCV 级联分类器的基本原理 基于Haar特征的级联分类器的目标检测是Paul Viola和Michael Jones在2001年的论文中提出的一种有效的目标检测方法。这是一种基于机器学习的方法&#xff0c;从大量的正面和负面图像中训练级联函数。然后用它来检测其他图像中的物体。 Haar特征…

CTF Misc(3)流量分析基础以及原理

前言 流量分析在ctf比赛中也是常见的题目&#xff0c;参赛者通常会收到一个网络数据包的数据集&#xff0c;这些数据包记录了网络通信的内容和细节。参赛者的任务是通过分析这些数据包&#xff0c;识别出有用的信息&#xff0c;例如登录凭据、加密算法、漏洞利用等等 工具安装…

共生与共享:线程与进程的关系

&#x1f30d;前言 在计算机科学和操作系统领域&#xff0c;线程&#xff08;Thread&#xff09;和进程&#xff08;Process&#xff09;是两个关键概念。它们之间存在密切的关系&#xff0c;但又有着明显的区别。本文将深入探讨线程和进程之间的关系&#xff0c;以及它们在并…

使用Perl脚本编写爬虫程序的一些技术问题解答

网络爬虫是一种强大的工具&#xff0c;用于从互联网上收集和提取数据。Perl 作为一种功能强大的脚本语言&#xff0c;提供了丰富的工具和库&#xff0c;使得编写的爬虫程序变得简单而灵活。在使用的过程中大家会遇到一些问题&#xff0c;本文将通过问答方式&#xff0c;解答一些…

telnet 连接闪退

目录 问题查找问题解决参考 问题 在使用 telnet 连接板子时&#xff0c;出现了 telnet 界面一闪而逝的情况。正常板子开机&#xff0c;查看其存在 telnetd 进程&#xff0c;而使用 telnet 后&#xff0c;登录界面一闪而逝&#xff0c;板子上的 telnetd 进程消失。 查找问题 …

python psutil库之——获取网络信息(网络接口信息、网络配置信息、以太网接口、ip信息、ip地址信息)

文章目录 使用Python psutil库获取网络信息安装psutil库获取网络连接信息查看所有网络连接过滤特定状态的连接 获取网络接口信息获取网络IO统计信息实例1实例2 总结 使用Python psutil库获取网络信息 Python的psutil库是一个跨平台库&#xff0c;能够方便地获取系统使用情况和…

docker-compose Install hfish

前言hfish HFish是一款社区型免费蜜罐,侧重企业安全场景,从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发,为用户提供可独立操作且实用的功能,通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁情报领域的能力。 HFish具有超过40种蜜罐环境、提供免费…

PTA 7-5 令人抓狂的四则运算

题目 曾记否&#xff0c;我们小学时&#xff0c;遇到这种四则运算&#xff0c;心情是抓狂的&#xff1a; 那么当我们学会使用计算机&#xff0c;自然是要程序去完成这个工作啦~ 现在请对输入的四则运算求值。注意&#xff1a; 四则运算表达式必定包含运算数&#xff0c;还可能…

【MySQL入门到精通-黑马程序员】MySQL基础篇-DCL

文章目录 前言一、DCL-介绍二、DCL-管理用户二、DCL-权限控制总结 前言 本专栏文章为观看黑马程序员《MySQL入门到精通》所做笔记&#xff0c;课程地址在这。如有侵权&#xff0c;立即删除。 一、DCL-介绍 DCL英文全称是Data Control Language&#xff08;数据控制语言&#x…

基于FPGA的数字时钟系统设计

在FPGA的学习中&#xff0c;数字时钟是一个比较基础的实验案例&#xff0c;通过该实验可以更好的锻炼初学者的框架设计能力以及逻辑思维能力&#xff0c;从而打好坚实的基本功&#xff0c;接下来就开始我们的学习吧&#xff01; 1.数码管介绍 数码管通俗理解就是将8个LED(包含…

使用 L293D 电机驱动器 IC 和 Arduino 控制直流电机

如果您打算组装新的机器人朋友&#xff0c;您最终会想要学习如何控制直流电机。控制直流电机最简单且经济的方法是将 L293D 电机驱动器 IC 与 Arduino 连接。它可以控制两个直流电机的速度和旋转方向。 此外&#xff0c;它还可以控制单极步进电机&#xff08;如 28BYJ-48&#…

GLTF纹理贴图工具让模型更逼真

1、如何制作逼真的三维模型&#xff1f; 要使三维模型看起来更加逼真&#xff0c;可以考虑以下几个方面&#xff1a; 高质量纹理&#xff1a;使用高分辨率的纹理贴图可以增强模型的细节和真实感。选择适合模型的高质量纹理图像&#xff0c;并确保纹理映射到模型上的UV坐标正确…

栈的运行算法

一&#xff0c;顺序栈的静态分配 二&#xff0c;顺序栈的动态分配 #include<stdio.h> #include<stdlib.h> #define initsize 5 #define incresize 5typedef struct Sqstack{int *base;int *top;int stacksize; }Sqstack;void InitStack(Sqstack *s){(*s).base(int…