写这篇东西的起因很简单:群里又有同学在深蓝学院第三章作业上卡了快一个周末。我第一次做第三章作业时也那样——看了一眼题目,觉得“就这?不就是矩阵乘法和指数映射嘛”,真正动手才发现,公式推不动、代码编译不过、画出来的轨迹跟参考答案对不上,整个人从“这很简单”变成“我是不是不适合学SLAM”。这篇把第三章作业从理论到代码、从推导到调参的完整链路拆开讲讲。内容适配SLAM方向课程第三章的典型作业,也覆盖了点云、位姿、轨迹相关任务里最常踩的坑,适合正在写作业、以及刚学完刚体运动和李代数想落地验证的同学。
1. 第三章作业到底在考什么:先看清理论主线
1.1 从三维空间刚体运动说起:为什么这一章是SLAM的地基
如果你报名的是深蓝学院的视觉SLAM或激光SLAM方向课程,第三章的内容大概率围绕三维空间刚体运动展开。这一章牵涉的核心概念非常集中:世界坐标系与相机坐标系之间的变换、旋转矩阵的性质、旋转向量与欧拉角、四元数,以及最重要的——李群SO(3)与李代数so(3)之间的映射关系。
这一章之所以让很多人栽跟头,不是因为某个公式特别难,而是它的知识点密度高、且彼此环环相扣。旋转矩阵你还能用线性代数的底子硬算,等到了李代数,矩阵的指数和对数、BCH近似、扰动模型这些概念一上来,如果没有把“为什么要引入李代数”这个问题想明白,后面的作业必然是一路磕绊。
第三章作业里,最常见的第一类题目就是验证这些表示方式的等价性:给你一个旋转轴和旋转角,要求分别写出旋转矩阵、四元数、旋转向量,并且验证它们描述的是同一个旋转。看起来是送分题,实际上考察的是你对三种表示方式在内存中的存储格式、转换公式、以及奇异性处理的掌握程度。很多同学在这里就暴露出问题:四元数转旋转矩阵的公式背不完整,或者忘了旋转向量有周期性、角度需要归一化到[0, π]范围。
1.2 李群与李代数的核心联系:指数映射、对数映射与BCH公式
第三章作业的第二类题目,才是真正拉开差距的环节——李群与李代数的相互转换。
先说为什么要引入这一套数学工具。SLAM里我们时时刻刻都在估计相机或机器人的位姿,位姿本身是一个带约束的量(旋转矩阵必须满足行列式为1且正交),直接把它当作优化变量非常麻烦,因为在迭代优化时你没法保证更新后的量还满足约束。李代数的存在,相当于把一个带约束的流形问题,先映射到一个无约束的向量空间里做运算,运算完了再用指数映射把它拉回流形上。
so(3)到SO(3)的指数映射在几何上就是罗德里格斯公式:
exp(φ^) = I + (sinθ/θ)φ^ + ((1-cosθ)/θ²)(φ^)²
其中θ是旋转向量的模长,φ^是φ对应的反对称矩阵。反过来从SO(3)到so(3)的对数映射,则是从旋转矩阵里提取旋转向量。
理论上这些公式在教科书上都有,但作业题不会直接让你默写公式。深蓝学院第三章作业里比较经典的考法是:给你一个旋转矩阵R,要求你手推它的对数映射得到旋转向量,再用罗德里格斯公式验证回去是不是能还原R。这一步看着简单,实际上暗藏了三个坑——反对称矩阵元素取正负号、旋转角接近π时的退化情况、以及atan2和arccos在数值稳定性上的差异。
BCH公式是另一个重点。SO(3)上的乘法对应到so(3)上并不是简单的向量加法,而是需要加上修正项。作业里经常让你验证近似版本的BCH公式在小角度下成立的程度,典型的题目是:生成两个小旋转,分别计算exp(φ1^)·exp(φ2^)的精确结果和用BCH近似后的结果,比较两者误差随角度增大的变化趋势。做这道题的意义,是为后面学习位姿图优化、IMU预积分中为什么能忽略高阶项做铺垫。
1.3 作业题型的典型分布:推导题、代码题、实验题
把深蓝学院系列课程的第三章作业做个横向对比,题型基本分为三类:
理论推导题:占30%-40%。核心集中在BCH近似误差分析、左扰动与右扰动模型的雅可比推导、四元数与旋转矩阵的转换公式证明。这类题考察的是对公式的敏感度,答案基本固定,但推导过程中的细节(比如矩阵求导的链式法则怎么用)非常容易丢分。
代码实现题:占40%-50%。要求用Eigen、Sophus等库实现旋转矩阵、四元数、欧拉角之间的转换,实现位姿变换,并对轨迹进行可视化。这部分占比最大,也是最容易卡住的环节。
实验分析题:占20%左右。给出一个包含噪声的轨迹数据或模拟数据,要求用李代数做插值或拟合,分析误差曲线,并说明原因的环节。这类题通常需要提交图表和分析结论。
三类题型的核心主线其实是一条:从旋转表示到李代数运算,再到用轨迹可视化来验证位姿估计的正确性。看懂这条主线,你就明白为什么作业要这样设计了——第三章不是孤立的数学课,它是在为第四章的相机模型、第五章的对极几何、以及后面整个后端优化铺路。
2. 手推作业中最容易翻车的两个公式:BCH近似与扰动模型
2.1 矩阵指数到底怎么展开:从罗德里格斯公式说起
先别急着跳去写代码。第三章作业里最容易翻车的,其实是看起来最简单的罗德里格斯公式。
很多同学把罗德里格斯公式背成R = I + sinθ·n^ + (1-cosθ)·(n^)²,然后直接套用。这个写法不能算错,但丢了一个关键概念:旋转向量n·θ中的θ和单位旋转轴n是分开的。如果作业题目给的是一个未归一化的旋转向量φ,你必须先计算θ = ||φ||,再令n = φ/θ,最后才能套公式。作业里常见的扣分点就是:直接拿φ的反对称矩阵代入公式计算,结果得到完全不合理的旋转矩阵。
我当时的做法是,先把φ = π/2 · (0, 0, 1)^T这个最简单的情况手动算一遍。用纸笔展开反对称矩阵、算平方项、代入系数,得到的结果是绕z轴旋转90度的标准旋转矩阵。这样一验证,你就能确信自己对公式的理解是对的。等代码阶段再用Eigen/Sophus输出同样结果做对比,整个链路就闭合了。
罗德里格斯公式还有一个容易忽略的边界条件:当θ接近0时,(sinθ/θ)和((1-cosθ)/θ²)都存在数值不稳定的问题。作业一般不会拿这个来卡你,但如果你在代码里先实现了自己手写的指数映射函数,建议加上一个判断:当θ小于某个阈值(比如1e-8)时直接返回单位矩阵。不然后续做轨迹可视化时,系统一旦出现极小幅度的旋转,你的程序就可能输出NaN,排查起来非常浪费时间。
2.2 BCH近似到底近似掉了什么
BCH公式的完整形式非常复杂,第三章作业通常不会让你推完整版,而是考近似版。
核心逻辑是:exp(φ1^)·exp(φ2^) = exp((φ1 + φ2 + 1/2·φ1×φ2 + ...)^),当φ1和φ2都是小量时,高阶项可以忽略,于是两个旋转的复合近似等于李代数直接相加。
作业里的典型考法是:给一组逐渐增大的旋转角,比如从0.01弧度到0.5弧度,分别计算精确的旋转矩阵乘积和BCH近似得到的旋转矩阵,画出误差随角度增大的曲线。做这道题的目的不是验证公式“对不对”,而是让你直观感受:BCH近似在什么角度范围内是可以接受的、在什么范围内误差会迅速膨胀。
我在做这个作业时发现一个很有价值的操作:把“相对误差”拆成角度误差和轨迹误差两个维度来分析。角度误差是直接计算两个旋转矩阵之间的相对旋转角,符合几何直觉;但更重要的是理解在后续BA优化中,迭代增量通常都控制得很小(远小于0.1弧度),所以BCH近似在优化迭代中基本不会引入显著误差。第三章作业如果能够把这层理解写进分析结论,分数通常会比单纯画误差曲线的同学高不少。
2.3 左扰动模型 vs 右扰动模型:选哪个,为什么
扰动模型相关的推导题是第三章作业中区分度最大的一部分,也是后续学习BA和位姿图优化必须跨过的门槛。
先看结论:对一个旋转后的点Rp求关于旋转R的导数时,我们不能简单地定义∂(Rp)/∂R,因为在流形上没有直接的加法。两种标准做法是左扰动和右扰动。左扰动模型在R左侧乘一个微小旋转ΔR,即ΔR·R·p;右扰动模型在R右侧乘一个微小旋转,即R·ΔR·p。两种模型推导出的雅可比表达式不同,但描述了同一个几何量。
作业里最常考的是左扰动模型的推导:对旋转矩阵R左乘一个扰动δφ对应的指数映射,展开后求极限,最后得到∂(Rp)/∂δφ = -(Rp)^,也就是旋转后向量对应的反对称矩阵取负号。
我见过太多同学在这里死记硬背“左扰动结果是-Rp的反对称矩阵”,却不知道这个结果是怎么来的。一旦题目变成“求∂(R^T·p)/∂δφ”或者“求∂(R1·R2)/∂δφ1”,立马就懵。实际上,只要你掌握了展开技巧:把exp(δφ^)在零点泰勒展开为I + δφ^,然后保留一阶项,剩下的就是纯矩阵运算。建议作业推导时不要直接跳步,一步步把左扰动展开、消去高阶项、整理出反对称矩阵的过程写完整,这比结果更重要。
2.4 我踩过的推导坑
第一个坑:矩阵求导时链式法则的方向搞反了。对一个复合函数f(R) = a^T·R·b求关于扰动的导数时,关键在于选对展开顺序。你应该先对R左扰动展开f(exp(δφ^)·R),得到f(R) + a^T·δφ^·R·b,然后再利用反对称矩阵的性质把δφ从左侧提出来,最终得到-(R·b)∧·a。这里符号特别容易错,建议每步都带上具体维度,用矩阵维度检验每一步是否合法。
第二个坑:把四元数直接当成李代数来扰动。有些同学觉得四元数和李代数都表示旋转,就试图用四元数的增量来推导雅可比。四元数所在的单位球面也是一个流形,但它的扰动模型在形式上和李代数不同,作业里如果混用,推导结果大概率是错的。稳妥的做法是:作业里要求用哪种符号体系就用哪种,不要自创。
第三个坑:没有数值验证就手推完了。推完一套雅可比,自我感觉良好,结果代入数值一算,跟数值差分结果差了十万八千里。强烈建议在推导后立刻做一个简单的数值验证——用0.01弧度的微小扰动,计算解析雅可比和数值差分的误差,如果相对误差超过1%,哪里肯定有问题。
3. 从公式到代码:用Eigen和Sophus把第三章作业落地
3.1 工具链准备:Sophus版本选择是第一个坑
第三章作业的代码部分,最常用也最顺手的组合是Eigen + Sophus + Pangolin。Eigen负责线性代数运算,Sophus提供李群和李代数的封装,Pangolin用来做3D可视化。
Sophus这个库有个历史包袱:老版本(非模板版本)中SO3类直接就是李群,成员函数用大写开头的Exp和Log;而新的模板版本中,SO3d类型只是李群,需要通过SO3d::exp()和SO3d::log()调用指数和对数映射。作业代码如果是从网上东拼西凑来的,最容易出现这种版本混用导致的编译报错。我的建议是:优先使用模板版本的Sophus,并且把依赖的fmt库版本也固定在官方推荐版本上。因为Sophus新版本依赖fmt做日志输出,如果系统里装了不兼容的fmt版本,编译时会报出非常隐晦的错误信息。
安装的时候还有一个小技巧:直接用系统包管理安装的Sophus很可能版本过旧,建议从GitHub克隆源码后本地编译安装。编译前确认Eigen已经安装到系统路径中,如果Eigen是自行解压的源码包,记得设置CMAKE_PREFIX_PATH指向Eigen的根目录,否则CMake会提示找不到Eigen3。
3.2 代码实现:绕Z轴旋转的位姿变换例子
理论讲了这么多,落到代码其实就十几行。下面给一个最小可复现的例子,覆盖旋转表示转换、李代数映射和位姿变换三个作业中最常用的操作。
#include <iostream> #include <Eigen/Core> #include <Eigen/Geometry> #include <sophus/so3.hpp> #include <sophus/se3.hpp> int main() { // 1. 定义绕z轴旋转90度的旋转矩阵 Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI / 2.0, Eigen::Vector3d::UnitZ()).toRotationMatrix(); // 2. 旋转矩阵 -> 四元数 -> 旋转向量 Eigen::Quaterniond q(R); Eigen::Vector3d axis = Eigen::Vector3d::UnitZ(); double angle = M_PI / 2.0; std::cout << "Quaternion: " << q.coeffs().transpose() << std::endl; std::cout << "Axis-Angle: " << axis.transpose() << ", angle: " << angle << std::endl; // 3. SO3 -> so3(对数映射),再指数映射回来 Sophus::SO3d SO3_R(R); Eigen::Vector3d so3 = SO3_R.log(); std::cout << "so3: " << so3.transpose() << std::endl; Sophus::SO3d SO3_again = Sophus::SO3d::exp(so3); std::cout << "R reconstructed error: " << (SO3_again.matrix() - R).norm() << std::endl; // 4. 用左扰动求旋转点对扰动的导数(解析 vs 数值) Eigen::Vector3d p(1.0, 0.0, 0.0); Eigen::Vector3d Rp = SO3_R * p; Eigen::Matrix3d J_analytic = Sophus::SO3d::hat(-Rp); Eigen::Matrix3d J_numeric = Eigen::Matrix3d::Zero(); double eps = 1e-6; for (int i = 0; i < 3; ++i) { Eigen::Vector3d delta = Eigen::Vector3d::Zero(); delta[i] = eps; Eigen::Vector3d Rp_plus = Sophus::SO3d::exp(delta) * SO3_R * p; Eigen::Vector3d Rp_minus = Sophus::SO3d::exp(-delta) * SO3_R * p; J_numeric.col(i) = (Rp_plus - Rp_minus) / (2.0 * eps); } std::cout << "J_analytic:\n" << J_analytic << std::endl; std::cout << "J_numeric:\n" << J_numeric << std::endl; std::cout << "Diff: " << (J_analytic - J_numeric).norm() << std::endl; return 0; }这段代码第一部分验证了旋转矩阵、四元数、旋转向量三种表示的等价性;第二部分把SO3映射到so3再映射回来,检验重构误差;第三部分用中心差分法验证左扰动模型解析雅可比的正确性。把这三块跑通,第三章作业的代码题基本就解决了一大半。
这里提一下为什么用中心差分而不是单侧差分。单侧差分( f(x+ε) - f(x) ) / ε虽然实现简单,但截断误差是O(ε)级别;中心差分的截断误差是O(ε²),在ε取1e-6时,数值稳定性和精度都明显更好。作业里如果要做数值验证,建议统一使用中心差分,省得因为数值误差太大而误判自己的解析推导有问题。
3.3 用数值差分验证解析雅可比:方法论与阈值判断
数值验证这一招,是我在第三章作业里学到的最值钱的技巧,没有之一。
具体做法就是上面代码里展示的那样:对旋转扰动δφ的每个分量分别做一个微小增量,用指数映射构造扰动后的旋转矩阵,计算函数值变化,然后除以扰动量得到数值雅可比。把它和你的解析雅可比做差,如果范数在1e-6到1e-5量级,说明推导正确;如果差到1e-2甚至更大,解析推导里必定有符号或运算顺序的错误。
做这个验证时有几个细节要注意。第一,扰动量的取值不能太大也不能太小,1e-6是一个比较稳妥的经验值,太小会触发浮点精度极限。第二,如果函数的计算链路较长,比如涉及多位姿连乘,建议拆开分段验证——先单独验证对第一个旋转的导数,再验证对第二个旋转的导数,不要一次性验证整体雅可比。这样定位错误会快得多。第三,对单位四元数相关的雅可比做数值验证时,记得先对四元数归一化,否则数值差分算出来的一直是错误的“有偏导数”。
3.4 作业中常见的运行报错与解决办法
把我在深蓝学院学员群里看到的高频报错整理成一张表,遇到问题先对照一下:
| 报错特征 | 根因 | 解决办法 |
|---|---|---|
undefined reference to Sophus::SO3d::exp | 链接阶段没有链接Sophus库 | CMake中确保target_link_libraries包含Sophus::Sophus |
| 编译时找不到fmt头文件 | Sophus依赖的系统fmt版本不匹配 | 单独编译安装官方推荐版本的fmt |
| 运行时输出NaN | 旋转角过小导致指数映射数值不稳定 | 对接近零的角度加阈值判断,返回单位阵 |
用非模板版Sophus的SO3::exp语法 | 版本混用 | 统一换成Sophus::SO3d::exp(...)模板版语法 |
| 轨迹可视化时程序崩溃 | Pangolin版本与OpenGL驱动冲突 | 在虚拟机或远程环境中建议改用matplotlib/mpl_toolkits的3D绘图 |
这些报错里最值得说的是“轨迹可视化时程序崩溃”这条。很多同学用Pangolin在本地Windows上跑得好好的,到了服务器或者Docker容器里一运行就段错误。原因通常是容器内没有图形环境和OpenGL库。如果作业只要求可视化,不一定非得用Pangolin——matplotlib的3D轴完全够用,而且能直接把轨迹图保存成PNG提交,对在线课程来说反而更方便。
4. 让作业结果可视化:轨迹图一出来,正确性立刻见分晓
4.1 可视化轨迹为什么能快速验证代码正确性
代码运行后只输出一串数字,很难判断结果是对是错。但当你能把轨迹画出来,看一眼画面,代码正确与否基本上三秒之内就能判断。
正常的轨迹应该是平滑连续的曲线,位姿中的旋转变化在轨迹上会体现为平滑的姿态过渡。如果代码里的旋转方向搞反了,画出来的轨迹会呈现明显的镜像或者扭曲;如果四元数符号没归一化或者映射错了,轨迹上会出现异常跳变;如果平移和旋转的顺序用错了,轨迹则会出现螺旋状的错乱。
我印象最深的一次调试是:作业给的轨迹数据是用真实传感器采集的,我在画轨迹时发现每一帧的旋转都绕着一个轴线旋转了180度。一开始怀疑是传感器标定数据错了,排查了很久才发现是代码中把四元数转旋转矩阵时把实部放错了位置,导致所有的旋转矩阵都被一个180度的旋转污染。这种问题如果只看数值输出,几乎不可能定位;一旦画出轨迹,问题立刻暴露。
4.2 最小可视化代码:用Python画一条三维轨迹
如果你的作业偏向视觉SLAM或者需要快速验证结果,我推荐先用Python做可视化原型,不要一上来就写C++的Pangolin。用Python验证逻辑,逻辑对了再移植到C++代码里,调试效率会高出很多。
下面给一个最小可用的三维轨迹绘制脚本。假设你有一个TUM格式的轨迹文件,每行是timestamp tx ty tz qx qy qz qw:
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def quat_to_rotmat(q): q = q / np.linalg.norm(q) x, y, z, w = q return np.array([ [1-2*(y*y+z*z), 2*(x*y-z*w), 2*(x*z+y*w)], [2*(x*y+z*w), 1-2*(x*x+z*z), 2*(y*z-x*w)], [2*(x*z-y*w), 2*(y*z+x*w), 1-2*(x*x+y*y)] ]) data = np.loadtxt('trajectory.txt') positions = [] for row in data: t = row[1:4] q = row[4:8] # 注意TUM格式是qx qy qz qw R = quat_to_rotmat(q) # 如果需要把相机系坐标转到世界系,可在这里做R @ point positions.append(t) positions = np.array(positions) fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') ax.plot(positions[:, 0], positions[:, 1], positions[:, 2], linewidth=2) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') ax.set_title('Trajectory') plt.savefig('trajectory.png', dpi=150)这个脚本足够简单,但能处理一个非常关键的问题:验证你的轨迹数据是否包含了明显的错误变换。如果你在代码里做位姿变换时把旋转矩阵写反了,画出来的轨迹会立刻出现“整体镜像”或者“来回折返”的现象。把这些异常情况当成“代码有bug的信号”而不是“数据有问题”,能少走很多弯路。
4.3 我在可视化阶段发现的“假正确”案例
所谓“假正确”,是指代码输出的数值误差很小,运行也不报错,但最终结果在几何上完全是错误的。这种情况特别容易出现在旋转矩阵的转置和逆、四元数的共轭这些概念上。
一个非常典型的例子:在某个作业版本中,需要把世界坐标系下的点变换到相机坐标系下,正确操作是乘以相机位姿的逆矩阵R^T·(p - t)。有同学写成了R·(p - t),从数值上看,每个点都经历了一个合法的旋转变换,没有任何维度错误或者NaN,输出的“轨迹”也是一条平滑曲线——但是这条轨迹,和真实轨迹相比,正好是在球面上被旋转了一个固定角度。如果是单目SLAM的轨迹,这种全局旋转误差甚至不会被闭环检测发现,只有在和真值轨迹对齐时才会露出马脚。
排查这种“假正确”问题,最有效的方式是拿一个已知点做手算验证。比如把世界坐标系原点(0,0,0)经某个位姿变换后的结果算出来,手动推导一遍,再对比代码输出。如果一致再继续;如果不一致,就说明变换链路中某个环节的矩阵方向搞反了。这个习惯我一直延续到了后续所有作业里——凡是涉及位姿变换的代码,必须先做“点的手算测试”,再做整体系统测试。
可视化还能帮你发现另一个隐蔽问题:轨迹的尺度漂移。如果把轨迹画出来发现整体形状合理、但尺度明显不对(比如实际走了10米,画出来像走了100米),那问题大概率出在单目SLAM的尺度不确定性或者位置更新时单位不一致上。绘制轨迹时应该同时打印轨迹总长度,和真实值做对比,很多调参问题瞬间就有了解题思路。
5. 实测总结:一份能直接参考的作业检查清单
5.1 提交之前按这个顺序自查
深蓝学院的作业评分通常由“正确性”和“完成度”两部分构成。正确性指你的代码输出是否与参考答案一致,完成度指你是否把扩展部分、分析图表和文字结论做完整。很多同学代码实现没问题,但提交时漏了分析图表,或者推导题只给了结果没给过程,白白丢分。
我在写完第三章作业后总结了一份自查清单,每次都按这个顺序过一遍:
- 三个旋转表示(旋转矩阵、四元数、旋转向量)是否互相验证一致?角度是否做了归一化和边界处理?
- so3与SO3的互转重构误差是否小于1e-6?如果误差偏大,检查对数映射提取旋转轴时是否对模长做了保护。
- 解析雅可比与数值差分之间的误差是否在1e-5以内?如果超了,先检查数值差分是否是中心差分,再检查扰动轴是否单位化。
- 位姿变换时是否混用了R和R^T?建议针对世界系原点做一个手算测试,确保方向正确。
- 轨迹可视化是否平滑、无跳变、总尺度与预期一致?所有位姿数据的坐标系约定是否统一?
- 推导题的步骤是否完整、符号是否一致?左扰动和右扰动有没有混用?
这份清单看起来简单,但每一条都对应着我在第三章作业和后续课程中实际踩过的坑。如果你提交前能逐条确认,基本可以避免80%以上的低级错误。
5.2 时间分配:别在第一章的衍生物上耗太久
从我自己的经验以及大量学员的反馈来看,第三章作业理论推导花的时间占比不应超过40%,代码调试不应超过40%,剩下20%应该留给分析和总结。
很多同学会在理论推导的“完美性”上过度纠结。BCH近似的推导确实重要,但作业要求的深度通常是“会用近似公式解决实际问题”,而不是“从零证明BCH公式”。如果你在某个推导步骤上卡了超过两个小时,建议先跳过去,用数值实验感受一下结论,再回头补推导。很多时候,数值实验会反过来帮你理解推导中卡住的那个关键步骤。
代码调试的时间也要设上限。警惕“再试一个参数组合就能跑通”的心态。如果连续尝试了多个不同方案还没解决,最有效的做法是把问题抽象成最小示例,单独写一个十几行的测试程序来定位,而不是在完整作业代码里打日志。第三章的代码量不大,但如果不加控制地调试,消耗十几个小时完全正常。
5.3 第三章作业和后面内容的衔接点:现在打牢底子,第四章更轻松
第三章不是孤立的一章。等进入相机模型和投影几何章节,你会发现它的本质就是对第三章位姿变换的延伸——相机的内参、外参、对极约束里的本质矩阵和基础矩阵,本质上都是三维空间刚体运动的某种紧凑表示方式。第四章的很多推倒卡壳点,比如讲讲极线约束时为什么要用到反对称矩阵、求基础矩阵时为什么要引入齐次坐标,根源都在第三章的矩阵运算和坐标变换基础。
后端优化也会大量用到第三章的结论。BA里的雅可比矩阵,有一大半是位姿对误差项的导数,本质上就是在用左扰动模型推导的结论。如果你在第三章就把扰动模型的推导吃透了,后面学习高斯牛顿法和LM算法时会非常顺畅。反之,如果第三章的雅可比推导只是背了个结论,后面到了手写BA的时候,光一个雅可比矩阵的结构就能让你怀疑人生。
我自己毕业之后回头复盘,发现第三章作业其实就是整个SLAM课程的分水岭。认认真真把它吃透的人,后面不管是做视觉SLAM还是激光SLAM、做状态估计还是位姿图优化,都会觉得“工具箱里的工具是顺手的”;而潦草应付过去的人,往往要到第五章、第六章再回来补第三章的课,学习成本成倍增加。既然已经写到这里了,就再多说一句:第三章作业里那道让你画轨迹误差曲线的题目,不要只画图交差,试着在总结里写一句“当旋转角小于多少弧度时BCH近似误差可忽略”的结论,这个结论才是作业真正想让你带走的东西。