【SLAM】Sophus库的超详细解析

news2025/4/7 21:52:39

在视觉SLAM中,李群李代数是描述位姿比较常用的一种表达形式。但是,在Eigen中并不提供对它的支持,一个较好的李群和李代数的库是Sophus库,它很好的支持了SO3、so3、SE3、se3。


Sophus简介

代码仓库:https://github.com/strasdat/Sophus。

本文所对照的源码版本是2023.9.7日期,因此和网上其他的一些教程版本有些出入,但是总体相差不大。总体来说,差别在SO3的double构造函数、SO3的<<运算符重写都取消了等。

Sophus库是基于Eigen基础上开发的,继承了Eigen库中的定义的各个类。因此在使用Eigen库中的类时,既可以使用Eigen命名空间,也可以使用Sophus命名空间。比如:

Eigen::Matrix3d 和 Sophus::Matrix3d
Eigen::Vector3d 和 Sophus::Vector3d

此外,为了方便说明SE4和se4,Sophus库还typedef了Vector4d、Matrix4d、Vector6d和Matrix6d等,即:

Sophus::Vector4d
Sophus::Matrix4d
Sophus::Vector6d
Sophus::Matrix6d

Sophus使用教程

李群李代数的描述形式

Sophus库中李群李代数的描述形式如下:

// 李群SO3
namespace Sophus {
template <class Scalar_, int Options = 0>
class SO3;
using SO3d = SO3<double>;
using SO3f = SO3<float>;
}

// 李群SE3
namespace Sophus {
template <class Scalar_, int Options = 0>
class SE3;
using SE3d = SE3<double>;
using SE3f = SE3<float>;
}

template <class Scalar, int M, int Options = 0>
using Vector = Eigen::Matrix<Scalar, M, 1, Options>;

// 李代数so3
namespace Sophus {
template <class Scalar, int Options = 0>
using Vector3 = Vector<Scalar, 3, Options>;
using Vector3f = Vector3<float>;
using Vector3d = Vector3<double>;
}

//李代数se3
namespace Sophus {
template <class Scalar>
using Vector6 = Vector<Scalar, 6>;
using Vector6f = Vector6<float>;
using Vector6d = Vector6<double>;
}

可以看出:对于李群,Sophus直接定义了相应的类;而对于李代数,Sophus直接使用Eigen::Matrix<>定义了一个向量表示

其中,so3的3个值对应的就是旋转,se3的前3个值对应的就是平移,后3个值对应的就是旋转。但是,se3平移的3个值都不直接等于平移,都需要经过一些转换才行,如何转换,在下文将会讲述到。

那么,李群类的构造函数有哪些呢?

// 默认构造函数将单位四元数初始化为单位旋转
SOPHUS_FUNC SO3();

// 拷贝构造函数
SOPHUS_FUNC SO3(SO3 const& other);

// 从SO3Base的其他子类进行拷贝构造
template <class OtherDerived>
SOPHUS_FUNC SO3(SO3Base<OtherDerived> const& other);

// 基于旋转矩阵的构造函数
// 前提条件:旋转矩阵需要正交
static int constexpr N = 3;
using Transformation = Matrix<Scalar, N, N>;
SOPHUS_FUNC SO3(Transformation const& R);

// 基于四元数的构造函数
// 前提条件:四元数不得接近零
template <class D>
SOPHUS_FUNC explicit SO3(Eigen::QuaternionBase<D> const& quat);


// 默认构造函数
SOPHUS_FUNC SE3();

// 拷贝构造函数
SOPHUS_FUNC SE3(SE3 const& other) = default;

// 从SE3Base的其他子类进行拷贝构造
template <class OtherDerived>
SOPHUS_FUNC SE3(SE3Base<OtherDerived> const& other);

// 基于SO3、平移向量的的构造函数
template <class OtherDerived, class D>
SOPHUS_FUNC SE3(SO3Base<OtherDerived> const& so3,
                Eigen::MatrixBase<D> const& translation);

// 基于旋转矩阵、平移向量的构造函数
// 前提条件:旋转矩阵需要正交
SOPHUS_FUNC SE3(Matrix3<Scalar> const& rotation_matrix, Point const& translation);

// 基于四元数、平移向量的构造函数
// 前提条件:四元数不得接近零
SOPHUS_FUNC SE3(Eigen::Quaternion<Scalar> const& quaternion,
                Point const& translation);

