4、抖动量化指标:用数字说话
做轨迹优化,最怕什么?
怕「我觉得不抖了」,但客户说「还是晕」。
所以我们必须把抖动变成数字。没有量化,就没有优化。这一章,我就带你看看我平时最常用的四个指标:ATE、RPE、平滑度指标和频谱分析。每个指标都有它的脾气,用对了地方,问题一眼就能看穿。
核心观点: 量化指标是你的「第三只眼」。ATE看全局漂移,RPE看局部抖动,平滑度看运动是否「丝滑」,频谱看抖动来自哪个频段。四个指标组合使用,才能全面诊断。
4.1 绝对轨迹误差(ATE)
ATE,全称 Absolute Trajectory Error。说白了,就是算你估计的轨迹和真实轨迹之间,每个点差了多少。
我习惯先做一步:把两条轨迹对齐。因为ATE对坐标系原点敏感,不先对齐的话,误差里会混入平移和旋转的偏差,那不是我们想看的抖动。
计算步骤
- 时间戳对齐:把估计轨迹和真值轨迹按时间配对。
- 位姿对齐:用Umeyama算法或ICP,把估计轨迹对齐到真值坐标系。
- 逐点计算误差:每个配对点,算平移向量的欧氏距离。
- 统计:RMSE、均值、中位数、标准差。
import numpy as np
def compute_ate(est_poses, gt_poses):
"""
est_poses, gt_poses: Nx4x4 齐次变换矩阵
返回 RMSE, mean, median, std
"""
errors = []
for est, gt in zip(est_poses, gt_poses):
t_est = est[:3, 3]
t_gt = gt[:3, 3]
error = np.linalg.norm(t_est - t_gt)
errors.append(error)
errors = np.array(errors)
rmse = np.sqrt(np.mean(errors ** 2))
return rmse, np.mean(errors), np.median(errors), np.std(errors)
我的习惯: 只看RMSE不够。有一次项目里RMSE只有0.05m,但轨迹末端漂了0.2m。后来我加了中位数和最大误差,才发现问题。建议你至少看RMSE + 最大误差 + 标准差。
4.2 相对位姿误差(RPE)
ATE看的是全局,RPE看的是局部。它衡量的是「每隔一段距离/时间,位姿变化量的误差」。说白了,就是看你的轨迹在短时间内的相对运动准不准。
RPE对抖动特别敏感。因为抖动往往表现为高频的小幅度位姿变化,ATE可能被平均掉,但RPE能抓住。
两种常见形式
| 类型 | 含义 | 适用场景 |
|---|---|---|
| 平移RPE | 相对平移误差(m) | 评估局部位置抖动 |
| 旋转RPE | 相对旋转误差(deg) | 评估局部姿态抖动 |
def compute_rpe(est_poses, gt_poses, delta=1):
"""
delta: 间隔帧数
返回平移RPE和旋转RPE的列表
"""
trans_errors = []
rot_errors = []
for i in range(len(est_poses) - delta):
# 计算相对变换
est_rel = np.linalg.inv(est_poses[i]) @ est_poses[i + delta]
gt_rel = np.linalg.inv(gt_poses[i]) @ gt_poses[i + delta]
# 误差矩阵
err_mat = np.linalg.inv(gt_rel) @ est_rel
# 平移误差
trans_err = np.linalg.norm(err_mat[:3, 3])
# 旋转误差(角度)
rot_err = np.arccos(np.clip((np.trace(err_mat[:3, :3]) - 1) / 2, -1, 1))
trans_errors.append(trans_err)
rot_errors.append(rot_err)
return trans_errors, rot_errors
注意: delta的选择很关键。delta太小,RPE对噪声敏感;delta太大,又失去了局部性。我一般先试delta=1(帧间),再试delta=10(短时窗口),对比看。
4.3 平滑度指标:Jerk和加速度方差
ATE和RPE都需要真值轨迹。但很多时候,我们没有真值。比如在未知环境里跑机器人,你拿什么当ground truth?
这时候,平滑度指标就派上用场了。它只依赖轨迹本身,不需要真值。
Jerk(加加速度)
Jerk是加速度的变化率。物理意义很直观:加速度变化越剧烈,运动越「顿挫」。人坐在车上,感觉到的就是Jerk。
def compute_jerk(positions, dt):
"""
positions: Nx3 位置序列
dt: 时间间隔
返回每个点的Jerk大小
"""
# 速度
vel = np.diff(positions, axis=0) / dt
# 加速度
acc = np.diff(vel, axis=0) / dt
# 加加速度
jerk = np.diff(acc, axis=0) / dt
# 每个点的Jerk大小(L2范数)
jerk_mag = np.linalg.norm(jerk, axis=1)
return jerk_mag
经验值: 在室内移动机器人上,Jerk均值小于0.5 m/s³时,人基本感觉不到抖动。超过2.0 m/s³,就会明显不舒服。当然,这个阈值跟应用场景有关——无人机可以放宽,手术机器人必须更严。
加速度方差
加速度方差衡量的是加速度的离散程度。方差大,说明加速度忽大忽小,运动不平滑。
def compute_acc_variance(positions, dt):
vel = np.diff(positions, axis=0) / dt
acc = np.diff(vel, axis=0) / dt
# 每个轴的方差
var_x = np.var(acc[:, 0])
var_y = np.var(acc[:, 1])
var_z = np.var(acc[:, 2])
# 总方差(迹)
total_var = var_x + var_y + var_z
return total_var
我的经验: 加速度方差和Jerk是互补的。Jerk对瞬时冲击敏感,加速度方差对整体波动敏感。我一般两个都算,如果Jerk小但方差大,说明有持续的低频抖动;如果Jerk大但方差小,说明有偶发的冲击。
4.4 频谱分析(FFT)
前面几个指标告诉你「抖不抖」,但没告诉你「为什么抖」。频谱分析就是干这个的。
把轨迹的位置或姿态信号做FFT,转到频域,你就能看到抖动能量集中在哪些频率上。比如:
- 低频(<1 Hz):可能是路径规划的问题,或者传感器漂移。
- 中频(1-10 Hz):可能是控制器的震荡,或者IMU的噪声。
- 高频(>10 Hz):通常是传感器噪声,或者机械振动。
import numpy as np
from scipy.fft import fft, fftfreq
def analyze_spectrum(signal, dt):
"""
signal: 一维信号(比如x轴位置)
dt: 采样间隔
返回频率和幅度
"""
n = len(signal)
yf = fft(signal)
xf = fftfreq(n, dt)[:n//2]
magnitude = 2.0/n * np.abs(yf[:n//2])
return xf, magnitude
注意: FFT之前一定要做去趋势(detrend)和加窗(windowing)。不然频谱里会出现假的低频分量。我吃过这个亏——有一次分析一个轨迹,频谱显示0.2Hz有强峰,折腾了半天才发现是直流分量没去掉。
4.5 知识体系总览
这四个指标不是孤立的。它们从不同角度描述抖动,组合起来才能全面诊断。下面这张图是我自己总结的框架:
4.6 指标选择指南
这么多指标,到底用哪个?我一般按这个逻辑来:
| 场景 | 推荐指标 | 原因 |
|---|---|---|
| 有真值轨迹 | ATE + RPE | 全局+局部,全面评估 |
| 无真值轨迹 | Jerk + 加速度方差 | 不需要真值,自评估平滑度 |
| 需要诊断抖动来源 | 频谱分析 | 定位频段,对症下药 |
| 实时监控 | 加速度方差 | 计算量小,适合嵌入式 |
| 最终验收 | ATE + Jerk | 一个看精度,一个看舒适度 |
我的建议: 别只用一个指标。我见过有人只看ATE,结果轨迹精度很高但抖动剧烈,客户照样投诉。也有人只看Jerk,结果轨迹平滑但漂移严重。四个指标组合使用,才能全面把控。