当前位置: 首页 > article >正文

ORB-SLAM3 从理论到代码实现(五):sim3 优化

1. 前言该函数实现于 src/Optimizer.cc 文件中被 src/LoopClosing.cc 文件中的LoopClosing::ComputeSim3() 调用。如果当前关键帧与某一候选关键帧匹配时则会计算两帧之间的 Sim3 变换 gScm候选关键帧到当前帧的Sim3变换。获得候选关键帧到当前帧的Sim3变换gScm之后然后再利用两帧之间的匹配关系对gScm进行优化首先看下图KF1代表当前关键帧KF2代表匹配成功的候选关键帧。g2oS12 表示 KF2→KF1 的Sim变换g2oS12 KF→KF2 的Sim变换。大家需要注意闭环线程中调用 LoopClosing::ComputeSim3() 时还未进行地图点融合。也就是说KF1的关键点KP1与KF1的关键点KP2匹配但是他们对应的地图点未必相同。2. 代码分析LoopClosing::DetectAndReffineSim3FromLastKF使用1投22投1这么来只优化g2oS12int Optimizer::OptimizeSim3(KeyFrame *pKF1, KeyFrame *pKF2, vectorMapPoint * vpMatches1, g2o::Sim3 g2oS12, const float th2, const bool bFixScale, Eigen::Matrixdouble, 7, 7 mAcumHessian, const bool bAllPoints) { // 1. 初始化g2o优化器 // 先构造求解器 g2o::SparseOptimizer optimizer; // 构造线性方程求解器Hx -b的求解器 g2o::BlockSolverX::LinearSolverType *linearSolver; // 使用dense的求解器常见非dense求解器有cholmod线性求解器和shur补线性求解器 linearSolver new g2o::LinearSolverDenseg2o::BlockSolverX::PoseMatrixType(); g2o::BlockSolverX *solver_ptr new g2o::BlockSolverX(linearSolver); // 使用L-M迭代 g2o::OptimizationAlgorithmLevenberg *solver new g2o::OptimizationAlgorithmLevenberg(solver_ptr); optimizer.setAlgorithm(solver); // Camera poses // 2.1 添加Sim3顶点 const cv::Mat R1w pKF1-GetRotation(); const cv::Mat t1w pKF1-GetTranslation(); const cv::Mat R2w pKF2-GetRotation(); const cv::Mat t2w pKF2-GetTranslation(); // Set Sim3 vertex ORB_SLAM3::VertexSim3Expmap *vSim3 new ORB_SLAM3::VertexSim3Expmap(); vSim3-_fix_scale bFixScale; vSim3-setEstimate(g2oS12); vSim3-setId(0); vSim3-setFixed(false); vSim3-pCamera1 pKF1-mpCamera; vSim3-pCamera2 pKF2-mpCamera; optimizer.addVertex(vSim3); // Set MapPoint vertices // 2.1 添加MP顶点 const int N vpMatches1.size(); const vectorMapPoint * vpMapPoints1 pKF1-GetMapPointMatches(); vectorORB_SLAM3::EdgeSim3ProjectXYZ * vpEdges12; // pKF2对应的MapPoints到pKF1的投影 vectorORB_SLAM3::EdgeInverseSim3ProjectXYZ * vpEdges21; // pKF1对应的MapPoints到pKF2的投影 vectorsize_t vnIndexEdge; vectorbool vbIsInKF2; vnIndexEdge.reserve(2 * N); vpEdges12.reserve(2 * N); vpEdges21.reserve(2 * N); vbIsInKF2.reserve(2 * N); const float deltaHuber sqrt(th2); int nCorrespondences 0; int nBadMPs 0; // 没有实际用处没有输出信息 int nInKF2 0; // 输出信息用 int nOutKF2 0; // 输出信息用 int nMatchWithoutMP 0; // 输出信息用 vectorint vIdsOnlyInKF2; // 2.2 遍历当前关键帧的所有MP点 for (int i 0; i N; i) { if (!vpMatches1[i]) continue; // pMP1和pMP2是匹配的MapPointspMP1表示当前帧正常对应的mppMP2表示对应的回环的mp MapPoint *pMP1 vpMapPoints1[i]; MapPoint *pMP2 vpMatches1[i]; // (1, 2) (3, 4) (5, 6) const int id1 2 * i 1; const int id2 2 * (i 1); // 返回这个点在pKF2关键帧中对应的特征点id const int i2 get0(pMP2-GetIndexInKeyFrame(pKF2)); cv::Mat P3D1c; // 点1在当前关键帧相机坐标系下的坐标 cv::Mat P3D2c; // 点2在候选关键帧相机坐标系下的坐标 if (pMP1 pMP2) { if (!pMP1-isBad() !pMP2-isBad()) { // 2.3 添加PointXYZ顶点 且设为了固定 g2o::VertexSBAPointXYZ *vPoint1 new g2o::VertexSBAPointXYZ(); cv::Mat P3D1w pMP1-GetWorldPos(); P3D1c R1w * P3D1w t1w; vPoint1-setEstimate(Converter::toVector3d(P3D1c)); // 点1在当前关键帧下的三维点坐标 vPoint1-setId(id1); vPoint1-setFixed(true); optimizer.addVertex(vPoint1); g2o::VertexSBAPointXYZ *vPoint2 new g2o::VertexSBAPointXYZ(); cv::Mat P3D2w pMP2-GetWorldPos(); P3D2c R2w * P3D2w t2w; vPoint2-setEstimate(Converter::toVector3d(P3D2c)); // 点2在候选关键帧下的三维点坐标 vPoint2-setId(id2); vPoint2-setFixed(true); optimizer.addVertex(vPoint2); } else { nBadMPs; continue; } } else { nMatchWithoutMP; // The 3D position in KF1 doesnt exist if (!pMP2-isBad()) { // 执行到这里意味着特征点没有对应的原始MP却有回环MP将其投到候选帧里面 g2o::VertexSBAPointXYZ *vPoint2 new g2o::VertexSBAPointXYZ(); cv::Mat P3D2w pMP2-GetWorldPos(); P3D2c R2w * P3D2w t2w; vPoint2-setEstimate(Converter::toVector3d(P3D2c)); vPoint2-setId(id2); vPoint2-setFixed(true); optimizer.addVertex(vPoint2); vIdsOnlyInKF2.push_back(id2); } continue; } if (i2 0 !bAllPoints) // bAllPoints true { Verbose::PrintMess( Remove point - i2: to_string(i2) ; bAllPoints: to_string(bAllPoints), Verbose::VERBOSITY_DEBUG); continue; } if (P3D2c.atfloat(2) 0) { Verbose::PrintMess(Sim3: Z coordinate is negative, Verbose::VERBOSITY_DEBUG); continue; } nCorrespondences; // 2.4 添加两个顶点3D点到相机投影的边 // Set edge x1 S12*X2 Eigen::Matrixdouble, 2, 1 obs1; const cv::KeyPoint kpUn1 pKF1-mvKeysUn[i]; obs1 kpUn1.pt.x, kpUn1.pt.y; // 这个边的误差计算方式 // 1. 将点2通过g2oS12计算到当前关键帧下 // 2. 点2在当前关键帧下投影到图像上与观测求误差 ORB_SLAM3::EdgeSim3ProjectXYZ *e12 new ORB_SLAM3::EdgeSim3ProjectXYZ(); e12-setVertex(0, dynamic_castg2o::OptimizableGraph::Vertex *(optimizer.vertex(id2))); // 2相机坐标系下的三维点 e12-setVertex(1, dynamic_castg2o::OptimizableGraph::Vertex *(optimizer.vertex(0))); // g2oS12 e12-setMeasurement(obs1); const float invSigmaSquare1 pKF1-mvInvLevelSigma2[kpUn1.octave]; e12-setInformation(Eigen::Matrix2d::Identity() * invSigmaSquare1); g2o::RobustKernelHuber *rk1 new g2o::RobustKernelHuber; e12-setRobustKernel(rk1); rk1-setDelta(deltaHuber); optimizer.addEdge(e12); // Set edge x2 S21*X1 // 2.5 另一个边 Eigen::Matrixdouble, 2, 1 obs2; cv::KeyPoint kpUn2; bool inKF2; // 投之前要确定下这个点的像素坐标 if (i2 0) { kpUn2 pKF2-mvKeysUn[i2]; obs2 kpUn2.pt.x, kpUn2.pt.y; inKF2 true; nInKF2; // 输出信息表示在kf2中找到MP2的点数 } else // BUG 如果没找到使用三维点投影到KF2中表示并没有特征点与之对应把这个结果当做obs2是不是会带来一些误差而且还不通过内参吗重大bug { float invz 1 / P3D2c.atfloat(2); float x P3D2c.atfloat(0) * invz; float y P3D2c.atfloat(1) * invz; // float u pKF2-fx * x pKF2-cx; // float v pKF2-fy * y pKF2-cy; // obs2 u, v; // kpUn2 cv::KeyPoint(cv::Point2f(u, v), pMP2-mnTrackScaleLevel); obs2 x, y; kpUn2 cv::KeyPoint(cv::Point2f(x, y), pMP2-mnTrackScaleLevel); inKF2 false; nOutKF2; } // 1相机坐标系下的三维点经过g2oS12投影到kf2下计算重投影误差 ORB_SLAM3::EdgeInverseSim3ProjectXYZ *e21 new ORB_SLAM3::EdgeInverseSim3ProjectXYZ(); e21-setVertex(0, dynamic_castg2o::OptimizableGraph::Vertex *(optimizer.vertex(id1))); e21-setVertex(1, dynamic_castg2o::OptimizableGraph::Vertex *(optimizer.vertex(0))); e21-setMeasurement(obs2); float invSigmaSquare2 pKF2-mvInvLevelSigma2[kpUn2.octave]; e21-setInformation(Eigen::Matrix2d::Identity() * invSigmaSquare2); g2o::RobustKernelHuber *rk2 new g2o::RobustKernelHuber; e21-setRobustKernel(rk2); rk2-setDelta(deltaHuber); optimizer.addEdge(e21); vpEdges12.push_back(e12); vpEdges21.push_back(e21); vnIndexEdge.push_back(i); vbIsInKF2.push_back(inKF2); } // Optimize! // 3. 开始优化 optimizer.initializeOptimization(); optimizer.optimize(5); // Check inliers // 4.剔除一些误差大的边因为e12与e21对应的是同一个三维点所以只要有一个误差太大就直接搞掉 // Check inliers // 进行卡方检验大于阈值的边剔除同时删除鲁棒核函数 int nBad 0; int nBadOutKF2 0; for (size_t i 0; i vpEdges12.size(); i) { ORB_SLAM3::EdgeSim3ProjectXYZ *e12 vpEdges12[i]; ORB_SLAM3::EdgeInverseSim3ProjectXYZ *e21 vpEdges21[i]; if (!e12 || !e21) continue; if (e12-chi2() th2 || e21-chi2() th2) { size_t idx vnIndexEdge[i]; vpMatches1[idx] static_castMapPoint *(NULL); optimizer.removeEdge(e12); optimizer.removeEdge(e21); vpEdges12[i] static_castORB_SLAM3::EdgeSim3ProjectXYZ *(NULL); vpEdges21[i] static_castORB_SLAM3::EdgeInverseSim3ProjectXYZ *(NULL); nBad; if (!vbIsInKF2[i]) { nBadOutKF2; } continue; } // Check if remove the robust adjustment improve the result e12-setRobustKernel(0); e21-setRobustKernel(0); } // 如果有坏点迭代次数更多 int nMoreIterations; if (nBad 0) nMoreIterations 10; else nMoreIterations 5; if (nCorrespondences - nBad 10) return 0; // Optimize again only with inliers // 5. 再一次优化 optimizer.initializeOptimization(); optimizer.optimize(nMoreIterations); int nIn 0; mAcumHessian Eigen::MatrixXd::Zero(7, 7); // 更新vpMatches1删除外点统计内点数量 for (size_t i 0; i vpEdges12.size(); i) { ORB_SLAM3::EdgeSim3ProjectXYZ *e12 vpEdges12[i]; ORB_SLAM3::EdgeInverseSim3ProjectXYZ *e21 vpEdges21[i]; if (!e12 || !e21) continue; e12-computeError(); e21-computeError(); if (e12-chi2() th2 || e21-chi2() th2) { size_t idx vnIndexEdge[i]; vpMatches1[idx] static_castMapPoint *(NULL); } else { nIn; } } // Recover optimized Sim3、 // 6.得到优化后的结果 g2o::VertexSim3Expmap *vSim3_recov static_castg2o::VertexSim3Expmap *(optimizer.vertex(0)); g2oS12 vSim3_recov-estimate(); return nIn; }参考文献【SLAM学习笔记】10-ORB_SLAM3关键源码分析⑧ Optimizer五sim3优化_口哨糖youri的博客-CSDN博客_sim3优化(01)ORB-SLAM2源码无死角解析-(64) BA优化(g2o)→闭环线程:Optimizer::OptimizeSim3→Sim3变换优化_江南才尽年少无知的博客-CSDN博客

相关文章:

ORB-SLAM3 从理论到代码实现(五):sim3 优化

1. 前言 该函数实现于 src/Optimizer.cc 文件中,被 src/LoopClosing.cc 文件中的LoopClosing::ComputeSim3() 调用。如果当前关键帧,与某一候选关键帧匹配时,则会计算两帧之间的 Sim3 变换 gScm(候选关键帧到当前帧的Sim3变换&am…...

如何轻松编辑Windows可执行文件资源:rcedit实用指南

如何轻松编辑Windows可执行文件资源:rcedit实用指南 【免费下载链接】rcedit Command line tool to edit resources of exe 项目地址: https://gitcode.com/gh_mirrors/rc/rcedit rcedit是一款功能强大的命令行工具,专为编辑Windows可执行文件资源…...

10分钟掌握Unity游戏翻译神器:XUnity.AutoTranslator完全指南

10分钟掌握Unity游戏翻译神器:XUnity.AutoTranslator完全指南 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 还在为外语Unity游戏而烦恼吗?XUnity.AutoTranslator正是你需要的终极…...

ORB-SLAM3 从理论到代码实现(四):Optimizer 尺度与重力优化

1. 前言 InertialOptimization共有4个重载 // Inertial pose-graph void static InertialOptimization(Map *pMap, Eigen::Matrix3d &Rwg, double &scale, Eigen::Vector3d &bg, Eigen::Vector3d &ba, bool bMono, Eigen::MatrixXd &covInertial, bool …...

Nginx配置实战:手把手教你修复CSP、X-XSS-Protection等10个常见安全响应头漏洞

Nginx安全响应头配置实战:10个关键漏洞修复指南 当安全扫描工具在你的Nginx服务器上标记出一连串"响应头缺失"警告时,那种感觉就像发现自家大门没锁一样令人不安。我曾为一家电商平台做安全审计,他们的扫描报告显示缺少8个关键安全…...

可重构软件无线电平台软硬件实现方法【附代码】

✨ 本团队擅长数据搜集与处理、建模仿真、程序设计、仿真代码、EI、SCI写作与指导,毕业论文、期刊论文经验交流。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,查看文章底部二维码(1)基于Zynq SoC的动态部分可重构基带处理架构&#x…...

HomeSpan实战:如何用Arduino IDE构建多功能智能家居配件

HomeSpan实战:如何用Arduino IDE构建多功能智能家居配件 【免费下载链接】HomeSpan HomeKit Library for the Arduino-ESP32 项目地址: https://gitcode.com/gh_mirrors/ho/HomeSpan HomeSpan是一款专为Arduino-ESP32设计的HomeKit库,它能帮助开发…...

tabula-java扩展开发指南:如何实现自定义表格提取算法

tabula-java扩展开发指南:如何实现自定义表格提取算法 【免费下载链接】tabula-java Extract tables from PDF files 项目地址: https://gitcode.com/gh_mirrors/ta/tabula-java 在处理PDF文件时,从复杂格式中准确提取表格数据一直是开发者面临的…...

车辆换挡缓冲阀结构设计与优化AMESim仿真【附代码】

✨ 本团队擅长数据搜集与处理、建模仿真、程序设计、仿真代码、EI、SCI写作与指导,毕业论文、期刊论文经验交流。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,查看文章底部二维码(1)缓冲阀动力学建模与AMESim参数化仿真:所…...

AI智能体开发新范式:引入节奏与记忆系统优化长期任务执行

1. 项目概述:当AI智能体学会“呼吸”与“节奏”在AI智能体开发领域,我们常常陷入一个误区:追求极致的单次响应速度与逻辑推理的深度,却忽略了智能体作为一个持续运行的“生命体”所应有的“节奏感”。想象一下,一个不知…...

ighack高级配置技巧:如何优化攻击性能与匿名性

ighack高级配置技巧:如何优化攻击性能与匿名性 【免费下载链接】ighack Hack Instagram From Termux With Help of Tor 项目地址: https://gitcode.com/gh_mirrors/ig/ighack ighack是一款专为Termux环境设计的Instagram攻击工具,通过Tor网络提供…...

Rust版LangChain:llm-chain构建高性能LLM应用实践

1. 项目概述:为什么我们需要一个Rust版的LangChain?如果你最近在折腾大语言模型应用,大概率听说过LangChain。它用Python写成,通过“链”的概念把提示词、工具调用、记忆管理这些功能串起来,让构建复杂AI应用变得像搭积…...

Unity Timeline实战:用自定义对话轨道打造电影级游戏过场动画(附完整资源)

Unity Timeline实战:用自定义对话轨道打造电影级游戏过场动画(附完整资源) 在《巫师3》的凯尔莫罕雪夜对话中,杰洛特与叶奈法的眼神交错配合台词节奏的微妙停顿,让玩家仿佛置身于真实的电影场景。这种沉浸式叙事体验的…...

构建企业级.NET代码编辑器:ScintillaNET终极架构解析

构建企业级.NET代码编辑器:ScintillaNET终极架构解析 【免费下载链接】ScintillaNET A Windows Forms control, wrapper, and bindings for the Scintilla text editor. 项目地址: https://gitcode.com/gh_mirrors/sc/ScintillaNET 在.NET桌面应用开发领域&a…...

VSCode 2026农业插件开发,从Node.js 20.12到Rust WASM桥接——跨平台低功耗灌溉控制插件落地全链路

更多请点击: https://intelliparadigm.com 第一章:VSCode 2026农业物联网插件开发背景与架构概览 随着精准农业与边缘智能的加速融合,面向田间部署的轻量级开发工具需求激增。VSCode 2026 版本正式将农业物联网(Agri-IoT&#xf…...

ai辅助android开发:让快马帮你编写自定义view与复杂动画

今天在做一个音频可视化功能时,遇到了自定义View绘制动态波形图的难题。作为一个Android开发者,我们都知道自定义View是进阶必备技能,但每次写起来都要处理测量、绘制、动画等一堆细节,特别耗时。好在现在有了AI辅助开发工具&…...

【限时解密】Docker边缘优化“静默失效”现象:当--cgroup-parent被忽略时,K3s集群吞吐量暴跌63%的隐蔽根源

更多请点击: https://intelliparadigm.com 第一章:Docker边缘优化 在资源受限的边缘设备(如树莓派、Jetson Nano 或工业网关)上运行 Docker 容器时,镜像体积、启动延迟与内存占用成为关键瓶颈。传统构建方式生成的镜像…...

西门子PLC数据采集(一):通过.net采集西门子PLC数据的方法

一、前言: (本文对于会一点.net Core开发的PLC自动化工程师及了解西门子PLC的.net 软件开发工程师比较友好) 谈到通过.net Core采集西门子PLC的数据,其实不仅仅涉及到采集,其中还包括数据的存储、展示、分析、数据上…...

Buck电路电感值、电容值计算

0. 结论当然,实际使用的电容计算值要考虑负载的波动,一般来说其电容值要远远大于此计算值1. Buck电路的伏秒平衡通常BUCK芯片的基本拓扑如下:内部集成了开关管以及其驱动器,外围电路包括输入、电感、二极管以及输出电容。图1 与 图…...

macOS Python 安装

目录 一、确认系统环境 二、安装 (一)下载安装包 (二)安装过程 三、配置环境变量 四、验证安装 一、确认系统环境 在安装 Python 之前,我们先简单了解一下自己的 MACOS 系统。可以点击屏幕左上角的苹果菜单&…...

半导体设计数据管理挑战与ENOVIA DesignSync解决方案

1. 半导体设计数据管理的行业挑战与解决方案在当今半导体行业,芯片复杂度正以惊人的速度增长。过去二十年里,芯片复杂度提升了1000倍,而工程师的生产力提升却远远跟不上这一步伐。这种差距导致了开发成本呈指数级增长,同时还要面对…...

揭秘书匠策AI:毕业论文写作的“超级外挂”!

在学术的征途上,毕业论文如同一座巍峨的山峰,让无数学生望而生畏。选题迷茫、资料难寻、逻辑混乱、格式繁琐……这些问题像一道道难以逾越的鸿沟,横亘在每一位即将毕业的学生面前。但别怕,今天我要给大家揭秘一个“超级外挂”——…...

需要抢答器功能?知识竞赛软件选购指南

🎯 需要抢答器功能?知识竞赛软件选购指南精准抢答 公平竞技 一键掌控📌 引言无论是学校学科竞赛、企业技能比拼,还是社区趣味活动,一场精彩的知识竞赛都离不开紧张刺激的抢答环节。传统的硬件抢答器存在布线繁琐、设…...

JAVA自营商城小程序APP商城源码单商户源码的uniapp代码片段

以下为JAVA自营商城小程序/APP单商户源码的Uniapp核心功能代码片段&#xff0c;包含商品展示、购物车管理、订单支付等模块&#xff1a;1. 商品列表页&#xff08;pages/product/list.vue&#xff09;vue<template><view class"container"><!-- 搜索栏…...

QMT自动交易逆回购实战:我的资金利用率提升20%的配置心得与三个常见坑

QMT自动交易逆回购实战&#xff1a;我的资金利用率提升20%的配置心得与三个常见坑 在量化交易的世界里&#xff0c;逆回购因其低风险特性成为资金管理的重要工具。但很多QMT用户发现&#xff0c;简单的自动化策略往往无法充分发挥资金效率——你可能遇到过14:58分下单失败、价格…...

AI构建赛博朋克任务控制台:纯前端模拟架构与交互设计解析

1. 项目概述&#xff1a;一个由AI构建的赛博朋克任务控制台如果你和我一样&#xff0c;对科幻电影里那些闪烁着霓虹光芒、数据流实时滚动的任务控制中心着迷&#xff0c;同时又对AI驱动的Web开发充满好奇&#xff0c;那么这个名为“OpenClaw Mission Control v3”的项目绝对值得…...

如何用自然语言构建专属RAG智能体:5分钟快速上手指南

如何用自然语言构建专属RAG智能体&#xff1a;5分钟快速上手指南 【免费下载链接】rags Build ChatGPT over your data, all with natural language 项目地址: https://gitcode.com/gh_mirrors/ra/rags RAGs是一款基于Streamlit开发的应用程序&#xff0c;能够让你通过自…...

无人机巡检中输电线路缺陷检测数据集(YOLO格式)

摘要&#xff1a;本数据集针对输电线路缺陷检测中缺陷特征识别难、人工巡检效率低等问题&#xff0c;构建了包含78,704张图像、356,160个标注框的YOLO格式数据集&#xff0c;涵盖绑线缺陷、并沟线夹缺陷、耐张线夹缺陷、锈蚀缺陷、杆塔损伤五类常见输电线路缺陷&#xff0c;支持…...

终极Voyager代码统计报告:语言分布与复杂度深度分析

终极Voyager代码统计报告&#xff1a;语言分布与复杂度深度分析 【免费下载链接】Voyager An Open-Ended Embodied Agent with Large Language Models 项目地址: https://gitcode.com/gh_mirrors/voya/Voyager Voyager作为一款基于大型语言模型的开放式具身智能体&#…...

d3dxSkinManage缩略图功能终极配置指南:三步搞定个性化皮肤管理

d3dxSkinManage缩略图功能终极配置指南&#xff1a;三步搞定个性化皮肤管理 【免费下载链接】d3dxSkinManage 3dmigoto skin mods manage tool 项目地址: https://gitcode.com/gh_mirrors/d3/d3dxSkinManage 还在为游戏皮肤管理工具的缩略图功能感到困惑吗&#xff1f;d…...