Hanjie's Blog

一只有理想的羊驼

GoPro IMU 静止时为什么重力加速度是“+9.81” 而不是“−9.81”?——一个容易误解的物理真相

几乎所有用过 GoPro 原始 IMU 数据的人,都会在第一时间产生同一个疑惑:

相机明明静静地躺在那里,Z 轴却稳稳输出 +9.81 m/s²,这不是反了吗?重力不是向下吗?不是应该显示 −9.81 才对吗?

结论先说在前头:
GoPro 没有反,它反而是目前所有运动相机里做得最正确、最符合物理教科书定义的那一个。
真正“反”的是我们大部分人的直觉。

1. 加速度计到底在测什么?

加速度计测量的不是“重力加速度”,而是物理学里严格定义的 proper acceleration(固有加速度),爱因斯坦在广义相对论里也用的就是这个词。

它的定义是:

传感器外壳相对于自由落体状态的相对加速度

换成大白话:
“要让这个传感器不掉下去,外壳必须受到多大的非重力加速度?”

2. 两个决定性的思想实验

场景 你认为的“运动加速度” 桌面/手对相机的真实作用力 加速度计读数(proper acceleration) GoPro 实际输出
相机在真空中自由落体 −9.81 m/s²(向下) 0(完全失重) 0 ≈ 0
相机静静放在桌子上 0(静止) 向上推力 = mg +9.81 m/s²(向上) ≈ +9.81

结论来了:
静止放在地球表面时,加速度计显示 +9.81 m/s²(Z向上)才是唯一正确的!
因为桌面正在用向上的力“加速”相机,阻止它下落,这个加速度的大小正好是 9.81 m/s²,方向向上。

3. 为什么我们总觉得“应该显示 −9.81”?

因为我们在写运动方程时习惯这样写:

1
实际运动加速度 = 加速度计读数 − 重力加速度向量(0,0,−9.81)

所以我们希望加速度计“帮忙”直接输出 −9.81,这样减法最简单。
这只是工程上的习惯约定,不是物理本质。

GoPro、iPhone 原始传感器、航空惯导、导弹制导……所有真正讲究的系统,都直接输出 proper acceleration,也就是静止时 +9.81。

4. 实际验证(左右平移实验)

把相机水平向左(+X 方向)加速 → X 轴读数为正 → 完全正确
向左减速(刹车)→ X 轴读数为负 → 完全正确

这进一步证明:GoPro 的加速度计在动态线性加速度上也是完全符合物理方向的。 imu

5. 正确处理 GoPro IMU 数据的方法(推荐)

1
2
3
4
5
6
7
8
# accel_raw 来自 GPMF,直接就是 proper acceleration(单位 m/s²)
ax, ay, az = accel_raw

# 想要得到“纯运动的线性加速度”(去掉重力影响):
gravity = np.array([0, 0, 9.81]) # 注意是正值!
accel_linear = np.array([ax, ay, az]) - gravity @ R # R 为重力在机体坐标系的方向
# 或者如果你已经知道相机姿态:
accel_linear = accel_raw - R.T @ [0, 0, 9.81]

Kabsch 算法

Kabsch 算法是一种用于求解两组三维点(或向量)之间最佳旋转矩阵的经典方法,广泛应用于计算机视觉、机器人学和分子动力学等领域。在你的问题中,Kabsch 算法被用来求解旋转矩阵 \(R\),使得摄像头的角速度 \(\text{gyros}_{\text{cam}}\) 和 IMU 的角速度 \(\text{gyros}_{\text{imu}}\) 之间的均方误差

\[\|\text{gyros}_{\text{cam}} - R \cdot \text{gyros}_{\text{imu}}\|_{2}^{2}\]

最小化。以下是对 Kabsch 算法数学原理的详细解析。

1. 问题定义

假设我们有两组三维向量:

  • \(A = \{\mathbf{a}_{i}\}_{i=1}^{N}\),表示 IMU 的角速度 \(\text{gyros}_{\text{imu}}\),每个 \(\mathbf{a}_{i} \in \mathbb{R}^{3}\)
  • \(B = \{\mathbf{b}_{i}\}_{i=1}^{N}\),表示摄像头的角速度 \(\text{gyros}_{\text{cam}}\),每个 \(\mathbf{b}_{i} \in \mathbb{R}^{3}\)

目标是找到一个旋转矩阵 \(R \in \mathrm{SO}(3)\)(即满足 \(R^{T}R = I\)\(\det(R) = 1\)),使得以下误差最小化:

