VINS-Mono/Fusion与OpenCV去畸变对比
VINS中没有直接使用opencv的去畸变函数,而是自己编写了迭代函数完成去畸变操作,主要是为了加快去畸变计算速度
本文对二者的结果精度和耗时进行了对比
VINS-Mono/Fusion与OpenCV去畸变对比
- 1 去畸变原理
- 2 代码实现
- 2.1 OpenCV去畸变
- 2.2 VINS去畸变
- 3 二者对比
1 去畸变原理
opencv去畸变操作由cv::undistortPoints实现
VINS去畸变由PinholeCamera::liftProjective实现(以针孔相机为例)
二者均采用了迭代求解,通过多次迭代逼近真值。其中cv::undistortPoints方法中默认迭代5次,并计算每次重投影误差是否小于阈值,VINS去畸变方法只设置了迭代8次。
二者均输入像素坐标,输出归一化坐标。
2 代码实现
2.1 OpenCV去畸变
opencv去畸变操作由cv::undistortPoints实现,代码在opencv-3.4.13/modules/imgproc/src
undistortPoints首先处理了输入参数,主要实现部分调用cvUndistortPointsInternal
void undistortPoints( InputArray _src, OutputArray _dst,InputArray _cameraMatrix, InputArray _distCoeffs,InputArray _Rmat, InputArray _Pmat, TermCriteria criteria)
void undistortPoints( InputArray _src, OutputArray _dst,InputArray _cameraMatrix,InputArray _distCoeffs,InputArray _Rmat,InputArray _Pmat,TermCriteria criteria)
{Mat src = _src.getMat(), cameraMatrix = _cameraMatrix.getMat();Mat distCoeffs = _distCoeffs.getMat(), R = _Rmat.getMat(), P = _Pmat.getMat();int npoints = src.checkVector(2), depth = src.depth();if (npoints < 0)src = src.t();npoints = src.checkVector(2);CV_Assert(npoints >= 0 && src.isContinuous() && (depth == CV_32F || depth == CV_64F));if (src.cols == 2)src = src.reshape(2);_dst.create(npoints, 1, CV_MAKETYPE(depth, 2), -1, true);Mat dst = _dst.getMat();CvMat _csrc = cvMat(src), _cdst = cvMat(dst), _ccameraMatrix = cvMat(cameraMatrix);CvMat matR, matP, _cdistCoeffs, *pR=0, *pP=0, *pD=0;if( !R.empty() )pR = &(matR = cvMat(R));if( !P.empty() )pP = &(matP = cvMat(P));if( !distCoeffs.empty() )pD = &(_cdistCoeffs = cvMat(distCoeffs));cvUndistortPointsInternal(&_csrc, &_cdst, &_ccameraMatrix, pD, pR, pP, criteria);
}
static void cvUndistortPointsInternal( const CvMat* _src, CvMat* _dst, const CvMat* _cameraMatrix, const CvMat* _distCoeffs, const CvMat* matR, const CvMat* matP, cv::TermCriteria criteria)
static void cvUndistortPointsInternal( const CvMat* _src, CvMat* _dst, const CvMat* _cameraMatrix,const CvMat* _distCoeffs,const CvMat* matR, const CvMat* matP, cv::TermCriteria criteria)
{CV_Assert(criteria.isValid());double A[3][3], RR[3][3], k[14]={0,0,0,0,0,0,0,0,0,0,0,0,0,0};CvMat matA=cvMat(3, 3, CV_64F, A), _Dk;CvMat _RR=cvMat(3, 3, CV_64F, RR);cv::Matx33d invMatTilt = cv::Matx33d::eye();cv::Matx33d matTilt = cv::Matx33d::eye();CV_Assert( CV_IS_MAT(_src) && CV_IS_MAT(_dst) &&(_src->rows == 1 || _src->cols == 1) &&(_dst->rows == 1 || _dst->cols == 1) &&_src->cols + _src->rows - 1 == _dst->rows + _dst->cols - 1 &&(CV_MAT_TYPE(_src->type) == CV_32FC2 || CV_MAT_TYPE(_src->type) == CV_64FC2) &&(CV_MAT_TYPE(_dst->type) == CV_32FC2 || CV_MAT_TYPE(_dst->type) == CV_64FC2));CV_Assert( CV_IS_MAT(_cameraMatrix) &&_cameraMatrix->rows == 3 && _cameraMatrix->cols == 3 );cvConvert( _cameraMatrix, &matA );if( _distCoeffs ){CV_Assert( CV_IS_MAT(_distCoeffs) &&(_distCoeffs->rows == 1 || _distCoeffs->cols == 1) &&(_distCoeffs->rows*_distCoeffs->cols == 4 ||_distCoeffs->rows*_distCoeffs->cols == 5 ||_distCoeffs->rows*_distCoeffs->cols == 8 ||_distCoeffs->rows*_distCoeffs->cols == 12 ||_distCoeffs->rows*_distCoeffs->cols == 14));_Dk = cvMat( _distCoeffs->rows, _distCoeffs->cols,CV_MAKETYPE(CV_64F,CV_MAT_CN(_distCoeffs->type)), k);cvConvert( _distCoeffs, &_Dk );if (k[12] != 0 || k[13] != 0){cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], NULL, NULL, NULL, &invMatTilt);cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], &matTilt, NULL, NULL);}}if( matR ){CV_Assert( CV_IS_MAT(matR) && matR->rows == 3 && matR->cols == 3 );cvConvert( matR, &_RR );}elsecvSetIdentity(&_RR);if( matP ){double PP[3][3];CvMat _P3x3, _PP=cvMat(3, 3, CV_64F, PP);CV_Assert( CV_IS_MAT(matP) && matP->rows == 3 && (matP->cols == 3 || matP->cols == 4));cvConvert( cvGetCols(matP, &_P3x3, 0, 3), &_PP );cvMatMul( &_PP, &_RR, &_RR );}const CvPoint2D32f* srcf = (const CvPoint2D32f*)_src->data.ptr;const CvPoint2D64f* srcd = (const CvPoint2D64f*)_src->data.ptr;CvPoint2D32f* dstf = (CvPoint2D32f*)_dst->data.ptr;CvPoint2D64f* dstd = (CvPoint2D64f*)_dst->data.ptr;int stype = CV_MAT_TYPE(_src->type);int dtype = CV_MAT_TYPE(_dst->type);int sstep = _src->rows == 1 ? 1 : _src->step/CV_ELEM_SIZE(stype);int dstep = _dst->rows == 1 ? 1 : _dst->step/CV_ELEM_SIZE(dtype);double fx = A[0][0];double fy = A[1][1];double ifx = 1./fx;double ify = 1./fy;double cx = A[0][2];double cy = A[1][2];int n = _src->rows + _src->cols - 1;for( int i = 0; i < n; i++ ){double x, y, x0 = 0, y0 = 0, u, v;if( stype == CV_32FC2 ){x = srcf[i*sstep].x;y = srcf[i*sstep].y;}else{x = srcd[i*sstep].x;y = srcd[i*sstep].y;}u = x; v = y;x = (x - cx)*ifx;y = (y - cy)*ify;if( _distCoeffs ) {// compensate tilt distortioncv::Vec3d vecUntilt = invMatTilt * cv::Vec3d(x, y, 1);double invProj = vecUntilt(2) ? 1./vecUntilt(2) : 1;x0 = x = invProj * vecUntilt(0);y0 = y = invProj * vecUntilt(1);double error = std::numeric_limits<double>::max();// compensate distortion iterativelyfor( int j = 0; ; j++ ){//在这里判断if ((criteria.type & cv::TermCriteria::COUNT) && j >= criteria.maxCount)break;if ((criteria.type & cv::TermCriteria::EPS) && error < criteria.epsilon)break;double r2 = x*x + y*y;double icdist = (1 + ((k[7]*r2 + k[6])*r2 + k[5])*r2)/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);if (icdist < 0) // test: undistortPoints.regression_14583{x = (u - cx)*ifx;y = (v - cy)*ify;break;}double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x)+ k[8]*r2+k[9]*r2*r2;double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y+ k[10]*r2+k[11]*r2*r2;x = (x0 - deltaX)*icdist;y = (y0 - deltaY)*icdist;if(criteria.type & cv::TermCriteria::EPS){double r4, r6, a1, a2, a3, cdist, icdist2;double xd, yd, xd0, yd0;cv::Vec3d vecTilt;r2 = x*x + y*y;r4 = r2*r2;r6 = r4*r2;a1 = 2*x*y;a2 = r2 + 2*x*x;a3 = r2 + 2*y*y;cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4;vecTilt = matTilt*cv::Vec3d(xd0, yd0, 1);invProj = vecTilt(2) ? 1./vecTilt(2) : 1;xd = invProj * vecTilt(0);yd = invProj * vecTilt(1);double x_proj = xd*fx + cx;double y_proj = yd*fy + cy;error = sqrt( pow(x_proj - u, 2) + pow(y_proj - v, 2) );}}}double xx = RR[0][0]*x + RR[0][1]*y + RR[0][2];double yy = RR[1][0]*x + RR[1][1]*y + RR[1][2];double ww = 1./(RR[2][0]*x + RR[2][1]*y + RR[2][2]);x = xx*ww;y = yy*ww;if( dtype == CV_32FC2 ){dstf[i*dstep].x = (float)x;dstf[i*dstep].y = (float)y;}else{dstd[i*dstep].x = x;dstd[i*dstep].y = y;}}
}
2.2 VINS去畸变
void
PinholeCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const
{double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u;double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d;//double lambda;// Lift points to normalised planemx_d = m_inv_K11 * p(0) + m_inv_K13;my_d = m_inv_K22 * p(1) + m_inv_K23;if (m_noDistortion){mx_u = mx_d;my_u = my_d;}else{if (0){double k1 = mParameters.k1();double k2 = mParameters.k2();double p1 = mParameters.p1();double p2 = mParameters.p2();// Apply inverse distortion model// proposed by Heikkilamx2_d = mx_d*mx_d;my2_d = my_d*my_d;mxy_d = mx_d*my_d;rho2_d = mx2_d+my2_d;rho4_d = rho2_d*rho2_d;radDist_d = k1*rho2_d+k2*rho4_d;Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d;Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d;inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d);mx_u = mx_d - inv_denom_d*Dx_d;my_u = my_d - inv_denom_d*Dy_d;}else{// Recursive distortion modelint n = 8;Eigen::Vector2d d_u;distortion(Eigen::Vector2d(mx_d, my_d), d_u);// Approximate valuemx_u = mx_d - d_u(0);my_u = my_d - d_u(1);for (int i = 1; i < n; ++i){distortion(Eigen::Vector2d(mx_u, my_u), d_u);mx_u = mx_d - d_u(0);my_u = my_d - d_u(1);}}}// Obtain a projective rayP << mx_u, my_u, 1.0;
}
3 二者对比
在相机坐标系下随机生成了 20 个观测点,并将其归算到归一化坐标系下作为真值。
#include <iostream>
#include <vector>
#include <random>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>
#include <chrono>#include "Camera.h"using namespace std;int main()
{// 随机数生成 20 个 三维特征点int featureNums=20;default_random_engine generator;vector<cv::Point2f> pts_truth; //归一化真值vector<cv::Point2f> uv_pts; //像素坐标vector<cv::Point2f> cv_un_pts, vins_un_pts; //归一化坐标for(int i = 0; i < featureNums; ++i){uniform_real_distribution<double> xy_rand(-4, 4.0);uniform_real_distribution<double> z_rand(8., 10.);double tx = xy_rand(generator);double ty = xy_rand(generator);double tz = z_rand(generator);Eigen::Vector2d p(tx/tz, ty/tz);Eigen::Vector2d p_distorted;distortion(p, p_distorted); //归一化坐标畸变p_distorted+=p;pts_truth.push_back(cv::Point2f(p(0), p(1)));cv::Point2f uv(fx*p_distorted(0)+cx, fy*p_distorted(1)+cy); //投影到像素坐标uv_pts.push_back(uv);}//OpenCV去畸变,输入像素坐标,输出归一化坐标chrono::steady_clock::time_point cv_t1 = chrono::steady_clock::now();cv::undistortPoints(uv_pts, cv_un_pts, K, distCoeffs);chrono::steady_clock::time_point cv_t2 = chrono::steady_clock::now();double cv_time = chrono::duration_cast<chrono::duration<double,milli>>(cv_t2-cv_t1).count();cout<<"OpenCV"<<endl;cout<<"used time: "<<cv_time/cv_un_pts.size()<<"ms"<<endl;cout<<"pixel error: "<<GetResidual(cv_un_pts, pts_truth)<<endl;//VINS去畸变chrono::steady_clock::time_point vins_t1 = chrono::steady_clock::now();liftProjective(uv_pts, vins_un_pts);chrono::steady_clock::time_point vins_t2 = chrono::steady_clock::now();double vins_time = chrono::duration_cast<chrono::duration<double, milli>>(vins_t2-vins_t1).count();cout<<"VINS"<<endl;cout<<"used time: "<<vins_time/vins_un_pts.size()<<"ms"<<endl;cout<<"pixel error: "<<GetResidual(vins_un_pts, pts_truth)<<endl;return 0;
}
输出结果
给出了每个观测点的平均去畸变耗时和像素坐标系下的重投影误差。
VINS所采用的去畸变算法耗时更少,重投影误差平均值更小,opencv方法与其相差一个数量级。
相关文章:

VINS-Mono/Fusion与OpenCV去畸变对比
VINS中没有直接使用opencv的去畸变函数,而是自己编写了迭代函数完成去畸变操作,主要是为了加快去畸变计算速度 本文对二者的结果精度和耗时进行了对比 VINS-Mono/Fusion与OpenCV去畸变对比1 去畸变原理2 代码实现2.1 OpenCV去畸变2.2 VINS去畸变3 二者对…...

jmx prometheus引起的一次cpu飙高
用户接入了jmx agent进行prometheus监控后,在某个时间点出现cpu飙高 排查思路: 1、top,找到java进程ID 2、top -Hp 进程ID,找到java进程下占用高CPU的线程ID 3、jstack 进程ID,找到那个高CPU的线程ID的堆栈。 4、分析堆…...
Android 虚拟 A/B 详解(六) SnapshotManager 之状态数据
本文为洛奇看世界(guyongqiangx)原创,转载请注明出处。 原文链接:https://blog.csdn.net/guyongqiangx/article/details/129094203 Android 虚拟 A/B 分区《AAndroid 虚拟 A/B 分区》系列,更新中,文章列表: Android 虚拟分区详解(一) 参考资料推荐Android 虚拟分区详解(二…...

Python快速入门系列之一:Python对象
Python对象1. 列表(list)2. 元组(tuple)3. 字典(dict)4. 集合(set)5. 字符串(string)6. BIF (Built-in Function)7. 列表、集合以及字…...

【博客626】不同类型的ARP报文作用以及ARP老化机制
不同类型的ARP报文作用以及ARP老化机制 1、ARP协议及报文 2、不同类型的ARP报文作用 3、ARP工作原理 4、ARP老化机制 5、Linux ARP老化机制 ARP状态机: 在上图中,我们看到只有arp缓存项的reachable状态对于外发包是可用的,对于stale状态的…...

nacos discovery和config
微服务和nacos版本都在2.x及之后。1、discovery用于服务注册,将想要注册的服务注册到nacos中,被naocs发现。pom引入的依赖是:yml配置文件中:2、config用于获取nacos配置管理->配置列表下配置文件中的内容pom引入的依赖是&#…...
【算法数据结构体系篇class06】:堆、大根堆、小根堆、优先队列
一、堆结构1)堆结构就是用数组实现的完全二叉树结构2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆4)堆结构的heapInsert与heapify操作5)堆结构的增大ad…...
试题 算法提高 最小字符串
资源限制内存限制:256.0MB C/C时间限制:2.0s Java时间限制:6.0s Python时间限制:10.0s问题描述给定一些字符串(只包含小写字母),要求将他们串起来构成一个字典序最小的字符串。输入格式第一行T,表示有T组数据。接下来T…...

