ORB_SLAM2中的Sim3变换
对于双目、RGB-D相机,可获得深度,因此不存在尺度问题,因此Sim3中的尺度s=1。
(1)通过词袋加速算法实现当前帧、闭环帧的特征点的匹配,建立闭环帧的路标点和当前帧的特征点间的联系。
(2)使用RANSAC法,随机采取3对点(根据特征点的索引,获得当前帧中的路标点(局部建图时获得)及对应的闭环帧中的路标点(步骤(1)中获得)),计算两帧间的Sim3变换。一共迭代5次,如果有一次,获得的内点数大于20,就认为成功。
(3)根据Sim3变换,将闭环帧的路标点投影至当前帧中进行匹配。随后优化Sim3矩阵。
(4)将闭环帧及其共视帧的路标点投影至当前帧中,如果匹配上的路标点大于40,就认为该闭环帧可靠。
(5)随后就可以进行闭环校正,根据当前帧和闭环帧的Sim3矩阵,去校正当前帧及其共视帧的位姿、路标点坐标。更新共视关系,更新本质图。
设置RANSAC采样参数
主要参数包括迭代次数、迭代成功时,内点的数目、K次迭代可以成功的概率。
void Sim3Solver::SetRansacParameters(double probability, int minInliers, int maxIterations)
{mRansacProb = probability; // 0.99mRansacMinInliers = minInliers; // 20mRansacMaxIts = maxIterations; // 最大迭代次数 300// 匹配点的数目N = mvpMapPoints1.size(); // number of correspondences// 内点标记向量mvbInliersi.resize(N);// Adjust Parameters according to number of correspondencesfloat epsilon = (float)mRansacMinInliers/N;// Set RANSAC iterations according to probability, epsilon, and max iterations // 计算迭代次数的理论值,也就是经过这么多次采样,其中至少有一次采样中,三对点都是内点// epsilon 表示了在这 N 对匹配点中,我随便抽取一对点是内点的概率; // 为了计算Sim3,我们需要从这N对匹配点中取三对点;那么如果我有放回的从这些点中抽取三对点,取这三对点均为内点的概率是 p0=epsilon^3// 相应地,如果取三对点中至少存在一对匹配点是外点, 概率为p1=1-p0// 当我们进行K次采样的时候,其中每一次采样中三对点中都存在至少一对外点的概率就是p2=p1^k// K次采样中,至少有一次采样中三对点都是内点的概率是p=1-p2// 候根据 p2=p1^K 我们就可以导出 K 的公式:K=\frac{\log p2}{\log p1}=\frac{\log(1-p)}{\log(1-epsilon^3)}// 也就是说,我们进行K次采样,其中至少有一次采样中,三对点都是内点; 因此我们就得到了RANSAC迭代次数的理论值int nIterations;if(mRansacMinInliers==N) nIterations=1; // 这种情况的时候最后计算得到的迭代次数的确就是一次elsenIterations = ceil(log(1-mRansacProb)/log(1-pow(epsilon,3))); // 外层的max保证RANSAC能够最少迭代一次;// 内层的min的目的是,如果理论值比给定值要小,那么我们优先选择使用较少的理论值来节省时间(其实也有极大概率得到能够达到的最好结果);// 如果理论值比给定值要大,那么我们也还是有限选择使用较少的给定值来节省时间mRansacMaxIts = max(1,min(nIterations,mRansacMaxIts));// 当前正在进行的迭代次数mnIterations = 0;
}
RANSAC求解Sim3变换
求解成功的标志:内点数大于20。
/*** @brief Ransac求解mvX3Dc1和mvX3Dc2之间Sim3,函数返回mvX3Dc2到mvX3Dc1的Sim3变换* * @param[in] nIterations 设置的最大迭代次数* @param[in] bNoMore 为true表示穷尽迭代还没有找到好的结果,说明求解失败* @param[in] vbInliers 标记是否是内点* @param[in] nInliers 内点数目* @return cv::Mat 计算得到的Sim3矩阵*/
cv::Mat Sim3Solver::iterate(int nIterations, bool &bNoMore, vector<bool> &vbInliers, int &nInliers)
开始循环
循环条件:当前迭代次数小于5,总的迭代次数小于300。当前迭代次数指的是对于当前帧尝试求解Sim3的次数,如果大于5了还不行,就取消。总的迭代次数指的是:在回环检测的时候,会计算所有的候选闭环帧和当前帧的Sim3,在这个过程中的总的迭代次数。
(1)随机取3组点,并将索引在列表中删除
// Get min set of points// Step 2.1 随机取三组点,取完后从候选索引中删掉for(short i = 0; i < 3; ++i){// DBoW3中的随机数生成函数int randi = DUtils::Random::RandomInt(0, vAvailableIndices.size()-1);int idx = vAvailableIndices[randi];// P3Dc1i和P3Dc2i中点的排列顺序:// 相机坐标// x1 x2 x3 ...// y1 y2 y3 ...// z1 z2 z3 ...mvX3Dc1[idx].copyTo(P3Dc1i.col(i));mvX3Dc2[idx].copyTo(P3Dc2i.col(i));// 从"可用索引列表"中删除这个点的索引 vAvailableIndices[randi] = vAvailableIndices.back();vAvailableIndices.pop_back();}
(3)根据匹配的3对路标点,计算Sim3变换
ComputeSim3(P3Dc1i,P3Dc2i);
(4)根据Sim3变换检测内点数量
CheckInliers();
(5)更新最优的Sim3参数系数,包括R,t,s,匹配上的内点数等。如果内点数大于20,则计算成功,直接返回Sim3矩阵。
// Step 2.4 记录并更新最多的内点数目及对应的参数if(mnInliersi>=mnBestInliers){mvbBestInliers = mvbInliersi;mnBestInliers = mnInliersi;mBestT12 = mT12i.clone();mBestRotation = mR12i.clone();mBestTranslation = mt12i.clone();mBestScale = ms12i;if(mnInliersi>mRansacMinInliers) // 只要计算得到一次合格的Sim变换,就直接返回{// 返回值,告知得到的内点数目nInliers = mnInliersi;for(int i=0; i<N; i++)if(mvbInliersi[i])// 标记为内点vbInliers[mvnIndices1[i]] = true;return mBestT12;} // 如果当前次迭代已经合格了,直接返回} // 更新最多的内点数目
上述过程重复5次还不行,就直接返回FALSE
// Step 3 如果已经达到了最大迭代次数了还没得到满足条件的Sim3,说明失败了,放弃,返回if(mnIterations>=mRansacMaxIts)bNoMore=true;
计算三个点的质心以及去质心坐标
/*** @brief 给出三个点,计算它们的质心以及去质心之后的坐标* * @param[in] P 输入的3D点* @param[in] Pr 去质心后的点* @param[in] C 质心*/
void Sim3Solver::ComputeCentroid(cv::Mat &P, cv::Mat &Pr, cv::Mat &C)
{// 矩阵P每一行求和,结果存在C。这两句也可以使用CV_REDUCE_AVG选项来实现cv::reduce(P,C,1,CV_REDUCE_SUM);C = C/P.cols;// 求平均for(int i=0; i<P.cols; i++){Pr.col(i)=P.col(i)-C;//减去质心}
}
根据3对匹配的点计算Sim3
需要3对相机1、相机2中的匹配路标点(相机坐标系中)
旋转矩阵------尺度------平移矩阵-------构建Sim3矩阵
计算三个点的质心
cv::Mat Pr1(P1.size(),P1.type()); // Relative coordinates to centroid (set 1)cv::Mat Pr2(P2.size(),P2.type()); // Relative coordinates to centroid (set 2)cv::Mat O1(3,1,Pr1.type()); // Centroid of P1cv::Mat O2(3,1,Pr2.type()); // Centroid of P2ComputeCentroid(P1,Pr1,O1);ComputeCentroid(P2,Pr2,O2);
计算M、N矩阵
// Step 2: 计算论文中三维点数目n>3的 M 矩阵。这里只使用了3个点// Pr2 对应论文中 r_l,i',Pr1 对应论文中 r_r,i',计算的是P2到P1的Sim3,论文中是left 到 right的Sim3cv::Mat M = Pr2*Pr1.t();// Step 3: 计算论文中的 N 矩阵double N11, N12, N13, N14, N22, N23, N24, N33, N34, N44;cv::Mat N(4,4,P1.type());N11 = M.at<float>(0,0)+M.at<float>(1,1)+M.at<float>(2,2); // Sxx+Syy+SzzN12 = M.at<float>(1,2)-M.at<float>(2,1); // Syz-SzyN13 = M.at<float>(2,0)-M.at<float>(0,2); // Szx-SxzN14 = M.at<float>(0,1)-M.at<float>(1,0); // ...N22 = M.at<float>(0,0)-M.at<float>(1,1)-M.at<float>(2,2);N23 = M.at<float>(0,1)+M.at<float>(1,0);N24 = M.at<float>(2,0)+M.at<float>(0,2);N33 = -M.at<float>(0,0)+M.at<float>(1,1)-M.at<float>(2,2);N34 = M.at<float>(1,2)+M.at<float>(2,1);N44 = -M.at<float>(0,0)-M.at<float>(1,1)+M.at<float>(2,2);N = (cv::Mat_<float>(4,4) << N11, N12, N13, N14,N12, N22, N23, N24,N13, N23, N33, N34,N14, N24, N34, N44);
计算旋转矩阵
// Step 4: 特征值分解求最大特征值对应的特征向量,就是我们要求的旋转四元数cv::Mat eval, evec; // val vec// 特征值默认是从大到小排列,所以evec[0] 是最大值// 计算对称矩阵的特征值,特征向量cv::eigen(N,eval,evec); // N 矩阵最大特征值(第一个特征值)对应特征向量就是要求的四元数(q0 q1 q2 q3),其中q0 是实部// 将(q1 q2 q3)放入vec(四元数的虚部)cv::Mat vec(1,3,evec.type());(evec.row(0).colRange(1,4)).copyTo(vec); //extract imaginary part of the quaternion (sin*axis)// Rotation angle. sin is the norm of the imaginary part, cos is the real part// 四元数虚部模长 norm(vec)=sin(theta/2), 四元数实部 evec.at<float>(0,0)=q0=cos(theta/2)// 这一步的ang实际是theta/2,theta 是旋转向量中旋转角度// ? 这里也可以用 arccos(q0)=angle/2 得到旋转角吧double ang=atan2(norm(vec),evec.at<float>(0,0));// vec/norm(vec)归一化得到归一化后的旋转向量,然后乘上角度得到包含了旋转轴和旋转角信息的旋转向量vec// vec:旋转向量vec = 2*ang*vec/norm(vec); //Angle-axis x. quaternion angle is the halfmR12i.create(3,3,P1.type());// 旋转向量(轴角)转换为旋转矩阵cv::Rodrigues(vec,mR12i); // computes the rotation matrix from angle-axis
计算s
双目、RGB-D的s为1。
// Step 6: 计算尺度因子 Scaleif(!mbFixScale){// 论文中有2个求尺度方法。一个是p632右中的位置,考虑了尺度的对称性// 代码里实际使用的是另一种方法,这个公式对应着论文中p632左中位置的那个// Pr1 对应论文里的r_r,i',P3对应论文里的 r_l,i',(经过坐标系转换的Pr2), n=3, 剩下的就和论文中都一样了double nom = Pr1.dot(P3);// 准备计算分母cv::Mat aux_P3(P3.size(),P3.type());aux_P3=P3;// 先得到平方(每个元素都平方)cv::pow(P3,2,aux_P3);double den = 0;// 然后再累加for(int i=0; i<aux_P3.rows; i++){for(int j=0; j<aux_P3.cols; j++){den+=aux_P3.at<float>(i,j);}}ms12i = nom/den;}elsems12i = 1.0f;
计算平移向量
// Step 7: 计算平移Translationmt12i.create(1,3,P1.type());// 论文中平移公式mt12i = O1 - ms12i*mR12i*O2;
计算1–>2,2–>1的Sim3变换矩阵
// Step 8: 计算双向变换矩阵,目的是在后面的检查的过程中能够进行双向的投影操作// Step 8.1 用尺度,旋转,平移构建变换矩阵 T12mT12i = cv::Mat::eye(4,4,P1.type());cv::Mat sR = ms12i*mR12i;// |sR t|// mT12i = | 0 1|sR.copyTo(mT12i.rowRange(0,3).colRange(0,3));mt12i.copyTo(mT12i.rowRange(0,3).col(3));// Step 8.2 T21mT21i = cv::Mat::eye(4,4,P1.type());cv::Mat sRinv = (1.0/ms12i)*mR12i.t();sRinv.copyTo(mT21i.rowRange(0,3).colRange(0,3));cv::Mat tinv = -sRinv*mt12i;tinv.copyTo(mT21i.rowRange(0,3).col(3));
检测内点
void Sim3Solver::CheckInliers()
投影路标点
根据Sim3变换,将1中的路标点投影至2中,2中的路标点投影至1中。
// 用计算的Sim3 对所有的地图点投影,得到图像点vector<cv::Mat> vP1im2, vP2im1;Project(mvX3Dc2,vP2im1,mT12i,mK1);// 把2系中的3D经过Sim3变换(mT12i)到1系中计算重投影坐标Project(mvX3Dc1,vP1im2,mT21i,mK2);// 把1系中的3D经过Sim3变换(mT21i)到2系中计算重投影坐标
根据重投影误差确定内点
// 对于两帧的每一个匹配点for(size_t i=0; i<mvP1im1.size(); i++){// 当前关键帧中的地图点直接在当前关键帧图像上的投影坐标mvP1im1,mvP2im2// 对于这对匹配关系,在两帧上的投影点距离都要进行计算cv::Mat dist1 = mvP1im1[i]-vP2im1[i];cv::Mat dist2 = vP1im2[i]-mvP2im2[i];// 取距离的平方作为误差const float err1 = dist1.dot(dist1);const float err2 = dist2.dot(dist2);// 根据之前确定的这个最大容许误差来确定这对匹配点是否是外点if(err1<mvnMaxError1[i] && err2<mvnMaxError2[i]){mvbInliersi[i]=true;mnInliersi++;}elsemvbInliersi[i]=false;}// 遍历其中的每一对匹配点
相关文章:

