视觉SLAM基础-李群和李代数
李群和李代数
引言
为什么会有李群和李代数的引出。在通常的 SLAM 中,我们估计的无非就是在极短的时间内物体的一个相对位姿运动,然后进行累加,即可得到物体的当前位置,即 SLAM 中的定位问题,但是往往该运动在较短的时间内其变化量是极小的。
通常其运动变化我们可以使用旋转加平移进行表示,即
怎么解决呢,这就要引出李群和李代数来解决。
前面我们介绍了若干种描述三维世界刚体运动的方式。除了用它们来描述相机的位姿之外,还要对它们进行估计和优化。这个问题可以描述为什么样的相机位姿最符合当前观测数据。而典型的做法就是将其构建成一个优化问题来求解。
1.0 李群
上一讲在介绍旋转矩阵和变换矩阵时,我们说三维旋转矩阵构成了特殊正交群
对变换矩阵也是如此。但这二者对于加法是不封闭的。两个变换矩阵相加后得到的矩阵并不是一个变换矩阵。
1.1 群
如此,我们就可以引出群的定义:群(group)是一种集合加上一种运算的代数结构。把集合记为
三维旋转矩阵构成了特殊正交群(
三维变换矩阵构成了特殊欧氏群(
什么是群?群(Group)是一种集合加上一种运算的代数结构。
记集合为
,运算为 · ,那么当运算满足以下性质时,称 ( , · )成群:
- 封闭性:
- 结合率:
- 幺元:
- 逆:
可以验证,旋转矩阵集合和变换矩阵集合分别和矩阵乘法构成群。还有其他很多群,如整数的加法等。群结构保证了在群上的运算具有良好的性质。
1.2 李群基础定义
定义:李群就是具有连续(光滑)性质的群。
前面举的整数的加法的例子显然不是连续的,因而它不是李群。但
介绍完李群,在引入李代数之前,我们来回顾下开头我们提到的问题:为什么要用到李群和李代数?避免一直是数学上的推导。我们用一个比较实际的例子。假设某个时刻我们预测机器人的位姿为
其中,
若共有N 个观测值,那么就有N 个这样的式子。机器人的位姿估计就转变成寻找一个最优的
通常,直接求解上式得出最优的
2.0李代数
2.1 引出
现在我们考虑任意的旋转矩阵
因为机器人在不断运动,所以
等式两边一起对时间求导可得:
亦即
一个矩阵等于其自身转置后取负号,可知
设这个对应的向量为
等式两边同时右乘
从式
NOTE:
同时,在
显然,上式是一个关于
由于我们之前做了一些假设,所以这个式子只在
但通过式
2.2 李代数的定义
每个李群都有与之对应的李代数。李代数描述了李群的局部性质。其定义为:李代数由一个集合
- 封闭性:
. - 双线性:
, 有 - 自反性:
. - 雅可比等价:
.
其中的二元运算
2.3 李代数
之前我们提到的SO(3) 对应的向量
高翔在书中没有明说,但经过计算可以得到结果为
至此,我们已经清楚了
下面我们先看李代数
2.4 李代数
对于
把每个
同样,李代数
3.0 指数和对数映射
3.1 上的指数映射
如前所述,李代数到李群是一个指数映射。现在来看看指数映射是如何计算的。
任意矩阵的指数映射都可以写成一个泰勒展开。但要注意的是展开式只有在收敛的情况下才有解,其结果仍然是一个矩阵。所以,对
将
回想前一讲,可以发现式
但上式太过复杂了。通常我们会用前一讲介绍的旋转向量到旋转矩阵的方式来计算旋转矩阵。
NOTE:指数映射是一个满射,即对每个SO(3) 中的元素都可以找到一个so(3) 的元素与之对应;但存在多个so(3) 元素对应到同一个SO(3) 上(一个旋转角为
3.2 上的指数映射
SE(3) 上的指数映射推导原理与SO(3) 类似,但更复杂。这里直接给出结论:
式(23) 与罗德里格斯公式相似。
相反的,SE(3) 到se(3) 也有对应的对数映射。但一般先利用左上角的旋转矩阵计算出旋转向量;对右上角的平移向量
即可得到
4.0 李代数求导与扰动模型
4.1 李群和李代数在计算上的对应关系
至此,我们已经介绍完了李群和李代数还有它们之间的对应关系。但最后还有一个问题。我们知道,对于两次连续的旋转可以用矩阵乘法来描述,但用李代数如何表示呢?我们自然是希望李群中两个矩阵相乘对应李代数上对应元素的相加,这样我们才能够利用加法来计算导数和进行更新。但很遗憾,这是不成立的。
两个李代数指数映射的乘积,由BCH 公式给出:
其中,
想象下机器人的运动,连续两帧间相机的位姿变化是一个小量;在优化过程中,通过求导而得到的更新量也是一个小量。更具体地,考虑SO(3) 上的李代数
解释:以第一个式子为例,当对一个旋转矩阵
对于左乘
它的逆为:
对于右乘模型,右乘雅可比矩阵就是将左乘雅可比的自变量取负号:
小结一下,假设有某个旋转
反之,如果我们在李代数上进行加法,让
同样地,对于
这里
回顾介绍李群时举的那个例子。现在我们有了李代数,which 具有良好的加法运算。因此,我们可以用它来解决求导问题了。具体的思路有两种:
- 用李代数来表示位姿,根据李代数加法来对李代数进行求导;
- 利用李群来左乘或者右乘微小扰动,在对这个扰动的李代数进行求导。
4.2 李代数求导模型
假设空间点
前面提到,SO(3)没有加法,所以上式无法按照导数的定义进行计算。设
按照导数的定义展开,并在推导中利用
这个公式是直接对旋转矩阵
4.3 扰动模型
另一种方法就是先对旋转矩阵
和李代数求导模型进行对比,最明显的就是式(37) 少了左雅可比矩阵
4.4 上的扰动模型
假设某空间点
最后的结果被定义为符号
4.5 相似变换及其李群和李代数
最后介绍下单目SLAM中会用到的相似变换群Sim(3)。由于单目视觉中存在一个尺度不确定性(简单说,把单目相机两次拍照的两张相片想象成双目相机一次拍的两张照片,这两次拍照中单目相机的位移(对应于双目中的基线)是不知道的,导致了在像素深度估计上的尺度不确定),因此在单目的情况下,需要把尺度因子显式表达出来。就由欧式变换到了相似变换。
设空间点为
这里,s 就是尺度因子,它相当于对p 进行了一次缩放。
显然,相似变换也对矩阵乘法构成群,称为相似变换群:
它对应的李代数为sim(3),是一个7维向量:前六维就是se(3),最后一项为尺度因子
二者间仍然是通过指数映射和对数映射相关联。指数映射为:
其中,
可以看到,相似变换的雅可比矩阵远比前面的复杂。而对数映射可以写为:
同样地,
显然,
5.0 Sophus
代码解析
- 这里使用的是带有模板的
Sophus
库。 - 依赖
fmt
#include <iostream>
#include <cmath>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "sophus/se3.hpp"
using namespace std;
using namespace Eigen;
/// 本程序演示sophus的基本用法
int main(int argc, char **argv) {
// 沿Z轴转90度的旋转矩阵
Matrix3d R = AngleAxisd(M_PI / 2, Vector3d(0, 0, 1)).toRotationMatrix();
// 或者四元数
Quaterniond q(R);
Sophus::SO3d SO3_R(R); // Sophus::SO3d可以直接从旋转矩阵构造
Sophus::SO3d SO3_q(q); // 也可以通过四元数构造
// 二者是等价的
cout << "SO(3) from matrix:\n" << SO3_R.matrix() << endl;
cout << "SO(3) from quaternion:\n" << SO3_q.matrix() << endl;
cout << "they are equal" << endl;
// 使用对数映射获得它的李代数
Vector3d so3 = SO3_R.log();
cout << "so3 = " << so3.transpose() << endl;
// hat 为向量到反对称矩阵
cout << "so3 hat=\n" << Sophus::SO3d::hat(so3) << endl;
// 相对的,vee为反对称到向量
cout << "so3 hat vee= " << Sophus::SO3d::vee(Sophus::SO3d::hat(so3)).transpose() << endl;
// 增量扰动模型的更新
Vector3d update_so3(1e-4, 0, 0); //假设更新量为这么多
Sophus::SO3d SO3_updated = Sophus::SO3d::exp(update_so3) * SO3_R;
cout << "SO3 updated = \n" << SO3_updated.matrix() << endl;
cout << "*******************************" << endl;
// 对SE(3)操作大同小异
Vector3d t(1, 0, 0); // 沿X轴平移1
Sophus::SE3d SE3_Rt(R, t); // 从R,t构造SE(3)
Sophus::SE3d SE3_qt(q, t); // 从q,t构造SE(3)
cout << "SE3 from R,t= \n" << SE3_Rt.matrix() << endl;
cout << "SE3 from q,t= \n" << SE3_qt.matrix() << endl;
// 李代数se(3) 是一个六维向量,方便起见先typedef一下
typedef Eigen::Matrix<double, 6, 1> Vector6d;
Vector6d se3 = SE3_Rt.log();
cout << "se3 = " << se3.transpose() << endl;
// 观察输出,会发现在Sophus中,se(3)的平移在前,旋转在后.
// 同样的,有hat和vee两个算符
cout << "se3 hat = \n" << Sophus::SE3d::hat(se3) << endl;
cout << "se3 hat vee = " << Sophus::SE3d::vee(Sophus::SE3d::hat(se3)).transpose() << endl;
// 最后,演示一下更新
Vector6d update_se3; //更新量
update_se3.setZero();
update_se3(0, 0) = 1e-4;
Sophus::SE3d SE3_updated = Sophus::SE3d::exp(update_se3) * SE3_Rt;
cout << "SE3 updated = " << endl << SE3_updated.matrix() << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通