30

是否存在将旋转的四元数表示转换为欧拉角表示的现有算法?欧拉表示的旋转顺序是已知的并且可以是六种排列中的任何一种(即xyz、xzy、yxz、yzx、zxy、zyx)。我见过固定旋转顺序的算法(通常是 NASA 航向、倾斜、滚动约定),但不是任意旋转顺序的算法。

此外,由于单个方向存在多个欧拉角表示,因此该结果将是模棱两可的。这是可以接受的(因为方向仍然有效,只是可能不是用户期望看到的方向),但是如果有一种算法可以接受旋转限制(即自由度数和考虑到每个自由度的限制)并在给定这些约束的情况下产生“最合理的”欧拉表示。

我感觉这个问题(或类似问题)可能存在于 IK 或刚体动力学领域。


澄清一下——我知道如何从四元数转换为所谓的“ Tait-Bryan ”表示——我称之为“NASA”约定。这是 zxy 的旋转顺序(假设“Z”轴向上的约定)。我需要一个所有轮换顺序的算法。

那么,可能的解决方案是采用 zxy 顺序转换并从中派生五个其他旋转顺序的其他转换。我想我希望有一个更“总体”的解决方案。无论如何,我很惊讶我无法找到现有的解决方案。

此外,这也许应该是一个单独的问题,任何转换(当然假设已知的旋转顺序)都会选择一个欧拉表示,但实际上有很多。例如,给定 yxz 的旋转顺序,两个表示 (0,0,180) 和 (180,180,0) 是等价的(并且会产生相同的四元数)。有没有办法使用自由度限制来约束解决方案?就像你在 IK 和刚体动力学中所做的那样?即在上面的例子中,如果围绕 Z 轴只有一个自由度,那么可以忽略第二个表示。


我在这个 pdf中找到了一篇可能是算法的论文,但我必须承认我发现逻辑和数学有点难以理解。当然还有其他解决方案吗?任意轮换顺序真的那么罕见吗?当然,每个允许骨骼动画和四元数插值的主要 3D 软件包(即 Maya、Max、Blender 等)都必须完全解决这个问题?

4

8 回答 8

14

这看起来像是一个被忽视的旧技术的经典案例——我设法从车库里挖出了一份Graphics Gems IV的副本,看起来 Ken Shoemake 不仅有一种从任意旋转顺序的欧拉角转换的算法,而且还有答案我关于这个主题的大部分其他问题。万岁的书。要是我能投票赞成 Shoemake 先生的回答并用声望点奖励他就好了。

我想任何使用欧拉角的人都应该从他们当地的图书馆获得一份 Graphics Gems IV 的副本并阅读从第 222 页开始的部分。它必须是我读过的问题的最清晰和最简洁的解释。


这是我发现的一个有用的链接 - http://www.cgafaq.info/wiki/Euler_angles_from_matrix - 这遵循与 Shoemake 相同的系统;24 种不同的旋转顺序排列被编码为四个单独的参数——内轴、奇偶校验、重复和帧——然后你可以将算法从 24 种情况减少到 2 种。一般来说可能是一个有用的维基——我没来之前穿过它。

提供的旧链接似乎被打破了这里是“从旋转矩阵计算欧拉角”的另一个副本。

于 2009-07-15T01:14:21.833 回答
10

在 Z 轴朝上的右手笛卡尔坐标系中,执行以下操作:

struct Quaternion
{
    double w, x, y, z;
};