Ubuntu16.04 下的网易云出现网络异常、无法播放,界面无响应问题的统一解决
能够在Linux系统下体验到原生界面的网易云音乐是件不错的事情,但是它总是经常性的出现网络异常,界面无响应的问题 为了听歌的体验,进行深入探究: 首先通过终端启用网易云音乐:sudo netease-cloud-music 会得到网易云音…

SpringBoot 概念和起步
一、概念和由来 1、什么是 Spring Boot Spring Boot 的设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用特定方式来进行配置,从而使开发人员不再需要定义样板化的配置。 Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用…

WKWebView Safari调试、JS互调、加载进度条、JS中alert、confirm、prompt
主要内容 Safari调试swift/OC与JS互调增加加载进度条支持JS中alert、confirm、prompt Safari调试 设置 —> safari --> 高级,开启JavaScript、网页检查器 打开Safari浏览器,选择调试的网页,同样在js里面可以断点调试: swift/OC与JS互调 这里…

CentOS7 打包RPM 升级OpenSSH8.3
目录 一、源码包 二、打包RPM 2.1、准备阶段 2.2、打包排错阶段 三、升级 漏扫设备发现OpenSSH有漏洞,需要升级到OpenSSH 8.1及以上版本,那么干脆就直接升级到发文时最新的版本,OpenSSH 8.3。做法是找到OpenSSH 8.3的源码包,…