// 基于4*4矩阵的构造函数
// 前提条件:左上3*3子矩阵需要正交。最后一行必须是(0,0,0,1)
SOPHUS_FUNC explicit SE3(Matrix4<Scalar> const& T);

可以看到,除了最基本的默认构造函数和拷贝构造函数之外,李群类还支持一下的构造函数:

  • SO(3)还支持旋转矩阵类,四元数类,不支持旋转向量(轴角)
  • SE(3)还支持旋转矩阵类+平移向量,四元数类+平移向量,4*4的变换矩阵类

需要注意的是,网上的一些版本会有类似于一下的构造函数:

Sophus::SO3f SO3_v(0, 0, M_PI/2);  // 这里注意,不是旋转向量的三个坐标值,有点像欧拉角构造

但是,从本文的Sophus版本上看,已经取消了该构造函数。

李群李代数的转换关系

下图展示了李群李代数的相互转换关系:

在这里插入图片描述

有一点需要注意:

视觉SLAM十四讲中提到,李群李代数的转换关系 S O 3 = exp ⁡ ( s o 3 ∧ ) SO3=\exp{(so3^{\wedge})} SO3=exp(so3)。也就是说,so3实际上就是由所谓的旋转向量组成的空间,而指数映射即罗德里格斯公式。 exp ⁡ \exp exp运算里面的参数应该是一个反对称矩阵,而不是一个向量。但是从图中看,so3直接通过SO3::exp()方法转换成SO3。

这是因为,在SO3::exp()内部已经经过了反对称运算的操作。具体可以参考文章:Sophus::SO3d::exp() 方法中的参数为什么是一个向量?。

还有一点需要注意:

  • 对于SO3而言,对应的so3经过反对称和 exp ⁡ \exp exp计算就得到了;即, s o 3 = ϕ so3=\phi so3=ϕ

exp ⁡ ( ϕ ∧ ) = R = S O 3 \exp(\phi^{\wedge})=R=SO3 exp(ϕ)=R=SO3

  • 对于SE3而言,对应的so3后三个元素经过反对称和 exp ⁡ \exp exp计算就得到SE3的左上3 * 3 的旋转部分,so3的前三个元素左乘一个 J J J才能得到SE3的右上3 * 1的平移部分。这个 J J J实际上就是BCH公式的 J l J_l Jl的值;即, s e 3 = ξ = [ ρ ϕ ] se3=\xi=\begin{bmatrix}\rho \\ \phi \end{bmatrix} se3=ξ=[ρϕ]

exp ⁡ ( ξ ∧ ) = [ R J ρ 0 1 ] = T \exp(\xi^{\wedge})=\begin{bmatrix}R&J\rho\\0&1\end{bmatrix}=T exp(ξ)=[R0Jρ1]=T

这部分可以参考文章:sophus库 根据se3的exp函数求解平移向量。

从代码上,先看李群初始化和相互转换部分:

// 先定义轴角、旋转矩阵、四元数、变换矩阵
Eigen::AngleAxisd A(M_PI / 2, Eigen::Vector3d(0, 0, 1));     // 旋转轴:(0,0,1),角度:180度
Eigen::Matrix3d R = A.matrix();
Eigen::Quaterniond Q(A);
Eigen::Vector3d t(1, 0, 0);
Eigen::Isometry3d T= Eigen::Isometry3d::Identity();          // 虽然称为3d,实质上是4*4的矩阵
T.rotate(R);
T.pretranslate(t);

// 初始化部分
Sophus::SO3d SO3_R(R);       // 从旋转矩阵初始化
Sophus::SO3d SO3_Q(Q);       // 从四元数初始化
// Sophus::SO3d SO3_A(A);       // 从轴角初始化,错误!!!!

Sophus::SE3d SE3_R(R, t);            // 从旋转矩阵初始化
Sophus::SE3d SE3_Q(Q, t);            // 从四元数初始化
Sophus::SE3d SE3_SO3(SO3_R, t);      // 从SO3初始化
Sophus::SE3d SE3_T(T.matrix());      // 从变换矩阵初始化

