自学SLAM(5)《第三讲:李群和李代数》作业

前言

在这里插入图片描述

小编研究生的研究方向是视觉SLAM,目前在自学,本篇文章为初学高翔老师课的第三次作业。

文章目录

  • 前言
  • 1.群的性质
  • 2.验证向量叉乘的李代数性质
  • 3.推导 SE(3) 的指数映射
  • 4.伴随
  • 5.轨迹的描绘
  • 6.* 轨迹的误差(附加题)


1.群的性质

课上我们讲解了什么是群。请根据群定义,求解以下问题:

  1. {Z, +} 是否为群?若是,验证其满⾜群定义;若不是,说明理由。
  2. {N, +} 是否为群?若是,验证其满⾜群定义;若不是,说明理由。
    其中 Z 为整数集, N 为⾃然数集。

在这里我再写一下群的定义,满足“凤姐咬你”的就是群,也就是四个性质,如图:
注意:
图中吧集合记作A,运算记作·(·不代表乘法),群可以记作G=(A,·)
对于整数的逆,进行的是乘法它的逆就是它的倒数 进行的是加法,它的逆就是它的相反数
在这里插入图片描述
对于 {Z, +} ,设a1∈Z,a2∈Z,a3∈Z
①封闭性:对于任意的a1∈Z,a2∈Z都有a1+a2∈Z,满足封闭性。
②结合性:对于任意的a1∈Z,a2∈Z,a3∈Z,都有(a1+a2)+a3=a1+(a2+a3),满足结合性。
③幺元:Z中存在0∈Z,对于任意的a∈Z,有a+0=a+0=a,因此满足幺元。
④逆:对于任意的a∈Z,有-a∈Z,a+(-a)=0,任何整数加上它的相反数等于幺元0,所以逆元素是其相反数,因此满足逆。

对于 {N, +}
①封闭性:两个自然数相加依然是自然数,封闭性成立。
②结合性:两个自然数相加可以互换位置,结合性成立。
③幺元:任何自然数与0相加仍然是自然数本身,幺元成立。
④逆: 自然数都是非负数(加法中,自然数的逆已经不属于自然数了),所以两个大于等于0的数相加不可能为0,逆不成立。

2.验证向量叉乘的李代数性质

我们说向量和叉乘运算构成了李代数,现在请你验证它。书中对李代数的定义为:李代数由⼀个集合V,⼀个数域 F 和⼀个⼆元运算 [,]组成。如果它们满⾜以下⼏条性质,称 (V, F, [, ]) 为⼀个李代数,记作g。
注意:自反性是指自己与自己的运算为零。
在这里插入图片描述

解题过程如下:
在这里插入图片描述

3.推导 SE(3) 的指数映射

课上给出了 SO(3) 的指数映射推导,但对于 SE(3),仅介绍了结论,没有给出详细推导。请你完成 SE(3)指数映射部分,有关左雅可⽐的详细推导。
在这里插入图片描述

解题过程如下:
在这里插入图片描述
在这里插入图片描述

4.伴随

在 SO(3) 和 SE(3) 上,有⼀个东西称为伴随(Adjoint)。下⾯请你证明 SO(3)伴随的性质。在这里插入图片描述

解题过程如下:
在这里插入图片描述
完整的 SO(3) 和 SE(3) 性质见下图
在这里插入图片描述
在这里插入图片描述

5.轨迹的描绘

我们通常会记录机器⼈的运动轨迹,来观察它的运动是否符合预期。⼤部分数据集都会提供标准轨迹以供参考,如 kitti、 TUM-RGBD 等。这些⽂件会有各⾃的格式,但⾸先你要理解它的内容。记世界坐标系为 W,机器⼈坐标系为 C,那么机器⼈的运动可以⽤ TWC 或TCW来描述。现在,我们希望画出机器⼈在世界当中的运动轨迹,请回答以下问题:
在这里插入图片描述

解题过程如下:
世界坐标系W(world),机器人坐标系也就是相机坐标系C(camera)

①Twc指的是从世界坐标系原点到相机中心的平移向量,(机器人(相机)坐标系的原点在世界坐标系中的坐标)
世界坐标系是不随相机运动变化的,因此可以认为Twc是机器人相对于原点坐标在移动, 移动可视化在观察者眼中就是机器人的运动轨迹。

