将欧拉角转换为矩阵,以及将矩阵转换为欧拉角

10
我试图使用.NET/C#将欧拉角描述的3D旋转转换为矩阵,然后再转回去。我的约定是:
  • 左手系(x向右,y向上,z向前)
  • 旋转顺序:绕y轴的偏航,绕x轴的俯仰,绕z轴的转弯
  • 使用左手规则进行正旋转(拇指指向+无穷大)

我的尝试是:

欧拉角到矩阵(我已经删除了x、y、z平移部分以简化问题)

Matrix3D matrix = new Matrix3D() {
    M11 =   cosH * cosB - sinH * sinP * sinB,
    M12 = - sinB * cosP,
    M13 =   sinH * cosB + cosH * sinP * sinB,
    M21 =   cosH * sinB + sinH * sinP * cosB,
    M22 =   cosB * cosP,
    M23 =   sinB * sinH - cosH * sinP * cosB,
    M31 = - sinH * cosP,
    M32 = - sinP,
    M33 =   cosH * cosP,
};

矩阵转欧拉角

const double RD_TO_DEG = 180 / Math.PI;            
double h, p, b; // angles in degrees

// extract pitch
double sinP = -matrix.M23;            
if (sinP >= 1) {
    p = 90; }       // pole
else if (sinP <= -1) {
    p = -90; } // pole
else {
    p = Math.Asin(sinP) * RD_TO_DEG; }             

// extract heading and bank
if (sinP < -0.9999 || sinP > 0.9999) { // account for small angle errors
    h = Math.Atan2(-matrix.M31, matrix.M11) * RD_TO_DEG;
    b = 0; }
else {
    h = Math.Atan2(matrix.M13, matrix.M33) * RD_TO_DEG;
    b = Math.Atan2(matrix.M21, matrix.M22) * RD_TO_DEG; }

这一定有问题。如果我取3个角度,将它们转换成矩阵,再将该矩阵转换回角度,结果与最初的值不同。

我浏览了几个网站,使用不同的公式,从euclideanspace.com开始,但现在我完全迷失了,找不到正确的计算方法。希望能得到一些帮助。有数学家在吗?


大多数文献都会用右手坐标系和旋转角度来表达这些操作。你最好将数据翻转一些符号以将其放入右手坐标系中,进行矩阵运算,然后再转换回左手坐标系。这可能会增加一些额外的操作,但会更容易理解! - Jim Lewis
这是一个带有连续“关节”的系统,可以旋转以移动相机。实际上,我必须在左手坐标系中维护HPB值,因为用户必须以这种方式输入和查看它们。最后,我将它们转换为右手坐标系,使用不同的起点进行绘制,以便能够使用右手坐标系的WPF 3D。 - Mike
3个回答

12

首先,应该:

sinP = -matrix.M32

编辑:完整的解决方案如下

我的推导过程:

Rx(P)=| 1      0       0 |
      | 0  cos P  -sin P |
      | 0  sin P   cos P |

Ry(H)=|  cos H  0  sin H |
      |      0  1      0 |
      | -sin H  0  cos H |

Rz(B)=| cos B  -sin B  0 |
      | sin B   cos B  0 |
      |     0       0  1 |

以您的订单为基础计算价格:

R = Ry(H)*Rx(P)*Rz(B)
  = | cos H*cos B+sin H*sin P*sin B  cos B*sin H*sin P-sin B*cos H  cos P*sin H |
    |                   cos P*sin B                    cos B*cos P       -sin P |
    | sin B*cos H*sin P-sin H*cos B  sin H*sin B+cos B*cos H*sin P  cos P*cos H |

这将给出反向导数:

tan B = M12/M22

sin P = -M32

tan H = M31/M33


我推导出了原始的旋转矩阵,但实际上我得到了不同的答案,我认为我的数学可能是错误的(或者只是等价的): | cosHcosB+sinHsinPsinB | cosBsinHsinP-sinBcosH | cosPsinH | | cosPsinB | cosBcosP | -sinP | | sinBcosHsinP-sinHcosB | sinHsinB+cosBcosHsinP | cosPcosH | - Mike Tunnicliffe
从我上面的评论中给出的矩阵,反演如下: tan B = M12/M22,sin P = -M32,tan H = M31/M33 - Mike Tunnicliffe
抱歉再次打扰…您能帮我从atan(M12/M22)转换到atan2(?, ?)吗?这是一个关于获得规范形式的一般性问题,但是我从未使用过。atan2维基百科文章对我来说不容易理解。 - Mike
当我说你的解决方案可行时,我使用了你的矩阵,但仍然使用了我未修改的矩阵->欧拉例程。我改用了你的公式进行转换,但它停止工作了。我发现你的矩阵是我错误的矩阵的转置。然而,似乎你的公式是错误的(请原谅!),而我使用的公式是正确的。M13/M33 = tan H,M21/M22 = tan B,-M23 = sin P(使用你的矩阵)。另外,在我的解决方案中查看极点的代码很奇怪。当B==0时,-M31/M11 = 1 / cos H,而不是我预期的tan H(不确定)。 - Mike
简而言之,上述发布的解决方案是正确的,但与DirectX的矩阵相比,它是转置的(DirectX遵循行主序矩阵约定)。 - Rodrigo Lopez
显示剩余6条评论