// 相互转化
Sophus::SO3d SO3_SE3 = SE3_R.so3();				// 从SE3转换到SO3
Eigen::Vector3d t_SE3 = SE3_R.translation(); 	// 从SE3转换到t
Eigen::Matrix3d R_SE3 = SE3_R.rotationMatrix();	// 从SE3转换到R
Eigen::Matrix4d T_SE3 = SE3_R.matrix();			// 从SE3转换到T

从代码上,再看李群李代数的转换部分:

Eigen::Vector3d so3(0, 0, M_PI / 2);            // so3在Eigen中用Vector3d表示
Sophus::SO3d SO3_so3 = Sophus::SO3d::exp(so3);
so3 = SO3_so3.log();

Eigen::Vector6d se3(0, 0 , 1, 0, 0, M_PI / 2);  // se3在Eigen中用Vector6d表示
Sophus::SE3d SE3_se3 = Sophus::SE3d::exp(se3);
se3 = SE3_se3.log();

在扰动模型中:

Eigen::AngleAxisd A(M_PI / 2, Eigen::Vector3d(0, 0, 1));
Eigen::Vector3d t(1, 0, 0);

Sophus::SO3d SO3_R(A.matrix());
Eigen::Vector3d update_so3(1e-4, 0, 0);                 // 假设更新量为这么多
Sophus::SO3d SO3_updated = Sophus::SO3d::exp(update_so3) * SO3_R;

Sophus::SE3d SE3_R(A.matrix(), t);
Sophus::Vector6d update_se3(1e-4, 0, 0, 0, 0, 0);       // 假设更新量为这么多
Sophus::SE3d SE3_updated = Sophus::SE3d::exp(update_se3) * SE3_R;

注意,这里仅仅是指原李群经过一个小的扰动更新成新的李群,并不涉及到BCH公式,或者求导的操作。

从代码上,再看李代数与反对称矩阵的转换部分:

Eigen::Vector3d so3(0, 0, M_PI / 2);
Eigen::Matrix3d so3_hat = Sophus::SO3d::hat(so3);
so3 = Sophus::SO3d::vee(so3_hat);

Eigen::Vector6d se3(0, 0 , 1, 0, 0, M_PI / 2);
Eigen::Matrix6d se3_hat = Sophus::SE3d::hat(se3);
se3 = Sophus::SE3d::vee(se3_hat);

其他代码

在李群李代数中,有两个公式比较常见,一个是 exp ⁡ \exp exp的罗德里格斯公式,一个是BCH公式,这里先简单回顾一下。

求解 exp ⁡ ( s o 3 ∧ ) \exp(so3^{\wedge}) exp(so3),那么(将 s o 3 so3 so3写成 θ a \theta a θa,其中, θ \theta θ为模长, a a a是一个长度为1的方向向量):

exp ⁡ ( θ a ∧ ) = I + ( 1 − c o s θ ) a ∧ a ∧ + s i n θ a ∧ \exp(\theta a^{\wedge})=I+(1-cos\theta)a^{\wedge}a^{\wedge}+sin\theta a^{\wedge} exp(θa)=I+(1cosθ)aa+sinθa

BCH公式 exp ⁡ ( ( ϕ 1 + ϕ 2 ) ∧ ) = exp ⁡ ( ϕ 1 ) ∧ exp ⁡ ( ( J r ( ϕ 1 ) ϕ 2 ) ∧ ) \exp((\phi_1+\phi_2)^{\wedge})=\exp(\phi_1)^{\wedge}\exp((J_r(\phi_1)\phi_2)^{\wedge}) exp((ϕ1+ϕ2))=exp(ϕ1)exp((Jr(ϕ1)ϕ2)),那么(将 ϕ 1 \phi_1 ϕ1写成 θ a \theta a θa,其中, θ \theta θ为模长, a a a是一个长度为1的方向向量):

J r ( θ a ) = I + ( 1 − s i n θ θ ) a ∧ a ∧ + c o s θ − 1 θ a ∧ J_{r}(\theta a)=I+(1-\frac{sin\theta}{\theta})a^{\wedge}a^{\wedge}+\frac{cos\theta - 1}{\theta}a^{\wedge} Jr(θa)=I+(1θsinθ)aa+θcosθ1a

ORB-SLAM3中IMU预积分中有一个函数,就实现了Exp和BCH的右雅可比矩阵的函数,代码为:

const float eps = 1e-4;