void GetEulerAngles(Quaternion q, double& yaw, double& pitch, double& roll)
{
    const double w2 = q.w*q.w;
    const double x2 = q.x*q.x;
    const double y2 = q.y*q.y;
    const double z2 = q.z*q.z;
    const double unitLength = w2 + x2 + y2 + z2;    // Normalised == 1, otherwise correction divisor.
    const double abcd = q.w*q.x + q.y*q.z;
    const double eps = 1e-7;    // TODO: pick from your math lib instead of hardcoding.
    const double pi = 3.14159265358979323846;   // TODO: pick from your math lib instead of hardcoding.
    if (abcd > (0.5-eps)*unitLength)
    {
        yaw = 2 * atan2(q.y, q.w);
        pitch = pi;
        roll = 0;
    }
    else if (abcd < (-0.5+eps)*unitLength)
    {
        yaw = -2 * ::atan2(q.y, q.w);
        pitch = -pi;
        roll = 0;
    }
    else
    {
        const double adbc = q.w*q.z - q.x*q.y;
        const double acbd = q.w*q.y - q.x*q.z;
        yaw = ::atan2(2*adbc, 1 - 2*(z2+x2));
        pitch = ::asin(2*abcd/unitLength);
        roll = ::atan2(2*acbd, 1 - 2*(y2+x2));
    }
}
于 2009-06-23T08:05:48.440 回答
9

我一直在寻找类似的解决方案几天,我终于遇到了这个网站,该网站有一个将四元数转换为任意欧拉和泰特-布莱恩旋转的算法!

这是链接:http ://bediyap.com/programming/convert-quaternion-to-euler-rotations/

这是代码:

///////////////////////////////
// Quaternion to Euler
///////////////////////////////
enum RotSeq{zyx, zyz, zxy, zxz, yxz, yxy, yzx, yzy, xyz, xyx, xzy,xzx};

void twoaxisrot(double r11, double r12, double r21, double r31, double r32, double res[]){
  res[0] = atan2( r11, r12 );
  res[1] = acos ( r21 );
  res[2] = atan2( r31, r32 );
}

void threeaxisrot(double r11, double r12, double r21, double r31, double r32, double res[]){
  res[0] = atan2( r31, r32 );
  res[1] = asin ( r21 );
  res[2] = atan2( r11, r12 );
}

void quaternion2Euler(const Quaternion& q, double res[], RotSeq rotSeq)
{
    switch(rotSeq){
    case zyx:
      threeaxisrot( 2*(q.x*q.y + q.w*q.z),
                     q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                    -2*(q.x*q.z - q.w*q.y),
                     2*(q.y*q.z + q.w*q.x),
                     q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                     res);
      break;

    case zyz:
      twoaxisrot( 2*(q.y*q.z - q.w*q.x),
                   2*(q.x*q.z + q.w*q.y),
                   q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                   2*(q.y*q.z + q.w*q.x),
                  -2*(q.x*q.z - q.w*q.y),
                  res);
      break;

    case zxy:
      threeaxisrot( -2*(q.x*q.y - q.w*q.z),
                      q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                      2*(q.y*q.z + q.w*q.x),
                     -2*(q.x*q.z - q.w*q.y),
                      q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                      res);
      break;

    case zxz:
      twoaxisrot( 2*(q.x*q.z + q.w*q.y),
                  -2*(q.y*q.z - q.w*q.x),
                   q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                   2*(q.x*q.z - q.w*q.y),
                   2*(q.y*q.z + q.w*q.x),
                   res);
      break;

    case yxz:
      threeaxisrot( 2*(q.x*q.z + q.w*q.y),
                     q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                    -2*(q.y*q.z - q.w*q.x),
                     2*(q.x*q.y + q.w*q.z),
                     q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                     res);
      break;

    case yxy:
      twoaxisrot( 2*(q.x*q.y - q.w*q.z),
                   2*(q.y*q.z + q.w*q.x),
                   q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                   2*(q.x*q.y + q.w*q.z),
                  -2*(q.y*q.z - q.w*q.x),
                  res);
      break;

    case yzx:
      threeaxisrot( -2*(q.x*q.z - q.w*q.y),
                      q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                      2*(q.x*q.y + q.w*q.z),
                     -2*(q.y*q.z - q.w*q.x),
                      q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                      res);
      break;

    case yzy:
      twoaxisrot( 2*(q.y*q.z + q.w*q.x),
                  -2*(q.x*q.y - q.w*q.z),
                   q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                   2*(q.y*q.z - q.w*q.x),
                   2*(q.x*q.y + q.w*q.z),
                   res);
      break;

    case xyz:
      threeaxisrot( -2*(q.y*q.z - q.w*q.x),
                    q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                    2*(q.x*q.z + q.w*q.y),
                   -2*(q.x*q.y - q.w*q.z),
                    q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                    res);
      break;

    case xyx:
      twoaxisrot( 2*(q.x*q.y + q.w*q.z),
                  -2*(q.x*q.z - q.w*q.y),
                   q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                   2*(q.x*q.y - q.w*q.z),
                   2*(q.x*q.z + q.w*q.y),
                   res);
      break;

    case xzy:
      threeaxisrot( 2*(q.y*q.z + q.w*q.x),
                     q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                    -2*(q.x*q.y - q.w*q.z),
                     2*(q.x*q.z + q.w*q.y),
                     q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                     res);
      break;

    case xzx:
      twoaxisrot( 2*(q.x*q.z - q.w*q.y),
                   2*(q.x*q.y + q.w*q.z),
                   q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                   2*(q.x*q.z + q.w*q.y),
                  -2*(q.x*q.y - q.w*q.z),
                  res);
      break;
    default:
      std::cout << "Unknown rotation sequence" << std::endl;
      break;
   }
}
于 2014-12-16T03:19:33.333 回答
3