\[E = \sum_{i=1}^{N} \|\mathbf{b}_{i} - R\mathbf{a}_{i}\|_{2}^{2}\]

这里,\(R\mathbf{a}_{i}\) 表示将 IMU 角速度 \(\mathbf{a}_{i}\) 旋转到摄像头坐标系后的向量。

2. 数学推导

2.1 中心化数据(在角速度标定中通常省略)

在点云配准中常需要中心化,但在角速度对齐问题中不需要,直接使用原始数据即可。

2.2 目标函数展开

\[ \begin{align*} E &= \sum_{i=1}^{N} \|\mathbf{b}_{i} - R\mathbf{a}_{i}\|_{2}^{2} \\ &= \sum_{i=1}^{N} \left( \mathbf{b}_{i}^{T}\mathbf{b}_{i} - 2\mathbf{b}_{i}^{T}R\mathbf{a}_{i} + \mathbf{a}_{i}^{T}R^{T}R\mathbf{a}_{i} \right) \end{align*} \]

因为 \(R^{T}R = I\),上式简化为

\[E = \sum_{i=1}^{N}\mathbf{b}_{i}^{T}\mathbf{b}_{i} + \sum_{i=1}^{N}\mathbf{a}_{i}^{T}\mathbf{a}_{i} - 2\sum_{i=1}^{N}\mathbf{b}_{i}^{T}R\mathbf{a}_{i}\]

前两项与 \(R\) 无关,故最小化 \(E\) 等价于最大化

\[\sum_{i=1}^{N} \mathbf{b}_{i}^{T} R \mathbf{a}_{i}\]

2.3 协方差矩阵

定义 \(3\times N\) 矩阵 \(A\)\(B\)(列分别为 \(\mathbf{a}_{i}\)\(\mathbf{b}_{i}\)),则

\[\sum_{i=1}^{N} \mathbf{b}_{i}^{T} R \mathbf{a}_{i} = \operatorname{trace}(B^{T} R A)\]

令协方差矩阵

\[H = AB^{T} = \sum_{i=1}^{N} \mathbf{a}_{i} \mathbf{b}_{i}^{T} \in \mathbb{R}^{3\times 3}\]

目标变为最大化 \(\operatorname{trace}(R H)\)

2.4 奇异值分解(SVD)

\(H\) 进行 SVD:

\[H = U \Sigma V^{T}\]

其中 \(\Sigma = \operatorname{diag}(\sigma_{1}, \sigma_{2}, \sigma_{3})\),且

\[\sigma_{1} \geq \sigma_{2} \geq \sigma_{3} \geq 0\]

\[\operatorname{trace}(R H) = \operatorname{trace}(R U \Sigma V^{T}) = \operatorname{trace}(\Sigma V^{T} R U)\]

\(S = V^{T} R U\)\(S\) 为正交矩阵),最大化 \(\operatorname{trace}(\Sigma S)\) 的最优解为 \(S = I\),此时

\[\operatorname{trace}(\Sigma S) = \sigma_{1} + \sigma_{2} + \sigma_{3}\]

因此

\[V^{T} R U = I \quad \Rightarrow \quad R = V U^{T}\]