如果我们假设机器人坐标系的原点为Oc,此时的Ow就是这个原点在世界坐标系下的坐标:
Ow=TwcOc=twc
这正是Twc的平移部分。因此,可以从Twc中直接看到相机在何处,这也就是我们所说的Twc更为直观。因此在可视化程序里,轨迹文件储存的是Twc而不是Tcw

我想这也是第一问问我们Twc而不是问·Tcw的原因。


首先我们需要安装Sophus

    git clone https://github.com/strasdat/Sophus.gitcd Sophusgit checkout a621ffmkdir buildcd buildcmake ..makesudo make install

但是我们会编译失败,按照如下操作修改后,重新编译即可。
解决方法:打开 Sophus/sophus/so2.cpp文件修改报错内容

//将
SO2::SO2() 
{ unit_complex_.real() = 1.; unit_complex_.imag() = 0.; }
//改为
SO2::SO2() 
{ unit_complex_.real(1.); unit_complex_.imag(0.); }

这时候我们就可以安装成功Sophus了
在这里插入图片描述

draw_trajectory.cpp对应代码如下:

#include "sophus/so3.h"
#include "sophus/se3.h"
#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <Eigen/Core>
// need pangolin for plotting trajectory
#include <pangolin/pangolin.h>
using namespace std;
// path to trajectory file
string trajectory_file = "/home/zhe/1/lianxi/3/plotTrajectory/trajectory.txt";
class SE3d;
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>>);
int main(int argc, char **argv) {vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses;//文件读取器ifstream fin(trajectory_file);if (!fin) {cerr << "trajectory " << trajectory_file << " not found." << endl;}//如果eof()返回0,就没读完while (!fin.eof()) {//按照时间  平移 四元素的顺序定义并读取double time, tx, ty, tz, qx, qy, qz, qw;fin >> time >> tx >> ty >> tz >> qx >> qy >> qz >> qw;Sophus::SE3 p1(Eigen::Quaterniond(qw, qx, qy, qz), Eigen::Vector3d(tx, ty, tz));poses.push_back(p1);}fin.close();// end your code here// draw trajectory in pangolinDrawTrajectory(poses);return 0;
}
/************************************************************************/
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses) {if (poses.empty()) {cerr << "Trajectory is empty!" << endl;return;}// create pangolin window and plot the trajectorypangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);glEnable(GL_DEPTH_TEST);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);pangolin::OpenGlRenderState s_cam(pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0));pangolin::View &d_cam = pangolin::CreateDisplay().SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f).SetHandler(new pangolin::Handler3D(s_cam));while (pangolin::ShouldQuit() == false) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);d_cam.Activate(s_cam);glClearColor(1.0f, 1.0f, 1.0f, 1.0f);glLineWidth(2);for (size_t i = 0; i < poses.size() - 1; i++) {glColor3f(1 - (float) i / poses.size(), 0.0f, (float) i / poses.size());glBegin(GL_LINES);auto p1 = poses[i], p2 = poses[i + 1];glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);glEnd();}pangolin::FinishFrame();usleep(5000);   // sleep 5 ms}
}

CmakeLists.txt对应代码如下:

cmake_minimum_required(VERSION 2.8)
project(draw_trajectory)
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )
set( CMAKE_BUILD_TYPE "Debug" )find_package(Pangolin REQUIRED)
find_package(Sophus REQUIRED)
include_directories("/usr/include/eigen3")
include_directories(${Pangolin_INCLUDE_DIRS}${Sophus_INCLUDE_DIR}
)
add_executable(trajectory draw_trajectory.cpp)
target_link_libraries(trajectory${Pangolin_LIBRARIES}${Sophus_LIBRARIES})

然后

cd  SLAM4track//自己建的文件夹
cat CMakeLists.txt
cd build
cmake ..
make
./trajectory

在这里插入图片描述
该图中:轨迹首尾颜色不一样,通过观察,发现是着色函数设置的颜色随位置变化.

6.* 轨迹的误差(附加题)