步步为营-44-窗体之间传值--观察者模式
说明 :观察者模式又叫发布-订阅模式,其中又涉及到中介者模式 1 结构 2 创建Main窗体(中介者),ChildForm1(发布者),ChildForm2(订阅者),ChildForm3(订阅者), 2.1 ChildForm1中添加按钮,当按钮被点击是ChildForm2(订阅者),ChildForm3(订阅者),的文本框汇中获取信息 2.2 定义接口 …

java指令详解
Java是通过java虚拟机来装载和执行编译文件(class文件)的,java虚拟机通过命令java option 来启动,-option为虚拟机参数,通过这些参数可对虚拟机的运行状态进行调整. 一、如何查看参数列表: 虚拟机参数分为基本和扩展两…

wrs-arcface虹软人脸识别
前言 虹软人脸识别组件,支持活体识别、离线识别、图片人脸特征识别、图片是否同一人对比、相机人脸识别或对比,虹软免费版请使https://ext.dcloud.net.cn/plugin?id6084 功能 支持活体识别、离线识别图片人脸特征识别(年龄、性别、3DAngle)两张图片是否是同一人…

C++指针与引用的区别
(1)指针是一个变量,本身占有内存,内存中存储的是所指向对象的地址。引用是内存的别名。 (2)指针可以通过解引用的方式,取出所指向内存中的值。引用没有解引用。 (3)指针可…

