如何将欧拉角转换为方向向量?

68

我有俯仰角、横滚角和偏航角。如何将它们转换为方向向量?

如果您能向我展示四元数和/或矩阵表示,那将非常酷!

5个回答

110
很不幸,关于如何定义这些东西有不同的约定(而且滚动、俯仰和偏航并不完全等同于欧拉角),所以你必须小心谨慎。
如果我们将俯仰定义为水平(z = 0),偏航定义为从x轴逆时针旋转,则方向向量将是
x = cos(yaw)*cos(pitch) y = sin(yaw)*cos(pitch) z = sin(pitch)
请注意,我没有使用滚动;这是一个方向单位向量,它不指定态度。很容易编写一个旋转矩阵,将事物带入飞行器的参考系中(如果您想知道左机翼尖在哪里),但最好先规定约定。您能告诉我们更多关于问题的信息吗?
编辑: (我一直想回答这个问题两年半了。)
对于完整的旋转矩阵,如果我们使用上面的约定,并且要先偏航,然后俯仰,最后滚动,为了得到世界坐标系中的最终坐标,我们必须按相反的顺序应用旋转矩阵。
首先是滚动:
| 1    0          0      |
| 0 cos(roll) -sin(roll) |
| 0 sin(roll)  cos(roll) |

然后是推销:

| cos(pitch) 0 -sin(pitch) |
|     0      1      0      |
| sin(pitch) 0  cos(pitch) |

然后偏航:

| cos(yaw) -sin(yaw) 0 |
| sin(yaw)  cos(yaw) 0 |
|    0         0     1 |

把它们组合起来,总旋转矩阵就是:

| cos(yaw)cos(pitch) -cos(yaw)sin(pitch)sin(roll)-sin(yaw)cos(roll) -cos(yaw)sin(pitch)cos(roll)+sin(yaw)sin(roll)|
| sin(yaw)cos(pitch) -sin(yaw)sin(pitch)sin(roll)+cos(yaw)cos(roll) -sin(yaw)sin(pitch)cos(roll)-cos(yaw)sin(roll)|
| sin(pitch)          cos(pitch)sin(roll)                            cos(pitch)sin(roll)|

如果一个单位向量起始于 x 轴,最终坐标将为:

x = cos(yaw)cos(pitch)
y = sin(yaw)cos(pitch)
z = sin(pitch)

对于起始于y轴(左机翼尖)的单位向量,最终坐标将为:

x = -cos(yaw)sin(pitch)sin(roll)-sin(yaw)cos(roll)
y = -sin(yaw)sin(pitch)sin(roll)+cos(yaw)cos(roll)
z =  cos(pitch)sin(roll)

3
就翻译而言,关于欧拉解(从-π到π归一化)有六种不同的公式,具体取决于旋转顺序(考虑到角度在2*π处绕行的无限变体)。根据您的使用情况,您可能需要使用其中一种备用公式才能获得所需的结果。 - Adisak
10
如果我们需要滚动,方程式会是什么样子? - ApproachingDarknessFish
2
@ApproachingDarknessFish,方程式完全相同,因为滚动会使向量围绕自身旋转,对其没有影响。 - sam hocevar
@Beta,我在哪里可以找到这个公式的数学推导?维基百科太广泛了,我需要在推导方面得到帮助。 - wangdq
1
@Beta,是否有可能将XYZ转换回Pitch Yaw Roll? - user4200570
显示剩余8条评论

30

有六种不同的方式可以将三个欧拉角转换为矩阵,具体取决于它们被应用的顺序:

typedef float Matrix[3][3];
struct EulerAngle { float X,Y,Z; };

// Euler Order enum.
enum EEulerOrder
{
    ORDER_XYZ,
    ORDER_YZX,
    ORDER_ZXY,
    ORDER_ZYX,
    ORDER_YXZ,
    ORDER_XZY
};


