《视觉SLAM十四讲》ch13 设计SLAM系统 相机轨迹实现

前言


        相信大家在slam学习中,一定会遇到slam系统的性能评估问题。虽然有EVO这样的开源评估工具,我们也需要自己了解系统生成的trajectory.txt的含义,方便我们更好的理解相机的运行跟踪过程。

项目配置如下:

 数据解读:


        这是KITTI数据集中的轨迹 (trajectory.txt),通常用于视觉里程计(VO)或SLAM的评估。文件格式通常是 12个浮点数,表示一个 4×4 变换矩阵(刚体变换),具体来说,每一行表示一个位姿(相机的姿态和位置),格式如下:

解析每列数据:

  1. 前3×3部分(列1到列9):旋转矩阵 RRR(描述相机的旋转)
  2. 列10到列12:平移向量 ttt(描述相机的位移)

项目解读:

run_kitti_stereo.cpp的vo->Init()初始化前段可视化模块viewer_,后端backend_,地图map_等slam子系统。

进入主线程的特征追踪:

void VisualOdometry::Run() {while (1) {LOG(INFO) << "VO is running";if (Step() == false) {// 退出,并保存轨迹frontend_->SaveTrajectory("/home/xulei/下载/slambook2/ch13/trajectory.txt");break;}}backend_->Stop();viewer_->Close();LOG(INFO) << "VO exit";
}
bool VisualOdometry::Step() {if (dataset_->current_image_index_ > 3681) {  //判断避免异常退出,数据集大小。return false;}Frame::Ptr new_frame = dataset_->NextFrame();if (new_frame == nullptr) return false;auto t1 = std::chrono::steady_clock::now();bool success = frontend_->AddFrame(new_frame);auto t2 = std::chrono::steady_clock::now();auto time_used =std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);LOG(INFO) << "VO cost time: " << time_used.count() << " seconds.";return success;}}  // namespace myslam

 其中,nextframe()方法是读取数据集,并进行当前帧的创建。

  Frame::Ptr new_frame = dataset_->NextFrame();
AddFrame(new_frame)方法是进入特征提取与追踪线程。
  bool success = frontend_->AddFrame(new_frame);

其中,很重要的一点是跟踪的质量评估:

bool Frontend::AddFrame(myslam::Frame::Ptr frame) {current_frame_ = frame;switch (status_) {case FrontendStatus::INITING:StereoInit();break;case FrontendStatus::TRACKING_GOOD:case FrontendStatus::TRACKING_BAD:Track();break;case FrontendStatus::LOST:Reset();break;}// 让线程暂停 0.5 秒std::this_thread::sleep_for(std::chrono::milliseconds(1000));last_frame_ = current_frame_;return true;
}

前端的工作大多在Frontend类中完成。

 该部分也是我们代码改进,并进一步实现轨迹的保存工作。

注意,我们这里不考虑关键帧的处理,即后端的优化的实时的影响,直接在位姿计算完成就推算世界坐标下的轨迹。

首先,在frontend.h类中,加入方法,与轨迹变量。

void SaveTrajectory(const std::string &filename);  // 轨迹保存
std::vector<Eigen::Matrix<double, 3, 4>> trajectory_;

具体在.cpp文件实现。

 找到StereoInit(),Track()函数,将函数替换为:(这里代码比较冗余,也可以封装成一个方法调用)

bool Frontend::StereoInit() {// **转换到世界坐标系,并存储 3×4 变换矩阵**SE3 T_c_w = current_frame_->Pose();   // 获取相机位姿 T_c_wSE3 T_w_c = T_c_w.inverse();          // 计算世界坐标系到相机的变换 T_w_cEigen::Matrix3d R_w_c = T_w_c.rotationMatrix().matrix();  // 获取旋转矩阵 (3×3)Eigen::Vector3d t_w_c = T_w_c.translation();              // 获取平移向量 (3×1)// 记录 3×4 矩阵Eigen::Matrix<double, 3, 4> world_pose;world_pose.block<3,3>(0,0) = R_w_c;  // 旋转部分world_pose.block<3,1>(0,3) = t_w_c;  // 平移部分// **记录轨迹**trajectory_.push_back(world_pose);int num_features_left = DetectFeatures();int num_coor_features = FindFeaturesInRight();if (num_coor_features < num_features_init_) {return false;}bool build_map_success = BuildInitMap();if (build_map_success) {status_ = FrontendStatus::TRACKING_GOOD;if (viewer_) {viewer_->AddCurrentFrame(current_frame_);viewer_->UpdateMap();}return true;}return false;
}
ool Frontend::Track() {if (last_frame_) {current_frame_->SetPose(relative_motion_ * last_frame_->Pose());}int num_track_last = TrackLastFrame();tracking_inliers_ = EstimateCurrentPose();printf("num_track_last = %d", tracking_inliers_);if (tracking_inliers_ > num_features_tracking_) {status_ = FrontendStatus::TRACKING_GOOD;} else if (tracking_inliers_ > num_features_tracking_bad_) {status_ = FrontendStatus::TRACKING_BAD;} else {status_ = FrontendStatus::LOST;}InsertKeyframe();relative_motion_ = current_frame_->Pose() * last_frame_->Pose().inverse();// **转换到世界坐标系,并存储 3×4 变换矩阵**SE3 T_c_w = current_frame_->Pose();   // 获取相机位姿 T_c_wSE3 T_w_c = T_c_w.inverse();          // 计算世界坐标系到相机的变换 T_w_cEigen::Matrix3d R_w_c = T_w_c.rotationMatrix().matrix();  // 获取旋转矩阵 (3×3)Eigen::Vector3d t_w_c = T_w_c.translation();              // 获取平移向量 (3×1)// 记录 3×4 矩阵Eigen::Matrix<double, 3, 4> world_pose;world_pose.block<3,3>(0,0) = R_w_c;  // 旋转部分world_pose.block<3,1>(0,3) = t_w_c;  // 平移部分// **记录轨迹**trajectory_.push_back(world_pose);// // **调试输出**// std::cout << std::fixed << std::setprecision(9);// std::cout << "Frame " << trajectory_.size()//           << " | R_w_c: \n" << R_w_c//           << "\n t_w_c: " << t_w_c.transpose() << std::endl;if (viewer_) viewer_->AddCurrentFrame(current_frame_);return true;
}

 最后实现其SaveTrajector()函数,保存.txt文件:

void Frontend::SaveTrajectory(const std::string &filename) {std::ofstream file(filename);if (!file.is_open()) {std::cerr << "Error opening trajectory file: " << filename << std::endl;return;}file << std::fixed << std::setprecision(9);  // 设定输出精度for (const auto &pose : trajectory_) {// 提取旋转和平移部分(假设 trajectory_ 存储的是 3×4 矩阵)Eigen::Matrix3d R_w_c = pose.block<3,3>(0,0);Eigen::Vector3d t_w_c = pose.block<3,1>(0,3);// 写入文件file << R_w_c(0,0) << " " << R_w_c(0,1) << " " << R_w_c(0,2) << " " << t_w_c(0) << " "<< R_w_c(1,0) << " " << R_w_c(1,1) << " " << R_w_c(1,2) << " " << t_w_c(1) << " "<< R_w_c(2,0) << " " << R_w_c(2,1) << " " << R_w_c(2,2) << " " << t_w_c(2) << std::endl;}file.close();std::cout << "Trajectory saved to " << filename << std::endl;
}

重新编译代码,运行:


最后得到,格式为:

0.999990331 -0.002488019 -0.003625839 0.001951625 0.002480646 0.999994849 -0.002036517 -0.007563952 0.003630888 0.002027503 0.999991353 0.681278434
0.999971701 -0.001201005 -0.007426599 -0.014921545 0.001173216 0.999992299 -0.003745129 -0.014889465 0.007431040 0.003736310 0.999965409 1.369789353
0.999938793 -0.000961412 -0.011022030 -0.041936008 0.000902450 0.999985264 -0.005353181 -0.016047572 0.011027014 0.005342906 0.999924926 2.069745033
0.999877013 0.000796693 -0.015662799 -0.064529202 -0.000890743 0.999981611 -0.005998610 -0.025888373 0.015657732 0.006011824 0.999859337 2.795177413
0.999790482 -0.000408300 -0.020465240 -0.073989152 0.000286856 0.999982336 -0.005936741 -0.036340712 0.020467303 0.005929627 0.999772939 3.531565772
0.999620212 0.011536263 -0.025026921 -0.084931933 -0.011736120 0.999900288 -0.007853525 -0.060457271 0.024933825 0.008144261 0.999655928 4.288284960
0.999448852 0.014043954 -0.030079214 -0.119917101 -0.014297214 0.999863991 -0.008221300 -0.075260961 0.029959663 0.008646818 0.999513707 5.057364575
0.999297794 0.014574258 -0.034518265 -0.151109538 -0.014807489 0.999869166 -0.006510771 -0.087394672 0.034418859 0.007017328 0.999382859 5.840754611
0.999154885 0.014253942 -0.038553105 -0.200260214 -0.014383673 0.999891776 -0.003089717 -0.114111359 0.038504892 0.003641641 0.999251776 6.608571213
0.999029138 0.013669141 -0.041880029 -0.238672274 -0.013693447 0.999906197 -0.000293535 -0.124407472 0.041872088 0.000866732 0.999122604 7.415410187
0.998883339 0.015144070 -0.044751887 -0.279161319 -0.015184780 0.999884542 -0.000569862 -0.138168200 0.044738090 0.001248774 0.998997970 8.233449862
0.998772936 0.014903271 -0.047228336 -0.316118436 -0.014963441 0.999887618 -0.000920710 -0.143100781 0.047209307 0.001626278 0.998883695 9.053884639
0.998375407 0.030955991 -0.047835896 -0.362929685 -0.031132074 0.999510955 -0.002940145 -0.168459719 0.047721487 0.004424599 0.998850881 9.881833862
0.998340888 0.030504872 -0.048835694 -0.420649005 -0.030656826 0.999527170 -0.002365388 -0.167085051 0.048740448 0.003858611 0.998804025


进一步用evo评估该系统:

evo_traj kitti  trajectory.txt  --ref=02.txt -p   --plot_mode xz

平面误差:

 相机的xyz轴的误差:

 

 相机的旋转:

 

 总结

这样实现的方法可能比较取巧,没有考虑后续的BA优化,后续也会持续的优化该系统,希望能给大家提供一些项目的改进灵感与slam的相机运动理解。

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

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

相关文章

软考高级信息系统管理工程师通关100题(21-40)附记忆口诀

文章目录 21.常用存储模式的技术与应用对比22.物联网架构23.云计算服务提供的资源层次24.大数据25.区块链26.人工智能27.虚拟现实VR28.IT治理的内涵29.IT 治理活动30.IT治理本质31.IT审计目标32.IT审计方法33.治理系统设计34.数据管理能力成熟度评估模型35.项目管理原则36.管理…

Redisson 分布式锁原理

加锁原理 # 如果锁不存在 if (redis.call(exists, KEYS[1]) 0) then# hash结构,锁名称为key,线程唯一标识为itemKey&#xff0c;itemValue为一个计数器。支持相同客户端线程可重入,每次加锁计数器1.redis.call(hincrby, KEYS[1], ARGV[2], 1);# 设置过期时间redis.call(pexpi…

主流加固方案深度剖析(梆梆/腾讯/阿里)

1. 加固技术演进与核心原理 1.1 移动端加固技术图谱 graph TD A[代码防护] --> A1[混淆] A --> A2[虚拟化] A --> A3[动态加载] B[数据防护] --> B1[资源加密] B --> B2[协议加密] C[运行时防护] --> C1[反调试] C --> C2[环境检测] C --> C…

大模型之蒸馏模型

蒸馏模型&#xff08;Distilled Model&#xff09;是一种通过知识蒸馏&#xff08;Knowledge Distillation&#xff09;技术训练得到的轻量级模型&#xff0c;其核心思想是将一个复杂的大模型&#xff08;称为教师模型&#xff09;的知识“迁移”到一个更小、更高效的模型&…

iPaaS集成平台中的API可视化编排能给企业带来什么作用

随着企业数字化转型的加速&#xff0c;API&#xff08;应用程序接口&#xff09;作为企业数字化资产的核心组成部分&#xff0c;其数量和复杂性不断增加。为了满足业务敏捷化交付的要求&#xff0c;API可视化编排平台应运而生。谷云科技作为这一领域的领先者&#xff0c;其API可…

演员马晓琳正式加入创星演员出道计划,开启演艺事业新篇章

3月19日&#xff0c;演员马晓琳正式加入“创星演员出道计划”&#xff0c;不仅得到参演都市爱情喜剧《和我结婚吧》角色的机会&#xff0c;还获得文旅精品网剧《醉梦灵州》的出演机会&#xff0c;自此开启全新影视之路。对表演抱有极大热情的马晓琳&#xff0c;相信未来可以凭借…

绿盟科技春招面试

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…

双碳战略下的电能质量革命:解码电力系统的健康密码

安科瑞顾强 在能源结构转型的深水区&#xff0c;电能质量正成为制约产业升级的隐形门槛。国家能源局数据显示&#xff0c;我国工业企业每年因电能质量问题造成的经济损失高达3000亿元&#xff0c;而新能源项目因并网质量问题导致的发电效率损失超过15%。在这场关乎能源安全的攻…

Microsoft Edge浏览器的取证分析(基于Chromium)

概述 早在2019年&#xff0c;微软就用Chromium替换了EdgeHTML浏览器引擎&#xff0c;这是微软支持谷歌Chrome浏览器的一个开源项目。通过切换到Chromium&#xff0c;Edge与Chrome浏览器共享一个共同的架构&#xff0c;这意味着用于Chrome浏览器调查的取证技术也适用于Edge。 …

python学智能算法(八)|决策树

【1】引言 前序学习进程中&#xff0c;已经对KNN邻近算法有了探索&#xff0c;相关文章链接为&#xff1a; python学智能算法&#xff08;七&#xff09;|KNN邻近算法-CSDN博客 但KNN邻近算法有一个特点是&#xff1a;它在分类的时候&#xff0c;不能知晓每个类别内事物的具…

RTSP/Onvif安防监控系统EasyNVR级联视频上云系统EasyNVS报错“Login error”的原因排查与解决

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

通信网络安全防护定级备案需要材料汇总

通信网络安全防护定级备案工作需要到指定的系统上先写基本信息&#xff0c;然后上传对应的材料&#xff0c;提交后会流转到地方通管局或部里审核。对于第一次使用该系统的朋友来说&#xff0c;通信网络安全防护定级备案需要什么材料是目前比较关注的问题。下面calm13就结合以往…

15:00面试,15:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

ORACLE 19.8版本数据库环境EXPDP导数据的报错处理

近期用户在做EXPDP导出时&#xff0c;报错异常termination终止;EXPDP本身是简单的功能并且这个环境也是经常做导出的&#xff0c;到底是什么原因导致了这个问题呢&#xff1f; 导出脚本报错&#xff1a; 分析导出日志&#xff0c;当时系统资源充足但是进程启动失败&#xff0c;…

【Editor】动态添加/移除宏定义

ProjectSetting中OtherSettings页签 执行工具指令 using UnityEditor; using UnityEngine; using System.Linq;public class Tools : Editor {//在菜单栏中点击自动添加[MenuItem("Tools/AddScriptingSymbols")]private static void AddScriptingSymbols(){//获取当…

Web-Machine-N7靶机实战攻略

1.安装并开启靶机 下载VirtualBox&#xff1a;https://www.virtualbox.org 导入虚拟机 设置为桥接模式 2.获取靶机IP Kali设为桥接模式 3.访问靶机 4.获取敏感目录文件和端口 gobuster dir -u http://172.16.2.68 -w /usr/share/wordlists/dirbuster/directory-list-2.3-me…

C语言实验:数组,指针实现问题求解

实验目的&#xff1a;掌握数组&#xff0c;指针的使用 实验内容&#xff1a; 1直接选择排序 2字符串运算 3交换数字 流程图&#xff1a; 1直接选择排序 2字符串运算 3交换数字 程序调试 1直接选择排序 1-1出现问题&#xff08;贴图并说明&#xff09; 错误原因&#xf…

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解 &#xff08;图1-1&#xff09; 一、鸿蒙中App、HAP、HAR、HSP是什么&#xff1f; &#xff08;1&#xff09;App Pack&#xff08;Application Package&#xff09; 是应用发布的形态&#xff0c;上架应用市场是以App Pa…

LiteIDE中配置golang编译生成无CMD窗口EXE的步骤

LiteIDE中配置golang编译生成无CMD窗口EXE的步骤 一、环境配置1、设置GOROOT‌2、配置GOPATH‌ 二、项目编译参数设置1、新建/打开项目‌2、修改编译配置‌3、其他优化选项&#xff08;可选&#xff09;‌ 三、构建与验证1、编译生成EXE‌2、验证无窗口效果‌ 四、注意事项 一、…

暗光增强技术研究进展与产品落地综合分析(2023-2025)

一、引言 暗光增强技术作为计算机视觉与移动影像领域的核心研究方向之一,近年来在算法创新、硬件适配及产品落地方面取得了显著进展。本文从技术研究与产业应用两个维度,系统梳理近三年(2023-2025)该领域的关键突破,并对比分析主流手机厂商的影像技术优劣势。 二、暗光增…