已解决ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘
已解决from radiomics import featureextractor导包,抛出ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘异常的正确解决方法,亲测有效!!! 文章目录报错问题报错翻译报错原因解决方法联系博…...

乡村振兴研究:全网最全指标农村经济面板数据(2000-2021年)
数据来源:国家统计局 时间跨度:2000-2021年 区域范围:全国31省 指标说明: 部分样例数据: 行政区划代码地区年份经度纬度乡镇数(个)乡数(个)镇数(个)村民委员会数(个)乡村户数(万户)乡村人口(万人)乡村从业人员(万人…...

C语言中用rand()函数产生一随机数
在C语言中如何产生一个随机数呢?用rand()函数。 rand()函数在头文件:#include <stdio.h>中,函数原型:int rand(void);。rand()会返回一个范围在0到RAND_MAX(32767)之间的随机数(整数&…...
关于系统架构
1.系统架构分类: C/S架构 B/S架构 2.C/S架构 Client / Server(客户端 / 服务器) 特点:需要安装特定的客户端软件。 C/S架构的系统优点和缺点: 优点: 1)速度快(软件中数据大部分都是集成到客户端当中,很少量的数据从服…...
LeetCode 1237. 找出给定方程的正整数解
原题链接 难度:middle\color{orange}{middle}middle 2023/2/18 每日一题 题目描述 给你一个函数 f(x,y)f(x, y)f(x,y) 和一个目标结果 zzz,函数公式未知,请你计算方程 f(x,y)zf(x,y) zf(x,y)z 所有可能的正整数 数对 xxx 和 yyy。满足条件…...

【ArcGIS Pro二次开发】(5):UI管理_自定义控件的位置
新增的自定义控件一般放在默认的【加载项】选项卡下,但是根据需求,我们可能需要将控件放在新的自定义选项卡下,在自定义选项卡添加系统自带的控件,将自定义的按钮等控件放在右键菜单栏里以方便使用,等等。 下面就以一…...

学习OpenGL图形2D/3D编程
环境:WindowsVisual Studio 2019最流行的几个库:GLUT,SDL,SFML和GLFWGLFWGLAD库查看显卡OPENGL支持情况VS2019glfwgladopenGL3.3顶点着色器片段着色器VAO-VBO-(EBO)->渲染VAO-VBO-EBO->texture纹理矩阵matrix对图形transfor…...
2023美赛思路 | A题时间序列预测任务的模型选择总结
2023美赛思路 | A题时间序列预测任务的模型选择总结 目录 2023美赛思路 | A题时间序列预测任务的模型选择总结基本介绍数据描述任务介绍时序模型基本介绍 这道题分析植被就行,主要涉及不同植被间的相互作用,有竞争有相互促进,我查了下“植物科学数据中心”和“中国迁地保护植…...

PHP教材管理系统设计(源代码+毕业论文)
【P003】PHP教材管理系统设计(源代码论文) 设计方案 本系统采用B/S结构,所有的程序及数据都放在服务器上,终端在取得相应的权限后使用Web页面浏览,录入,修改等功能。在语言方面使用PHP语言,在…...

nps内网穿透工具
一、准备一台有公网ip的服务器 https://github.com/ehang-io/nps/releases 在这个地址下载服务端的安装包,centos的下载这个 上传到服务器上。 二、然后解压,安装,启动 [rootadministrator ~]# tar xzvf linux_amd64_server.tar.gz [roo…...
webpack打包时的热模块替代配置以及source-map
1.HMR 在devServer当中添加hot:true 热模块化功能 含义:当其中有一个文件发生变化的时候,那么就会被重新打包一次,极大的提高了构建速度 A.样式文件:可以使用HMR功能,因为在style-loader当中实现了 B.js文件:默认不能使用HMR功能…...

Seata架构篇 - TCC模式
TCC 模式 概述 TCC 是分布式事务中的两阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel)。Try:对业务资源的检查并…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...