Matrix EulerAnglesToMatrix(const EulerAngle &inEulerAngle,EEulerOrder EulerOrder)
{
    // Convert Euler Angles passed in a vector of Radians
    // into a rotation matrix.  The individual Euler Angles are
    // processed in the order requested.
    Matrix Mx;

    const FLOAT    Sx    = sinf(inEulerAngle.X);
    const FLOAT    Sy    = sinf(inEulerAngle.Y);
    const FLOAT    Sz    = sinf(inEulerAngle.Z);
    const FLOAT    Cx    = cosf(inEulerAngle.X);
    const FLOAT    Cy    = cosf(inEulerAngle.Y);
    const FLOAT    Cz    = cosf(inEulerAngle.Z);

    switch(EulerOrder)
    {
    case ORDER_XYZ:
        Mx.M[0][0]=Cy*Cz;
        Mx.M[0][1]=-Cy*Sz;
        Mx.M[0][2]=Sy;
        Mx.M[1][0]=Cz*Sx*Sy+Cx*Sz;
        Mx.M[1][1]=Cx*Cz-Sx*Sy*Sz;
        Mx.M[1][2]=-Cy*Sx;
        Mx.M[2][0]=-Cx*Cz*Sy+Sx*Sz;
        Mx.M[2][1]=Cz*Sx+Cx*Sy*Sz;
        Mx.M[2][2]=Cx*Cy;
        break;

    case ORDER_YZX:
        Mx.M[0][0]=Cy*Cz;
        Mx.M[0][1]=Sx*Sy-Cx*Cy*Sz;
        Mx.M[0][2]=Cx*Sy+Cy*Sx*Sz;
        Mx.M[1][0]=Sz;
        Mx.M[1][1]=Cx*Cz;
        Mx.M[1][2]=-Cz*Sx;
        Mx.M[2][0]=-Cz*Sy;
        Mx.M[2][1]=Cy*Sx+Cx*Sy*Sz;
        Mx.M[2][2]=Cx*Cy-Sx*Sy*Sz;
        break;

    case ORDER_ZXY:
        Mx.M[0][0]=Cy*Cz-Sx*Sy*Sz;
        Mx.M[0][1]=-Cx*Sz;
        Mx.M[0][2]=Cz*Sy+Cy*Sx*Sz;
        Mx.M[1][0]=Cz*Sx*Sy+Cy*Sz;
        Mx.M[1][1]=Cx*Cz;
        Mx.M[1][2]=-Cy*Cz*Sx+Sy*Sz;
        Mx.M[2][0]=-Cx*Sy;
        Mx.M[2][1]=Sx;
        Mx.M[2][2]=Cx*Cy;
        break;

    case ORDER_ZYX:
        Mx.M[0][0]=Cy*Cz;
        Mx.M[0][1]=Cz*Sx*Sy-Cx*Sz;
        Mx.M[0][2]=Cx*Cz*Sy+Sx*Sz;
        Mx.M[1][0]=Cy*Sz;
        Mx.M[1][1]=Cx*Cz+Sx*Sy*Sz;
        Mx.M[1][2]=-Cz*Sx+Cx*Sy*Sz;
        Mx.M[2][0]=-Sy;
        Mx.M[2][1]=Cy*Sx;
        Mx.M[2][2]=Cx*Cy;
        break;

    case ORDER_YXZ:
        Mx.M[0][0]=Cy*Cz+Sx*Sy*Sz;
        Mx.M[0][1]=Cz*Sx*Sy-Cy*Sz;
        Mx.M[0][2]=Cx*Sy;
        Mx.M[1][0]=Cx*Sz;
        Mx.M[1][1]=Cx*Cz;
        Mx.M[1][2]=-Sx;
        Mx.M[2][0]=-Cz*Sy+Cy*Sx*Sz;
        Mx.M[2][1]=Cy*Cz*Sx+Sy*Sz;
        Mx.M[2][2]=Cx*Cy;
        break;

    case ORDER_XZY:
        Mx.M[0][0]=Cy*Cz;
        Mx.M[0][1]=-Sz;
        Mx.M[0][2]=Cz*Sy;
        Mx.M[1][0]=Sx*Sy+Cx*Cy*Sz;
        Mx.M[1][1]=Cx*Cz;
        Mx.M[1][2]=-Cy*Sx+Cx*Sy*Sz;
        Mx.M[2][0]=-Cx*Sy+Cy*Sx*Sz;
        Mx.M[2][1]=Cz*Sx;
        Mx.M[2][2]=Cx*Cy+Sx*Sy*Sz;
        break;
    }
    return(Mx);
}

顺便提一句,某些CPU可以同时计算正弦和余弦(例如x86上的fsincos)。如果这样做,您可以通过三次调用而不是6次来计算初始正弦和余弦值,从而使它稍微快一点。

更新:实际上有12种方式,具体取决于您想要右手系还是左手系的结果——您可以通过对角度取反来改变“手性”。


10
Beta拯救了我。然而,我使用的参考坐标系略有不同,我的俯仰定义为上下(向上点头表示赞同),其中的俯仰会导致的y分量。我的参考向量是OpenGl样式(沿着负z轴向下),因此在偏航=0,俯仰=0时,结果单位向量应等于(0, 0, -1)。 如果有人遇到此帖子并难以将Beta的公式转化为此特定系统,请使用以下方程式:
vDir->X = sin(yaw);
vDir->Y = -(sin(pitch)*cos(yaw));
vDir->Z = -(cos(pitch)*cos(yaw));

请注意符号变化和偏航角 <-> 俯仰角的交换。希望这能节省某人的时间。

2
你需要明确你的定义 - 特别是你想要什么向量?如果它是飞机指向的方向,那么滚动甚至不会影响它,你只是使用球面坐标(可能轴/角度交换)。
另一方面,如果你想通过这些角度来转换给定的向量,你需要一个旋转矩阵。维基百科文章关于旋转矩阵包含了一个基于xyz旋转矩阵的偏航-俯仰-翻滚旋转的公式。考虑到涉及到希腊字母和矩阵,我不会在这里尝试输入它。

-1

如果有人在寻找FreeCAD的实现方案时遇到困难。

import FreeCAD, FreeCADGui
from FreeCAD import Vector
from math import sin, cos, pi

cr = FreeCADGui.ActiveDocument.ActiveView.getCameraOrientation().toEuler()
crx = cr[2] # Roll
cry = cr[1] # Pitch
crz = cr[0] # Yaw

crx = crx * pi / 180.0
cry = cry * pi / 180.0
crz = crz * pi / 180.0

x = sin(crz)
y = -(sin(crx) * cos(crz))
z = cos(crx) * cos(cry)

view = Vector(x, y, z)

2
问题是C/C++,不是Python。 - Khoi V

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