第九章 位姿估计应用(优化版)
Chapter 9 Pose Estimation Applications (Optimized)
本章导航 / Chapter Navigator
一句话总结(TL;DR)
本章用 Lie 群工具解决四个经典 3D 估计问题:点云对齐(Wahba)、点云跟踪(EKF)、位姿图优化(SLAM 后端)、惯性导航(IMU)。
阅读路径建议:
- 🟢 快速浏览:读完”直觉图解”即可把握核心思想
- 🔵 理解算法:阅读”概念层”的公式和解释
- ⚫ 深度推导:展开”推导层”查看完整数学
9.1 点云对齐:找最佳旋转和平移
🎯 直觉:拼三维拼图
你有一张局部照片(车体系中的点云)和一份地图(固定系中的点云),要找到相机的位姿。
关键约束:
- 旋转矩阵 必须满足 ,
- 这使它不能用普通最小二乘
好消息:这个问题没有局部极小值!任何满足一阶条件的临界点要么是全局最小,要么是鞍点/极大值。
🔑 概念层:三种等价解法
| 方法 | 核心工具 | 优点 |
|---|---|---|
| 四元数 | 4×4 特征值问题 | 解析解,一次性算出最优旋转 |
| 旋转矩阵(SVD) | 3×3 SVD | 全局分析,处理退化情形 |
| 迭代 | 左扰动 Gauss-Newton | 同时优化旋转+平移 |
SVD 解法步骤:
-
计算去心点对的互协方差矩阵:
-
SVD 分解:
-
最优旋转:
-
最优平移:
注意: 的最后一个对角元取 是为了确保 (真旋转而非反射)。
🧮 数值示例:ICP 核心步骤
场景:两个点云各有 3 个点(已找到对应关系)
- 固定系点云:, ,
- 车体系观测:, ,
计算:
- 质心:,
- 去心后计算互协方差:
- 结果:(接近绕z轴90°旋转)
- SVD:
- 最优旋转:(绕 z 轴转 90°)
验证:, ✓
📐 推导层:为什么 SVD 给出最优解
展开:从优化目标到 SVD 解
目标:最大化 约束 。
对任意正交矩阵 ,有:
令 ,它也是正交矩阵。
当 时取等号,即 。
如果 ,需要调整最后一个对角元为 -1 以保证 。
9.2 点云跟踪:时间序列的位姿估计
🎯 直觉:同时用”运动模型”和”观测”
问题:机器人移动时,每个时刻观测地标点,估计整条轨迹。
两种信息:
- 运动先验:从里程计/控制输入知道”大概怎么移动”
- 测量信息:地标观测告诉”实际在哪里”
EKF 的做法:
- 预测:按运动模型推进
- 校正:用地标观测修正
🔑 概念层:李群版 EKF
状态:,每个
关键创新:均值在 ,协方差在 ( 矩阵)
预测步骤:
- 均值:(矩阵乘法)
- 协方差:
其中 是伴随矩阵。
重要性质: 只依赖于输入 ,不依赖于当前状态估计!
校正步骤:
- 卡尔曼增益:
- 协方差更新:
- 均值更新:
测量 Jacobian:,其中 是第 8 章定义的齐次点算子。
🔑 概念层:批量 MAP 方法
同时优化整条轨迹,目标函数:
运动误差(在李代数中):
高斯-牛顿迭代:
- 在当前猜测 处线性化
- 构建块稀疏矩阵方程
- 求解,更新:
- 重复直到收敛
9.3 位姿图松弛:SLAM 后端
🎯 直觉:解 inconsistent 的拼图
场景:机器人绕了一圈回到起点,但里程计说”还没到”。
位姿图:
- 节点:各时刻的位姿
- 边:相对位姿测量
问题:测量 inconsistent(绕闭环不等于单位),需要”松弛”找到全局一致的估计。
🔑 概念层:误差函数与优化
移动系不变误差:
直觉: 是从 到 的真实相对变换,与测量 比较。
目标函数:
高斯-Newton 步骤:
- 线性化误差:
- 构建稀疏系统
- 的稀疏性 = 位姿图的连通性
初始化:找生成树,从根节点通过复合测量初始化所有位姿。
9.4 惯性导航:IMU 融合
🎯 直觉:高速传感器 + 低速校正
IMU 特性:
- 输出频率高(100-1000 Hz)
- 积分会漂移(误差累积)
- 需要与其他传感器(GPS、视觉)融合
核心问题:如何在 Lie 群框架中处理加速度积分?
🔑 概念层: 扩展位姿
创新:把旋转、位置、速度打包成 矩阵:
优势:一次矩阵乘法完成”旋转+平移+速度更新”!
右扰动方案(本书唯一使用右扰动的地方):
原因:IMU 噪声自然出现在运动方程的”车体侧”。
🔑 概念层:IMU 运动学积分
标称更新(假设测量在采样间隔内恒定):
其中 是左雅可比, 是新函数(考虑旋转时的加速度积分)。
状态维度:15(9 维扩展位姿扰动 + 6 维偏置)
EKF 流程:
- 预测:对每个 IMU 数据传播均值和协方差
- 更新:当有外部测量(GPS、相机)时,标准 EKF 校正
本章自检清单
阅读完本章,你应该能回答:
- 点云对齐的 SVD 解法步骤是什么?(互协方差 → SVD → )
- 为什么点云对齐没有局部极小值?(任何临界点要么是全局最小,要么是鞍点)
- 李群版 EKF 的均值和协方差分别在哪里?(均值在 ,协方差在 )
- 位姿图优化的核心误差函数是什么?()
- 包含哪些量?(旋转、位置、速度)
- IMU 导航为什么用右扰动?(噪声出现在运动方程的车体侧)
如果以上都清楚,你掌握了 3D 位姿估计的核心!
延伸阅读与代码
Python 实现(ICP 核心):
import numpy as np
def skew(v):
return np.array([[0, -v[2], v[1]],
[v[2], 0, -v[0]],
[-v[1], v[0], 0]])
def point_cloud_alignment(src_pts, dst_pts, weights=None):
"""
用 SVD 求解点云对齐(Wahba 问题)
src_pts, dst_pts: [N, 3] 数组
返回: (R, t) 使得 dst ≈ R @ src + t
"""
N = src_pts.shape[0]
if weights is None:
weights = np.ones(N) / N
else:
weights = weights / np.sum(weights)
# 质心
src_mean = np.sum(weights[:, None] * src_pts, axis=0)
dst_mean = np.sum(weights[:, None] * dst_pts, axis=0)
# 去心
src_centered = src_pts - src_mean
dst_centered = dst_pts - dst_mean
# 互协方差矩阵
W = np.sum(weights[:, None, None] *
dst_centered[:, :, None] @ src_centered[:, None, :], axis=0)
# SVD
U, D, Vt = np.linalg.svd(W)
# 修正符号确保 det(R) = +1
S = np.eye(3)
S[2, 2] = np.sign(np.linalg.det(U @ Vt))
R = U @ S @ Vt
t = dst_mean - R @ src_mean
return R, t
def se3_ekf_predict(T, P, xi, Q, dt):
"""
SE(3) EKF 预测步骤
T: 当前位姿 (4x4)
P: 协方差 (6x6)
xi: 广义速度 [v; omega] (6,)
Q: 过程噪声 (6x6)
"""
# 均值预测
Xi = np.eye(4)
Xi[:3, :3] = so3_exp(dt * xi[3:])
Xi[:3, 3] = dt * xi[:3] # 简化;实际用左雅可比
T_pred = Xi @ T
# 协方差预测(伴随矩阵)
F = se3_adjoint(Xi)
P_pred = F @ P @ F.T + Q
return T_pred, P_pred
def se3_ekf_update(T_pred, P_pred, y, p, R):
"""
SE(3) EKF 校正步骤
y: 观测 (3,)
p: 地标在固定系中的位置 (3,)
R: 测量噪声协方差 (3x3)
"""
# 预测观测
p_homo = np.append(p, 1)
z = T_pred @ p_homo
y_pred = z[:3]
# 测量 Jacobian
G = np.zeros((3, 6))
G[:, :3] = np.eye(3)
G[:, 3:] = -skew(y_pred)
# 卡尔曼增益
S = G @ P_pred @ G.T + R
K = P_pred @ G.T @ np.linalg.inv(S)
# 更新
innovation = y - y_pred
delta_xi = K @ innovation
# 均值更新(指数映射)
delta_T = np.eye(4)
delta_T[:3, :3] = so3_exp(delta_xi[3:])
delta_T[:3, 3] = delta_xi[:3]
T = delta_T @ T_pred
# 协方差更新
P = (np.eye(6) - K @ G) @ P_pred
return T, P
def pose_graph_optimization(poses, edges, max_iter=10):
"""
位姿图优化(简化版)
poses: [N, 4, 4] 初始位姿
edges: [(i, j, T_ij_meas, Sigma_inv)] 边列表
"""
T = poses.copy()
for _ in range(max_iter):
# 构建线性系统 A x = b
# 这里简化,只展示核心思想
for (i, j, T_meas, Sigma_inv) in edges:
# 误差: ln(T_meas @ T_j @ T_i^{-1})^vee
error_T = T_meas @ T[j] @ np.linalg.inv(T[i])
e = so3_log(error_T[:3, :3]) # 简化,只取旋转部分
# 构建 Jacobian(省略细节)
# 求解更新量并应用
# 收敛检查
return T下一章将处理同时估计位姿和地标位置的 SLAM 问题。