Linux08-日志
目录 一、systemd的日志 1.1、sytemd-journald与systemd日志 1.2、systemd日志的持久化 二、系统常规日志 2.1、系统日志概述 2.2、查看系统日志文件 2.3、日志的轮转 2.4、分析系统日志 2.5、使用logger发送消息到日志 RHEL7的日志由2个服务负责记录,分别…

Java的小实验——各种测试以及说明
日期:2018.10.07 星期五 博客期:014 一、Java中的位运算 代码如下: 1 package Morts107;2 3 public class Test107 {4 public static void main(String[] args) {5 int z;6 z 13>>1;//00001101(13)---------------…

C++内存的分区
C内存分为四个区: (1)代码区:存放代码转译成的二进制代码。 (2)全局区:存放全局变量、静态变量(static)、常量(如字符串常量)。 全局区中还包含一…

SpringCloud的服务网关zuul
演示如何使用api网关屏蔽各服务来源 一、概念和定义 1、zuul最终还是使用Ribbon的,顺便测试一下Hystrix断路保护2、zuul也是一个EurekaClient,访问服务注册中心,获取元数据,使用本地的Ribbon负载均衡,Hystrix断路保护&…

wrs-tuya-cloud
前言 wrs-tuya-cloud是涂鸦官网针对云开发的插件,包含垂直品类硬件API(万能红外开放能力、设备连接服务、设备OTA固件升级、实时音视频、睡眠带开放能力、体脂秤开放能力、智能门锁开放能力、视频云存储 、邮件服务 、 语音消息服务、消息推送服务、短信服务 、内测…

Windows Server 2016 笔记
从业界普遍实践结果来看,Windows Server在服务器领域真是不太好用。但是,有些时候由于种种原因不得不用,所以还是有必要了解一下的。今天参加了一个Windows Server的培训,主要面对Windows Server 2016,写下这篇博客备忘…

(办公)网页发送到桌面快捷方式怎么做
转载自百度:https://jingyan.baidu.com/article/f79b7cb303d50a9145023e6e.html 有时候一个网页我们需要经常用到,每次找那个需要的网页很耗时间,那么我们怎么把我们需要的网页发送到桌面快捷方式呢? 这样下次我们直接点击桌面上的快捷方式就…

C++程序编译过程
程序编译的过程,是将源代码转换为计算机可执行的机械语言的过程。分为预处理、编译、汇编、链接四步。 (1)预处理:对程序进行预处理,比如将头文件的代码直接赋值到当前代码中等等. (2)编译&am…

Java的注释(详细版)
注释是对代码进行必要的说明,以便于后期的修改、维护和升级。Java的注释分为三种:第一种是**单行注释**:用双斜杠“//”来进行实例://单行注释第二种是**文档注释**:用斜杠“/”和星号“*”来进行实例:/***…

Hadoop的存储架构介绍
http://lxw1234.com/archives/2016/04/638.htm 该文章介绍了Hadoop的架构原理,简单易懂。 目前公司提供Hadoop的运算集群BMR,可以直接申请集群资源。转载于:https://www.cnblogs.com/blog-of-Fourier/p/6809811.html

编译OpenSSH8.4的RPM包及升级
目录 一、安装相关依赖包 二、创建rpmbuild目录并下载源码 三、打包及排错 四、升级到OpenSSH 8.4p1 以下是打包好的OpenSSH 8.4p1,包括7个rpm包,欢迎下载使用。 OpenSSH-8.4p1-Bundle 一、安装相关依赖包 根据以往经验,需要安装wget、…

centos 系统使用verdaccio搭建npm私库
.安装nodejs yum install -y nodejs 2.安装verdaccio npm install -g verdaccio --unsafe-perm 3.配置 a.修改配置文件 config.yaml,在其最后添加监听端口(使其可在外网访问) listen: 0.0.0.0:4873 b.对外开放4873端口 firewall-cmd --state …

视觉SLAM中PNP求解
PNP(Perspective-n-points)是SLAM中估计位姿的重要方法。已知条件为路标点在相机1中的相机坐标以及投影到相机2中的像素坐标,据此去估计相机1、相机2间的位姿。主要解法包括DLT、P3P、EPNP P3P 已知A、B、C在相机1坐标系下的坐标࿰…

Java程序的运行原理 用记事本编写Java代码
首先将Java代码写入源文件(.java)中→ 通过 javac 生成class文件(.class) → 再通过java命令执行程序:◆将class文件加载内存(相当于将东西输入大脑)◆检验class文件(大脑检查是否有语法等错误,若无误)◆将…

Linux下修改mysql的root密码后数据库消失怎么处理
Linux系统下如果没有通过password()函数修改mysql的root密码就会导致mysql数据库消失。有些人可能不知道而直接修改了mysql的root密码,于是产生了mysql数据库消失的问题,这个时候该怎么处理呢? 可以用下面的办法解决&a…

编译httpd-2.4.46的RPM包
目录 一、下载源码 二、编译&排错 2.1、第一次编译,解决依赖包问题。 2.2、第二次编译,解决anaconda导致的环境变量问题 2.3、第三次编译,解决apr版本过低问题 提供 apr-1.7.0、httpd-2.4.46 的RPM包下载。 apr-1.7.0-bundle.zip …

C/s模式B/S模式
C/s模式:是客户端/服务器(Client/Server)模式,主要指的是传统的桌面级的应用程序。比如我们经常用的信息管理系统。 C/S 客户端/服务器 例如QQ,网络游戏,需要下载客户端才能访问服务器的程序 B/S 浏览器/服务器 例如Intel…

分割catalina.out 每天生成一个文件
1. touch xxx(文件名字).sh 2. vim xxx.sh 写入 ----------------------- #!/bin/shcd dirname $0pwdddate %Y%m%dd7date -d7 day ago %Y%m%dcd ../logs/cp catalina.out catalina.out.${d}cat /dev/null > catalina.outrm -rf catalina.out.${d7} ----------…

、|| 和 、| 的区别(详尽版)
&&和|| 是逻辑运算符(也包括 !) 逻辑运算符含义&&逻辑与(两者为真才为真,一者为假即为假)︱︱逻辑或(两者为假才为假,一者为真即为真)!逻辑非(本来值的…

C++负数、小数如何保存
负整数 正整数直接按照源码存储,负整数按照补码存储。 原码、反码、补码 首先要清楚原码、反码、补码: 计算机中一个字节为8位,在此以一个字节为例: 原码: 十进制1:0000 0001 十进制-1:1000 …

MySQL00-这都不知道还TM学啥MySQL
目录 一、MySQL架构概述 1.1、客户端连接器 1.2、连接层 1.3、可插拔存储引擎 1.4、文件系统与文件 二、配置文件 三、数据文件 四、日志文件(以MySQL5.7.32为例) 4.1、错误日志 Error Log 4.2、一般查询日志 General Query Log 4.3、二进制日…

ORB_SLAM2 PnPSolver
EPNP:已知4组(默认)3D-2D匹配点,构建参考点,通过计算参考点的相机坐标,线性组合成路标点的相机坐标。然后使用ICP估计相机间的位姿变换。 需要注意的事,EPNP可以同时使用N组路标点构建M矩阵…