8
有大量的这些函数组合方式,因为答案取决于您的约定。我通常使用DirectX和与Unity相同的约定。此外,我的背景是飞行模拟器、太空和地图,所以偏航、俯仰、翻滚匹配经纬度样式。

对约定不清楚或具有不匹配的合成/分解函数可能会导致非常奇怪的错误。还值得记住的是,多组欧拉角可以产生相同的方向。

约定(如上所述):

  • 欧拉角:X =俯仰角,Y =偏航角,Z =翻滚角
  • 欧拉顺序:应用旋转,先偏航再俯仰再翻滚
  • 轴:+X右,+Y上,+Z前进
  • 矩阵:DirectX约定(使用MS的SimpleMath.h DirectXTK

要转换为OpenGL版本,请查看this

我已经采用了Mike Tunnicliffe的答案,并将其转换为C++代码并添加到我的库中。我希望其他人使用它可以省下一些时间。

值得注意的是,compose函数会清除第四列和翻译组件到单位矩阵,并且decompose函数假设3x3旋转元素只包含纯旋转(即没有缩放等)。

首先,生成欧拉角矩阵的代码:

//====================================================================================================
// MatrixFromYawPitchRoll
//
// Create matrix based on provided yaw (heading), pitch and roll (bank).
//
// Assumptions:
//  Euler:   X = Pitch, Y = Yaw, Z = Roll
//  Applied: Yaw then pitch then roll
//  Axes:    X = Right, Y = Up, Z = Forward
//  DirectX: Matrices are row major (http://www.mindcontrol.org/~hplus/graphics/matrix-layout.html)
//
// Code is based on Mike Tunnicliffe's answer to this question:
//   https://dev59.com/JHI-5IYBdhLWcg3wJE0K
inline void MatrixFromYawPitchRoll(
    const DirectX::SimpleMath::Vector3& euler,
    DirectX::SimpleMath::Matrix&        mat)
{
    float cosY = cosf(euler.y);     // Yaw
    float sinY = sinf(euler.y);

    float cosP = cosf(euler.x);     // Pitch
    float sinP = sinf(euler.x);

    float cosR = cosf(euler.z);     // Roll
    float sinR = sinf(euler.z);

    mat = DirectX::SimpleMath::Matrix::Identity;
    mat._11 = cosY * cosR + sinY * sinP * sinR;
    mat._21 = cosR * sinY * sinP - sinR * cosY;
    mat._31 = cosP * sinY;

    mat._12 = cosP * sinR;
    mat._22 = cosR * cosP;
    mat._32 = -sinP;

    mat._13 = sinR * cosY * sinP - sinY * cosR;
    mat._23 = sinY * sinR + cosR * cosY * sinP;
    mat._33 = cosP * cosY;
}

然后编写代码从矩阵中获取欧拉角:

//====================================================================================================
// MatrixDecomposeYawPitchRoll
//
// Extract the rotation contained in the provided matrix as yaw (heading), pitch and roll (bank) in
// radiuans.
//
// Assumptions:
//  Euler:   X = Pitch, Y = Yaw, Z = Roll
//  Applied: Yaw then pitch then roll
//  Axes:    X = Right, Y = Up, Z = Forward
//  DirectX: Matrices are row major (http://www.mindcontrol.org/~hplus/graphics/matrix-layout.html)
//
// Code is based on Mike Tunnicliffe's answer to this question:
//   https://dev59.com/JHI-5IYBdhLWcg3wJE0K
inline void MatrixDecomposeYawPitchRoll(
    const DirectX::SimpleMath::Matrix&  mat,
    DirectX::SimpleMath::Vector3&       euler)
{
    euler.x = asinf(-mat._32);                  // Pitch
    if (cosf(euler.x) > 0.0001)                 // Not at poles
    {
        euler.y = atan2f(mat._31, mat._33);     // Yaw
        euler.z = atan2f(mat._12, mat._22);     // Roll
    }
    else
    {
        euler.y = 0.0f;                         // Yaw
        euler.z = atan2f(-mat._21, mat._11);    // Roll
    }
}

3
你的想法是错误的:“这一定是错的。如果我把3个角度转换成矩阵,然后再把矩阵转换回角度,结果就会与最初的值不同。”这看起来很美好,但并不一定正确。通常情况下,多个欧拉角三元组(固定约定)可以导致在空间中相同的方向。但这并不意味着你的计算中没有错误。 以下是维基百科的内容:
例如,假设我们使用上面的zyz约定,则有以下等效对应: (90°,45°,-105°) ≡ (-270°,-315°,255°) 多个360°的倍数 (72°,0°,0°) ≡ (40°,0°,32°) 奇异对齐 (45°,60°,-30°) ≡ (-135°,-60°,150°) 双稳翻转

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接