我在我的网站 noelhughes.net 上发布了题为“使用几何方法的任意旋转序列的四元数到欧拉角转换”的论文。我还有将任何一组欧拉角转换为四元数和四元数到/从方向余弦矩阵的算法,我将在本周末发布。这些也在 Martin Bakers 的网站上,虽然有点难找。谷歌我的名字,Noel Hughes 和四元数,你应该会找到的。

于 2009-10-27T03:38:15.967 回答
3

我这样解决:

第 1 步:确定你想要的欧拉旋转约定,比如zyx

第 2 步:计算旋转的解析旋转矩阵。例如,如果你想要 R( zyx ),

**R***zyx* = **R***x*( phi ) * **R***y*( theta ) * **R***z*( psi ),其中元素变为

R11 =  cos(theta)*cos(psi)
R12 = -cos(theta)*sin(psi)
R13 =  sin(theta)
R21 =  sin(psi)*cos(phi) + sin(theta)*cos(psi)*sin(phi)
R22 =  cos(psi)*cos(phi) - sin(theta)*sin(psi)*sin(phi)
R23 = -cos(theta)*sin(phi)
R31 =  sin(psi)*sin(phi) - sin(theta)*cos(psi)*cos(phi)
R32 =  cos(psi)sin(phi) + sin(theta)*sin(psi)*cos(phi)
R33 =  cos(theta)*cos(phi) 

第 3 步:通过检查,您可以使用上述元素找到三个角度的 sin 或 tan。在这个例子中,

tan(phi) = -R23/R33

sin(theta) = -R13

tan(psi) = -R12/R11

第 4 步:根据您的四元数计算旋转矩阵(请参阅wikipedia),用于计算上述 3) 中的角度所需的元素。

可以使用相同的过程计算其他约定。

于 2010-01-15T10:47:56.407 回答
3

这是我写的一篇关于将四元数转换为欧拉角的论文。

链接 1

我还在这个位置放置了一些文档,讨论四元数、欧拉角和旋转矩阵 (DCM) 的各个方面。

链接 2

于 2010-03-05T22:32:28.773 回答
3

对于那些在谷歌搜索时偶然发现此页面的人,我最近发现了所有 12 个内在 Tait-Bryan(1-2-3、3-2-1 等)和 Proper Euler(1-2-1、 3-1-3等)以下两个参考中的旋转顺序:

感谢frodo2975提供第二个链接。

于 2017-04-17T04:41:34.847 回答
2

维基百科展示了如何使用四元数的各个部分并计算欧拉角。

于 2009-06-23T07:03:46.083 回答