2.5 修正行列式(确保 \(\det(R) = +1\)

计算 \(R = V U^{T}\) 后,若 \(\det(R) = -1\)(反射),则修正为:

\[V' = V \cdot \operatorname{diag}(1, 1, -1)\]

\[R = V' U^{T}\]

此时 \(\det(R) = +1\),为合法旋转矩阵。

3. 算法步骤总结

  1. 构造协方差矩阵
    \[H = \sum_{i=1}^{N} \mathbf{a}_{i} \mathbf{b}_{i}^{T}\]

  2. \(H\) 进行 SVD
    \[H = U \Sigma V^{T}\]

  3. 计算 \(R = V U^{T}\)

  4. \(\det(R) < 0\),则 \(V \leftarrow V \cdot \operatorname{diag}(1,1,-1)\),重新计算 \(R\)

4. 在你的代码中的实现(C++/Eigen)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 2. 构建协方差矩阵 H = sum(P_imu * P_cam^T)
Eigen::Matrix3d H = Eigen::Matrix3d::Zero();
for (size_t i = 0; i < gyros_cam.size(); ++i) {
// P_cam 是 gyros_cam, P_imu 是 gyros_imu_synced
H += gyros_imu_synced[i] * gyros_cam[i].transpose();
}

// 3. 对 H 进行 SVD 分解
Eigen::JacobiSVD<Eigen::Matrix3d> svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV);
const Eigen::Matrix3d& U = svd.matrixU();
const Eigen::Matrix3d& V = svd.matrixV();

// 4. 计算旋转矩阵 R = V * U^T
Eigen::Matrix3d R = V * U.transpose();

// 确保 R 是一个纯旋转矩阵 (det(R) = +1)
if (R.determinant() < 0) {
std::cout << "检测到反射,正在修正..." << std::endl;
Eigen::Matrix3d V_prime = V;
V_prime.col(2) *= -1; // 翻转最后一列的符号
R = V_prime * U.transpose();
}

// --- 标定结束,输出结果 ---
std::cout << "\n--- 标定结果 ---" << std::endl;
std::cout << "计算出的旋转矩阵 R (IMU -> Camera):" << std::endl;
std::cout << R << std::endl;

// (可选) 验证误差
double total_error = 0.0;
for (size_t i = 0; i < gyros_cam.size(); ++i) {
Eigen::Vector3d error_vec = gyros_cam[i] - R * gyros_imu_synced[i];
total_error += error_vec.squaredNorm();
}
double mean_squared_error = total_error / gyros_cam.size();
std::cout << "\n标定后的均方误差 (MSE): " << mean_squared_error << std::endl;

5. 数学性质与局限性

5.1 性质
  • 唯一性:如果数据点 $ {i} $ 和 $ {i} $ 线性无关(即 $ H $ 的秩为 3),Kabsch 算法给出的 $ R $ 是唯一的全局最优解。
  • 效率:SVD 分解的计算复杂度为 $ O(n) $,其中 $ n $ 是数据点数量,适合实时应用。
  • 几何意义:Kabsch 算法本质上是找到一个旋转,将一组向量尽可能对齐到另一组向量。
5.2 局限性
  • 噪声敏感性:如果 $ {} $ 或 $ {} $ 包含大量噪声,Kabsch 算法可能不够鲁棒,需要预处理数据(如滤波)。
  • 时间同步:算法假设两组向量是一一对应的。如果时间戳未对齐,需先进行插值。
  • 退化情况:如果 $ {i} $ 或 $ {i} $ 线性相关(例如所有向量共线),SVD 分解可能不稳定,导致 $ R $ 不唯一。

7. 总结

Kabsch 算法通过 SVD 分解协方差矩阵 $ H = A B^T $,高效地求解最佳旋转矩阵 $ R $,其核心数学原理是最大化 $ (R H) $。在你的应用中,它将 IMU 角速度旋转到摄像头坐标系,适用于时间同步后的角速度数据。对于噪声较大或复杂约束的情况,可以结合非线性优化进一步提高精度。

由于Chrome从v140版本后就不再支持manifest-v2,导致很多插件无法使用。

降低版本到v139

从这里chrome-versions寻找到版本为139.0.7258.155的chrome,下载后安装并且替代掉现有的chrome。

禁止Chrome自动更新1

删除和设置权限

删除更新程序:

1
rm -rf ~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle

将更新程序目录设置为系统级别只读:

1
sudo chflags schg ~/Library/Google/GoogleSoftwareUpdate

要恢复:

1
sudo chflags noschg ~/Library/Google/GoogleSoftwareUpdate

如果是全新系统或者新建的用户,未运行 Chrome,可以手动创建上述文件夹再设置为只读

1
2
mkdir -p ~/Library/Google/GoogleSoftwareUpdate
sudo chflags schg ~/Library/Google/GoogleSoftwareUpdate

修改hosts

手动编辑,打开终端,执行:

1
sudo vi /etc/hosts

添加:

1
2
127.0.0.1 update.googleapis.com
127.0.0.1 tools.google.com

以上两步任意一个已经可以屏蔽自动更新,同时操作更加保险。

强制打开manifest-v2 2

打开chrome后,输入:

1
chrome://flags/#temporary-unexpire-flags-m137

设置为Enabled,然后重启浏览器。再次把下面几项的按照这样配置:

1
2
3
4
chrome://flags/#extension-manifest-v2-deprecation-warning [Disabled]
chrome://flags/#extension-manifest-v2-deprecation-disabled [Disabled]
chrome://flags/#extension-manifest-v2-deprecation-unsupported [Disabled]
chrome://flags/#allow-legacy-mv2-extensions [Enabled]

最后重启一次浏览器, 就可以强制启用了。


  1. https://sysin.org/blog/disable-chrome-auto-update/↩︎

  2. https://us.v2ex.com/t/1144634↩︎

0%