本题为附加题。 除了画出真实轨迹以外,我们经常需要把 SLAM 估计的轨迹与真实轨迹相⽐较。下⾯说明⽐较的原理,请你完成⽐较部分的代码实现。
在这里插入图片描述

CMakeLists.txt对应代码

cmake_minimum_required(VERSION 2.8)
project(wucha)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")#set(CMAKE_CXX_STANDARD 11)
#set(CMAKE_BUILD_TYPE "Release")
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
#添加库
#sophus
#  为使用 sophus,需要使用find_package命令找到它并赋给Sophus_INCLUDE_DIRS
find_package(Sophus REQUIRED)
include_directories(${Sophus_INCLUDE_DIRS})
#Pangolin生成一个libPangolin动态链接库
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})include_directories("/usr/include/eigen3")
#编译
add_executable(plotError compare_tra.cpp)
#链接
#target_link_libraries(plotError Sophus::Sophus)
target_link_libraries(plotError ${Sophus_LIBRARIES} )target_link_libraries(plotError ${Pangolin_LIBRARIES})

compare_tra.cpp对应代码:

#include "sophus/so3.h"
#include "sophus/se3.h"
#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
// need pangolin for plotting trajectory
#include <pangolin/pangolin.h>
using namespace std;
// path to trajectory file
string gt_file = "../groundtruth.txt";
string est_file = "../estimated.txt";
vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,est_poses;
// function for plotting trajectory, don't edit this code
// start point is red and end point is blue
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> est_poses);
void readData(string filepath);
void ErrorTrajectory();
int main(int argc, char **argv) {readData(gt_file);readData(est_file);ErrorTrajectory();// draw trajectory in pangolinDrawTrajectory(gt_poses,est_poses);//打印两条轨迹return 0;/// implement pose reading code// start your code here (5~10 lines)
}
/*******************************************************************************************/
void readData(string filepath){vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses;ifstream infile(filepath);double t1,tx,ty,tz,qx,qy,qz,qw;string line;if(infile){while(getline(infile,line)){stringstream record(line);    //从string读取数据record>>t1>>tx>>ty>>tz>>qx>>qy>>qz>>qw;Eigen::Vector3d t(tx,ty,tz);Eigen::Quaterniond q = Eigen::Quaterniond(qw,qx,qy,qz).normalized();  //四元数的顺序要注意Sophus::SE3 SE3_qt(q,t);poses.push_back(SE3_qt);}}else{cout<<"没找到这个文件"<<endl;}if(filepath==gt_file){gt_poses = poses;}else if( filepath==est_file ){est_poses = poses;}else{ cout<<"读文件出错"<<endl;}infile.close();
}
/*******************************************************************************************/
void ErrorTrajectory()
{double RMSE = 0;Eigen::Matrix<double ,6,1> se3;vector<double> error;for(int i=0;i<gt_poses.size();i++){se3=(gt_poses[i].inverse()*est_poses[i]).log();  //这里的se3为向量形式,求log之后是向量形式//cout<<se3.transpose()<<endl;error.push_back( se3.squaredNorm() );  //二范数// cout<<error[i]<<endl;}for(int i=0; i<gt_poses.size();i++){RMSE += error[i];}RMSE /= double(error.size());RMSE = sqrt(RMSE);cout<<RMSE<<endl;
}
/*******************************************************************************************/
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> est_poses) {if (gt_poses.empty()) {cerr << "groundtruth is empty!" << endl;return;}if (est_poses.empty()) {cerr << "estimated is empty!" << endl;return;}// create pangolin window and plot the trajectorypangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);glEnable(GL_DEPTH_TEST);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);pangolin::OpenGlRenderState s_cam(pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0));pangolin::View &d_cam = pangolin::CreateDisplay().SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f).SetHandler(new pangolin::Handler3D(s_cam));while (pangolin::ShouldQuit() == false) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);d_cam.Activate(s_cam);glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//窗口,rgbaglLineWidth(2);for (size_t i = 0; i < est_poses.size() - 1; i++) {glColor3f(1 - (float) i / est_poses.size(), 0.0f, (float) i / est_poses.size());glBegin(GL_LINES);auto p1 = est_poses[i], p2 = est_poses[i + 1];glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);glEnd();}for (size_t i = 0; i < gt_poses.size() - 2; i++) {glColor3f(0.f, 0.8f, 0.f);//绿色glBegin(GL_LINES);auto p3 = gt_poses[i], p4 = gt_poses[i + 1];//只显示tx,ty,tzglVertex3d(p3.translation()[0], p3.translation()[1], p3.translation()[2]);glVertex3d(p4.translation()[0], p4.translation()[1], p4.translation()[2]);glEnd();}pangolin::FinishFrame();usleep(5000);   // sleep 5 ms}
}

然后运行,运行命令如下:

cd track_compare
cd build
cmake ..
make
./plotError

在这里插入图片描述
本人自学SLAM,如果错误,还请见谅留言提醒
希望的我的博客对你有帮助!

在这里插入图片描述

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

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

相关文章

Spring Security—Spring MVC 整合

目录 一、EnableWebMvcSecurity 二、MvcRequestMatcher 三、AuthenticationPrincipal 四、异步 Spring MVC 整合 五、Spring MVC 和 CSRF 整合 1、自动包含 Token 2、解析 CsrfToken Spring Security提供了一些与Spring MVC的可选整合。本节将进一步详细介绍这种整合。 …

UML中类之间的六种主要关系

UML中类之间的六种主要关系: 继承&#xff08;泛化&#xff09;&#xff08;Inheritance、Generalization&#xff09;, 实现&#xff08;Realization&#xff09;&#xff0c;关联&#xff08;Association)&#xff0c;聚合&#xff08;Aggregation&#xff09;&#xff0c;组…

Linux--进程替换

1.什么是进程替换 在fork函数之后&#xff0c;父子进程各自执行代码的一部分&#xff0c;但是如果子进程想要执行一份全新的程序呢&#xff1f; 通过进程替换来完成&#xff0c;进程替换就是父子进程代码发生写时拷贝&#xff0c;子进程执行自己的功能。 程序替换就是通过特定的…

python 笔记:h5py 读取HDF5文件

1 HDF5文件 HDF5 是 Hierarchical Data Format version 5 的缩写&#xff0c;是一种用于存储和管理大量数据的文件格式一个h5py文件可以看作是 “dataset” 和 “group” 二合一的容器 dataset : 数据集&#xff0c;像 numpy 数组一样工作group : 包含了其它 dataset 和 其它 …

GZ035 5G组网与运维赛题第4套

2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项&#xff08;高职组&#xff09; 赛题第4套 一、竞赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通&#xff08;35分&#xff09; 子任务1&#xff1a;5G公共网络部署与调试&#xff08;15分&#xff09; 子…

C语言_断言assert详解

一、assert定义 assert() 的用法像是一种"契约式编程"&#xff0c;在我的理解中&#xff0c;其表达的意思就是&#xff0c;程序在我的假设条件下&#xff0c;能够正常良好的运作&#xff0c;其实就相当于一个 if 语句&#xff1a; if(假设成立) {程序正常运行&…

(免费领源码) Asp.Net#SQL Server校园在线投票系统10557-计算机毕业设计项目选题推荐

摘 要 随着互联网大趋势的到来&#xff0c;社会的方方面面&#xff0c;各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去&#xff0c;而其中最好的方式就是建立网络管理系统&#xff0c;并对其进行信息管理。由于现在网络的发达&#xff0c;校园投票通过网…

java - IDEA IDE - 设置字符串断点

文章目录 java - IDEA IDE - 设置字符串断点概述笔记END java - IDEA IDE - 设置字符串断点 概述 IDE环境为IDEA2022.3 在看一段序列化的代码, 想找出报错抛异常那个点, 理解一下代码实现. 因为序列化代码实现在第三方jar包中, 改不了(只读的). 根本数不清第几次才会开始报…

java基础之泛型

泛型 泛型是在JDK1.5增加的功能&#xff0c;在没有泛型之前&#xff0c;从集合中取出来的每一个对象都必须进行强制类型转换&#xff0c;如果有人插入了错误类型的对象&#xff0c;在运行时的转换就会出现问题&#xff0c;有了泛型之后&#xff0c;这些问题就会在编译期暴露出来…

OpenCV学习(五)——图像基本操作(访问图像像素值、图像属性、感兴趣区域ROI和图像边框)

图像基本操作 5. 图像基本操作5.1 访问像素值并修改5.2 访问图像属性5.2 图像感兴趣区域ROI5.3 拆分和合并图像通道5.4 为图像设置边框&#xff08;填充&#xff09; 5. 图像基本操作 访问像素值并修改访问图像属性设置感兴趣区域&#xff08;ROI&#xff09;分割和合并图像 …

如何在vscode中添加less插件

Less &#xff08;Leaner Style Sheets 的缩写&#xff09; 是一门向后兼容的 CSS 扩展语言。它对CSS 语言增加了少许方便的扩展&#xff0c;通过less可以编写更少的代码实现更强大的样式。但less不是css&#xff0c;浏览器不能直接识别&#xff0c;即浏览器无法执行less代码&a…

2023年正版win10/win11系统安装教学(纯净版)

第一步&#xff1a;准备一个8G容量以上的U盘。 注意&#xff0c;在制作系统盘时会格式化U盘&#xff0c;所以最好准备个空U盘&#xff0c;防止资料丢失。 第二步&#xff1a;制作系统盘。 安装win10 进入windows官网 官网win10下载地址&#xff1a;https://www.microsoft.c…

安卓开发实例:随机数

点击按钮生成一个1-100之间的随机数 activity_random_number.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutxmlns:android"http://schemas.android.com/apk/res/android"xmlns:a…

conda 复制系统环境

直接复制 想要通过 conda 直接复制一个已存在的环境&#xff0c;你可以使用 conda create 命令并配合 --clone 参数。以下是具体步骤&#xff1a; 查看现有的环境: 首先&#xff0c;你可以使用以下命令来查看所有的 conda 环境&#xff1a; conda env list这会给你一个环境列表…

TypeScript -类型断言的简单理解

类型断言是干啥的 类型断言 &#xff1a; 是手动的给某个变量 指定类型&#xff0c;从而可以方便操作该类型的属性和方法。 类型断言的两种写法 方式一 &#xff1a; 变量名 as 类型 let x: number | string abc; console.log((x as string).length); // 输出 3 &#xff0c;因…

10月Java行情 回暖?

最近面试 Java 开发&#xff0c;看看行情。总的来说没有前几年好&#xff0c;真的是互联网寒冬&#xff0c;面试机会都比以前少了不少&#xff0c;很多互联网公司都在降本增效&#xff0c;只招少量的人。如果你能约到面试就好好珍惜&#xff0c;记住不要裸辞&#xff0c;不要裸…

记一次vue3实现TRSP大华相机拉流的经历

一、背景 业务场景&#xff0c;大华IP相机安装在A城市某建筑场所&#xff0c;工控机是内网通过4G流量卡上网&#xff0c;工控机通过相机采集数据后做故障识别并上传故障信息到地面服务器&#xff0c;地面服务器在B城市。 现需要在地面服务器提供的WEB界面上实现IP相机实时拉流…

linux套接字选项API

获取套接字的选项值(getsockopt) 【头文件】 #include <sys/types.h> #include <sys/socket.h> 【函数原型】 int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen); 【函数功能】 用于获取一个套接字的选项 【参数含义】 […

P1025 [NOIP2001 提高组] 数的划分 题解

文章目录 题目描述输入格式输出格式样例样例输入样例输出 数据范围提示思路与部分实现完整代码 题目描述 将整数 n n n 分成 k k k 份&#xff0c;且每份不能为空&#xff0c;任意两个方案不相同&#xff08;不考虑顺序&#xff09;。 例如&#xff1a; n 7 n7 n7&#xf…

C++编译与运行:其一、静态类型和动态类型

一、什么是静态类型和动态类型&#xff1f; 先说结论&#xff1a;编译期间可以明确的类型是静态类型&#xff1b;运行期间才能明确的类型是动态类型。 后半句可能有点不好理解&#xff0c;通俗地说&#xff0c;需要通过执行代码才能明确的类型是动态类型。 假如我们有两个类&…