在SLAM中, 位姿作为状态变量是未知的,需要解决什么样的相机位姿最符合当前观测数据这样的问题。一种典型的方式是把它构建成一个优化问题,求解最优的R, t,使得误差最小化。旋转矩阵自身是带有约束的(正交且行列式为1)。它们作为优化变量时,会引入额外的约束,使优化变得困难。通过李群——李代数间的转换关系能够把位姿估计变成无约束的优化问题,简化求解方式
一、李群李代数基础定义
- 三维旋转矩阵构成了特殊正交群
SO(3)
:
- 四维变换矩阵构成了特殊欧式群
SE(3)
- 旋转矩阵、变换矩阵加法是不封闭的:
- SO(3)和SE(3)关于乘法是封闭的:
二、群
对于只有一个(良好的)运算的集合,我们称之为群,群是一种集合加上一种运算的代数结构,我们把集合记为A,运算记作.,那么群可以记作G = (A,.)
2.1 群的定义
群满足以下特性:
旋转矩阵集合和矩阵乘法构成群,叫做旋转矩阵群;同样,变换矩阵和矩阵乘法也构成群,叫做变换矩阵群。矩阵中常见的群有:
- 一般线性群 GL(n): n×n 的可逆矩阵,它们对矩阵乘法成群
- 特殊正交群 SO(n): 即旋转矩阵群,其中 SO(2) 和 SO(3) 最为常见
- 特殊欧式群 SE(n): 即 n 维欧氏变换,如 SE(2) 和 SE(3)
我们重点要学习了解的是李群
2.2 李群
- 指具有连续(光滑)性质的群
- 直观上看, SO(3) SE(3)在实数空间上都是连续的,所以都可称之为李群,但是,SO(3)和SE(3)只有定义良好的乘法,没有加法,所以难以进行取极限、求导等操作
三、李代数
每一个李群对应一个李代数 ,李代数描述了李群的局部性质,位于向量空间
3.1李代数的引入
任意一个旋转矩阵R(正交阵),它满足:
R是t的函数,由于它是旋转矩阵,故满足:
等式两边对t求导:
移向整理得:
观察可以看出等式左边为一个反对称矩阵,记作:
前式两侧右乘R(t),而R(t)为正交矩阵,故有微分方程:
设t0=0,R(0)=I,将R(t)在0附近进行一阶泰勒展开为:
可得初始值为:R(t0)=R(0)=I,依据异界线性常系数微分方程的求解公式解得:
- 对于任意的t,都可以找到一个R和一个ϕ与之对应
- 外积引入了^符号,将一个向量变成了反对称矩阵。同理,对于任意反对称矩阵,亦能找到一个与之对应的向量,把这个运算用符号∨表示。(向量加了∧符号变成反对称矩阵,矩阵加了∨符号变成对应的向量)
李代数具有如下性质:
3.2 李代数so(3)
so(3)是三维向量(三行一列),其矩阵形式为3维反对称矩阵
- SO(3)对应的李代数:so(3)
3.3 李代数se(3)
se(3)是一个六维向量(六行一列),前三维是平移,后三位是旋转
- SE(3)对应的李代数为:se(3)
3.4 指数映射与对数映射
旋转矩阵 R 与另一个反对称矩阵 ϕ0∧t 通过指数关系发生了联系;给定某时刻的R,就能求得一个ϕ,它描述了R在局部的导数关系。ϕ正是对应到SO(3) 上的李代数so(3);李代数(ϕ)到李群(R)是指数关系,而李群(R)到李代数(ϕ)是对数关系
- so(3)实际上就是由旋转向量组成的空间,而指数映射即罗德里格斯公式。通过指数映射,把so(3)中任意一个向量对应到了一个位于SO(3)中的旋转矩阵。反之,如果定义对数映射,也能把SO(3)中的元素对应到so(3)中。SO(3)与so(3)的结论和前面的旋转向量与旋转矩阵很相似,而指数映射即是罗德里格斯公式。旋转矩阵的导数可以由旋转向量指定,指导着如何在旋转矩阵中进行微积分运算。SO(3)、SE(3)、so(3)、se(3)之间的对应关系总结如下图
四、增量扰动模型
使用李代数的一大动机是进行优化,而在优化过程中导数是非常重要信息,首先slam的定位就是在对相机进行位姿估计,李群对加法没有封闭性,但向量对加法具有封闭性
因此,使用李代数解决求导问题的思路分为两种:
- 用李代数表示姿态,然后根据李代数加法对李代数求导,然后对应到李代数的求导模型
- 对李群左乘或右乘微小扰动,然后对该扰动求导,称为左扰动和右扰动模型 ,对应到扰动模型(更为简便)
第一种方式对应到李代数的求导模型,第二种方式对应到扰动模型
tips:一般在工程中选择第二种,旋转矩阵的微分是一个反对称矩阵左乘它本身
4.1 SO(3)上的扰动模型(左乘)
在对旋转矩阵R进行求导时,扰动模型指的是对R进行一次扰动R的微小增量,相对于扰动的变化率,左扰动即左乘,右扰动即右乘,两者都行,只是结果会有一些微小差距。以左扰动为例:
相对于直接对李代数求导,省去了一个雅各比的计算,使得扰动模型更加实用,这种方法在位姿估计中具有重要的意义
4.2 SE(3)上的李代数求导
假设某空间点p经过一次变换T,得到Tp,现在,给T左乘一个扰动:
tips:为使乘法成立,p必须使用齐次坐标
五、Sophus库使用
目前,一个较好的李代数库是Strasdat维护的Sophus库,接下来的使用中,我们使用带模板的库
建立useSophus.cpp
文件
其主要步骤为:
- 通过旋转矩阵或者四元数定义李群
- 通过李群的对数映射得出李代数
- 在SLAM中,增量扰动模型很关键,对扰动向量进行指数映射,并左乘到原矩阵,就完成了增量扰动的更新
#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-4d;
Sophus::SE3d SE3_updated = Sophus::SE3d::exp(update_se3) * SE3_Rt;
cout << "SE3 updated = " << endl << SE3_updated.matrix() << endl;
return 0;
}
编译运行结果如下:
xyb@xyb:~/slam/ch4/build$ ./useSophus
SO(3) from matrix:
2.22045e-16 -1 0
1 2.22045e-16 0
0 0 1
SO(3) from quaternion:
2.22045e-16 -1 0
1 2.22045e-16 0
0 0 1
they are equal
so3 = 0 0 1.5708
so3 hat=
0 -1.5708 0
1.5708 0 -0
-0 0 0
so3 hat vee= 0 0 1.5708
SO3 updated =
0 -1 0
1 0 -0.0001
0.0001 2.03288e-20 1
*******************************
SE3 from R,t=
2.22045e-16 -1 0 1
1 2.22045e-16 0 0
0 0 1 0
0 0 0 1
SE3 from q,t=
2.22045e-16 -1 0 1
1 2.22045e-16 0 0
0 0 1 0
0 0 0 1
se3 = 0.785398 -0.785398 0 0 0 1.5708
se3 hat =
0 -1.5708 0 0.785398
1.5708 0 -0 -0.785398
-0 0 0 0
0 0 0 0
se3 hat vee = 0.785398 -0.785398 0 0 0 1.5708
SE3 updated =
2.22045e-16 -1 0 1.0001
1 2.22045e-16 0 0
0 0 1 0
0 0 0 1
- 程序中由于对乘法运算发进行了重载,重载功能中完成了相关运算,所以调库编程比实际计算要简单得多