You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中如何从旋转矩阵恢复构造用的最小XYZ欧拉角

从旋转矩阵恢复最小幅值XYZ小欧拉角

我有一组由小角度XYZ欧拉角构造的3D旋转矩阵,所有原始角度范围为 -20° < angle < 20°,旋转矩阵构造方式如下:
XYZ顺序欧拉角旋转矩阵构造公式
我需要从该旋转矩阵反向求解原始欧拉角,但欧拉角存在多组可行解,目标是在C++中恢复出可构造该旋转矩阵的最小幅值角度。

目前我在C++中使用Eigen::eulerAngles()接口求解角度,但该接口返回的角度范围为 x∈[0:pi] y∈[-pi:pi] z∈[-pi:pi],无法返回负的X轴角度:例如原始X角为 -0.2 弧度时,接口返回的X角为 2.94159 弧度,不符合小角度预期。

测试代码

void eigen_test(float x, float y, float z) {
  auto mat = AngleAxisf(x, Vector3f::UnitX())
    * AngleAxisf(y, Vector3f::UnitY())
    * AngleAxisf(z, Vector3f::UnitZ());

  auto angle = mat.toRotationMatrix().eulerAngles(0,1,2);

  cout << "(" <<  x << ", " << y << ", " << z << ") 
-> (" << angle[2] << ", " << angle[1] << ", " << angle[0] << ")" << endl;

}

int main() {
  std::vector<float> num = { -0.2,0.,0.2 };
  for (auto x : num) {
    for (auto y : num) {
      for (auto z : num) {
        eigen_test(x, y, z);
      }
    }
  }

  return 0;
}

完整测试结果

X    , Y   , Z     -> X'   , Y'   , Z'
__________________________________________________
(-0.2, -0.2, -0.2) -> (2.94159, -2.94159, 2.94159)
(-0.2, -0.2, 0   ) -> (2.94159, -2.94159, -3.14159)
(-0.2, -0.2, 0.2 ) -> (2.94159, -2.94159, -2.94159)
(-0.2, 0,    -0.2) -> (2.94159, -3.14159, 2.94159)
(-0.2, 0,    0   ) -> (2.94159, 3.14159, -3.14159)
(-0.2, 0,    0.2 ) -> (2.94159, 3.14159, -2.94159)
(-0.2, 0.2,  -0.2) -> (2.94159, 2.94159, 2.94159)
(-0.2, 0.2,  0   ) -> (2.94159, 2.94159, 3.14159)
(-0.2, 0.2,  0.2 ) -> (2.94159, 2.94159, -2.94159)
(0,   -0.2,  -0.2) -> (0, -0.2, -0.2)
(0,   -0.2,  0   ) -> (0, -0.2, 0)
(0,   -0.2,  0.2 ) -> (3.14159, -2.94159, -2.94159)
(0,   0,     -0.2) -> (0, 0, -0.2)
(0,   0,     0   ) -> (-0, 0, -0)
(0,   0,     0.2 ) -> (-0, 0, 0.2)
(0,   0.2,   -0.2) -> (3.14159, 2.94159, 2.94159)
(0,   0.2,   0   ) -> (-0, 0.2, 0)
(0,   0.2,   0.2 ) -> (0, 0.2, 0.2)
(0.2, -0.2,  -0.2) -> (0.2, -0.2, -0.2)
(0.2, -0.2,  0   ) -> (0.2, -0.2, -0)
(0.2, -0.2,  0.2 ) -> (0.2, -0.2, 0.2)
(0.2, 0,     -0.2) -> (0.2, 0, -0.2)
(0.2, 0,     0   ) -> (0.2, 0, 0)
(0.2, 0,     0.2 ) -> (0.2, 0, 0.2)
(0.2, 0.2,   -0.2) -> (0.2, 0.2, -0.2)
(0.2, 0.2,   0   ) -> (0.2, 0.2, -0)
(0.2, 0.2,   0.2 ) -> (0.2, 0.2, 0.2)

解决方法

XYZ顺序欧拉角本身存在两组数学等价解,Eigen接口默认返回X角落在[0, pi]区间的那组,不符合小角度场景需求。由于已知所有原始角度都在±20°区间,不存在万向锁问题,只需要生成两组等价解,选择角度幅值和最小的一组即可。

两组等价解的转换规则:

  • 第一组为Eigen直接返回的解:(x1, y1, z1)
  • 第二组等价解为:(x1 - π, π - y1, z1 - π),所有角度需要规整到[-π, π]区间

可直接使用的实现代码

#include <cmath>
#include <Eigen/Core>
#include <Eigen/Geometry>

// 将角度规整到[-pi, pi]区间
inline float wrap_pi(float angle) {
    angle = fmodf(angle + M_PI, 2 * M_PI);
    if (angle < 0) angle += 2 * M_PI;
    return angle - M_PI;
}

// 从旋转矩阵提取最小幅值的XYZ顺序欧拉角
Eigen::Vector3f extract_small_xyz_euler(const Eigen::Matrix3f& rot) {
    Eigen::Vector3f ang = rot.eulerAngles(0, 1, 2);
    float x1 = ang.x(), y1 = ang.y(), z1 = ang.z();
    // 生成第二组等价解
    float x2 = wrap_pi(x1 - M_PI);
    float y2 = wrap_pi(M_PI - y1);
    float z2 = wrap_pi(z1 - M_PI);
    // 比较两组解的角度绝对值和,返回更小的那组
    float cost1 = fabsf(x1) + fabsf(y1) + fabsf(z1);
    float cost2 = fabsf(x2) + fabsf(y2) + fabsf(z2);
    return cost2 < cost1 ? Eigen::Vector3f(x2, y2, z2) : ang;
}

注意:测试代码中打印角度的顺序有误,eulerAngles(0,1,2)返回的向量顺序已经是[X轴角度, Y轴角度, Z轴角度],不需要倒序取angle[2], angle[1], angle[0]。

使用上述函数替换原生eulerAngles调用后,所有测试用例都能正确恢复出原始的-0.2、0、0.2小角度结果,不会返回接近π的大角度值。


内容的提问来源于stack exchange,提问作者Maxime Charrière

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:30:17