IntegratedRotation::IntegratedRotation(const float &x, const float &y, const float &z) {
  const float d2 = x * x + y * y + z * z;
  const float d = sqrt(d2);

  Eigen::Vector3f v;
  v << x, y, z;
  Eigen::Matrix3f W = Sophus::SO3f::hat(v);
  if (d < eps) {
    // 小值的Exp近似计算
    deltaR = Eigen::Matrix3f::Identity() + W;
    // 小值的右雅可比,近似为I
    rightJ = Eigen::Matrix3f::Identity();
  } else {
    // Exp 罗德里格斯公式
    deltaR = Eigen::Matrix3f::Identity() + W * sin(d) / d + W * W * (1.0f - cos(d)) / d2;
    // SO3的BCH公式,右雅可比
    rightJ = Eigen::Matrix3f::Identity() - W * (1.0f - cos(d)) / d2 + W * W * (d - sin(d)) / (d2 * d);
  }
}

这里的deltaR就可以用Sophus::SO3f::exp(v)来表示。


相关阅读

  • Sophus库的安装和使用教程
  • Sophus源码逐行解读 ( 结合视觉SLAM十四讲的概念 )
  • SLAM本质剖析-Sophus

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

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

相关文章

laravel系列(二) Dcat admin框架开发工具使用

开发工具可以非常好的帮助我们去快速的开发CURD等操作,但也是有部分框架有些不是太便捷操作,这篇博客主要为大家介绍Dcat admin的开发工具详细使用. 如何创建页面: 在联表我们首先要去.env文件中去找连接数据库方法: APP_NAMELaravel APP_ENVlocal APP_KEYbase64:thO0lOVlzj0…

机器学习入门教学——人工智能、机器学习、深度学习

1、人工智能 人工智能相当于人类的代理人&#xff0c;我们现在所接触到的人工智能基本上都是弱AI&#xff0c;主要作用是正确解释从外部获得的数据&#xff0c;并对这些数据加以学习和利用&#xff0c;以便灵活的实现特定目标和任务。例如&#xff1a; 阿尔法狗、智能汽车简单…

苹果跨入“迷你超级周期”:iPhone15Pro占比提升75%,性价比高?

据美国投行韦德布什证券的分析师Daniel Ives最近发布的投资者报告&#xff0c;苹果公司今年推出的iPhone 15机型比例已经从往年的3&#xff1a;2调整为了3&#xff1a;1&#xff0c;这标志着苹果公司跨入了“迷你超级周期”。 分析师郭明錤曾预测&#xff0c;今年iPhone 15系列…

【深度学习】 Python 和 NumPy 系列教程(二):Python基本数据类型:3、字符串(索引、切片、运算、格式化)

目录 一、前言 二、实验环境 三、Python基本数据类型 3. 字符串&#xff08;Strings&#xff09; 1. 初始化 2. 索引 3. 切片 4. 运算 a. 拼接运算 b. 复制运算 c. 子串判断 d. 取长度 5. 格式化 a. 使用位置参数 b. 使用关键字参数 c. 使用属性访问 f-string…

在家也能轻松使用用友畅捷通T3管理财务,实现高效率远程办公!

文章目录 前言1. 用友畅捷通T3借助cpolar实现远程办公1.1 在被控端电脑上&#xff0c;点击开始菜单栏&#xff0c;打开设置——系统1.2 找到远程桌面1.3 启用远程桌面 2. 安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web…

三.listview或tableviw显示

一.使用qt creator 转变类型 变形为listview或tableviw 二.导出ui文件为py文件 # from123.py 为导出 py文件 form.ui 为 qt creator创造的 ui 文件 pyuic5 -o x:\xxx\from123.py form.uifrom123.py listview # -*- coding: utf-8 -*-# Form implementation generated fro…

GDAL Python 过滤Shape Polygon中的面积小于某个阈值的小图斑

# -*- coding: utf-8 -*- # !/usr/bin/mgdal_env # Time : 2023/9/6 9:36 # Author : Hexk # 过滤矢量文件中的面积小于某个阈值的小图斑from osgeo import ogr, gdal, osr import osdef ShapeFiltratePitch(_input_path, _output_path, _area_threshold):"""过…

logback/log4j基本配置和标签详解

什么是logback logback 继承自 log4j&#xff0c;它建立在有十年工业经验的日志系统之上。它比其它所有的日志系统更快并且更小&#xff0c;包含了许多独特并且有用的特性。 logback.xml 首先直接上配置&#xff0c;我在项目过程中发现一些同时遇到需要logback文件的时候就去…

2023-9-8 求组合数(一)

题目链接&#xff1a;求组合数 I #include <iostream> #include <algorithm>using namespace std;const int mod 1e9 7;int n; const int N 2010; int c[N][N];void init() {for(int i 0; i < N; i )for(int j 0; j < i; j)if(!j) c[i][j] 1;else c[i]…

学习SpringMvc第三战-利用SpringMvc实现CRUD

前言&#xff1a; 小编讲述了参数传递&#xff0c;返回值以及页面跳转&#xff01;为我们的CRUD提供了理论基础&#xff0c;接下来小编会通过SpringMvc实现CRUD来讲述在企业开发中必须要学会的CRUD 一.前期环境搭建 1.替换pom.xml的内容 <properties><project.buil…

C# WPF 自己写的一个模拟病毒传播的程序,有可视化

原程序下载: https://download.csdn.net/download/qq_34677276/88314649 主要代码 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;n…

向量数据库Milvus Cloud 2.3 Attu 界面升级,用户体验更友好

全新升级的Milvus Cloud 2.3 Attu带来了全新的界面设计,为用户提供了更加友好的使用体验。作为向量数据库的专家和《向量数据库指南》的作者,我将在本文中详细讲解该版本的升级内容,并提供一些案例来加深大家对于Milvus Cloud 2.3 Attu的理解。 一、界面设计优化 1.1 界面整…

使用正则表达式总结

多行匹配 使用Pattern.DOTALL | Pattern.MULTILINE参数 Pattern.CASE_INSENSITIVE&#xff1a;不区分大小写 public static void main(String[] args) {String teststr "AA aa AASSF \n\r */ DDET AA";String regStr "(?AA)\\w\\b";extracted(testst…

LeetCode(力扣)90. 子集 IIPython

LeetCode90. 子集 II 题目链接代码 题目链接 https://leetcode.cn/problems/subsets-ii/ 代码 class Solution:def subsetsWithDup(self, nums):result []path []used [False] * len(nums)nums.sort() # 去重需要排序self.backtracking(nums, 0, used, path, result)retu…

十二、集合(5)

本章概要 for-in 和迭代器 适配器方法惯用法 本章小结 简单集合分类 for-in和迭代器 到目前为止&#xff0c;for-in 语法主要用于数组&#xff0c;但它也适用于任何 Collection 对象。实际上在使用 ArrayList 时&#xff0c;已经看到了一些使用它的示例&#xff0c;下面是它…

软件生命周期及流程【软件测试】

软件的生命周期 软件生命周期是软件开始研制到最终被废弃不用所经历的各个阶段。 瀑布型生命周期模型 规定了它们自上而下、相互衔接的固定次序&#xff0c;如同瀑布流水&#xff0c;逐级下落&#xff0c;具有顺序性和依赖性。每个阶段规定文档并需进行评审。 特点&#xff…

es滚动查询分析和使用步骤

ES在进行普通的查询时&#xff0c;默认只会查询出来10条数据。我们通过设置es中的size可以将最终的查询结果从10增加到10000。如果需要查询数据量大于es的翻页限制或者需要将es的数据进行导出又当如何&#xff1f; Elasticsearch提供了一种称为"滚动查询"&#xff08…

更换 yum 阿里源 - 手把手教你怎么配置,在也不需要求别人了 - 看懂一个就相当于看懂了其他的linux系统

更换阿里源 我的是centos8 当然 centos7 也可以换 后面有更详细的怎么配 &#xff0c;再也不用求别人怎么弄了 最直接的方式 直接复制 执行 centos7 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo或者 wget -O /etc/yum.repos.…

SpringMVC:从入门到精通,7篇系列篇带你全面掌握--三.使用SpringMVC完成增删改查

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringMVC的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 效果演示 一.导入项目的相关依赖 二.…

规范预算编制,打造企业全面预算管理新章程

随着我国财税体系不断改革&#xff0c;经济形式日新月异&#xff0c;包括国有企业、事业单位在内的各类型企业对于财务会计和预算管理的要求越来越高。众所周知&#xff0c;传统的预算管理模式已经难以满足企业现代化、数字化进程的需求&#xff0c;面对横亘在高效工作面前的阻…