四元数 - 在偏航俯仰翻滚和欧拉角之间转换只在俯仰角为Pi时产生错误的结果。

4

我花了一些时间实现了一些算法,用于在欧拉角和四元数之间进行转换。

我正在使用以下代码测试四元数的值是否相同:

        Quaternion orientation0 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(0, 0, 0);
        Vector3 rotation = orientation0.ToEulerAngles();
        Quaternion orientation1 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);

        Console.WriteLine(orientation0);
        Console.WriteLine(orientation1);

我曾经使用过一个之前讨论过的方法,在这里,后来我又实现了另一种方法,在这里

    public static Quaternion QuaternionFromYawPitchRoll(float yaw, float pitch, float roll)
    {
        float rollOver2 = roll * 0.5f;
        float sinRollOver2 = (float)Math.Sin((double)rollOver2);
        float cosRollOver2 = (float)Math.Cos((double)rollOver2);
        float pitchOver2 = pitch * 0.5f;
        float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
        float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
        float yawOver2 = yaw * 0.5f;
        float sinYawOver2 = (float)Math.Sin((double)yawOver2);
        float cosYawOver2 = (float)Math.Cos((double)yawOver2);

        // X = PI is giving incorrect result (pitch)

        // Heading = Yaw
        // Attitude = Pitch
        // Bank = Roll

        Quaternion result;
        //result.X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
        //result.Y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
        //result.Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
        //result.W = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;

        result.W = cosYawOver2 * cosPitchOver2 * cosRollOver2 - sinYawOver2 * sinPitchOver2 * sinRollOver2;
        result.X = sinYawOver2 * sinPitchOver2 * cosRollOver2 + cosYawOver2 * cosPitchOver2 * sinRollOver2;
        result.Y = sinYawOver2 * cosPitchOver2 * cosRollOver2 + cosYawOver2 * sinPitchOver2 * sinRollOver2;
        result.Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 - sinYawOver2 * cosPitchOver2 * sinRollOver2;

        return result;
    }

    public static Vector3 ToEulerAngles(this Quaternion q)
    {
        // Store the Euler angles in radians
        Vector3 pitchYawRoll = new Vector3();

        double sqx = q.X * q.X;
        double sqy = q.Y * q.Y;
        double sqz = q.Z * q.Z;
        double sqw = q.W * q.W;

        // If quaternion is normalised the unit is one, otherwise it is the correction factor
        double unit = sqx + sqy + sqz + sqw;

        double test = q.X * q.Y + q.Z * q.W;
        //double test = q.X * q.Z - q.W * q.Y;

        if (test > 0.4999f * unit)                              // 0.4999f OR 0.5f - EPSILON
        {
            // Singularity at north pole
            pitchYawRoll.Y = 2f * (float)Math.Atan2(q.X, q.W);  // Yaw
            pitchYawRoll.X = PIOVER2;                           // Pitch
            pitchYawRoll.Z = 0f;                                // Roll
            return pitchYawRoll;
        }
        else if (test < -0.4999f * unit)                        // -0.4999f OR -0.5f + EPSILON
        {
            // Singularity at south pole
            pitchYawRoll.Y = -2f * (float)Math.Atan2(q.X, q.W); // Yaw
            pitchYawRoll.X = -PIOVER2;                          // Pitch
            pitchYawRoll.Z = 0f;                                // Roll
            return pitchYawRoll;
        }
        else
        {
            pitchYawRoll.Y = (float)Math.Atan2(2f * q.Y * q.W - 2f * q.X * q.Z, sqx - sqy - sqz + sqw);       // Yaw
            pitchYawRoll.X = (float)Math.Asin(2f * test / unit);                                              // Pitch
            pitchYawRoll.Z = (float)Math.Atan2(2f * q.X * q.W - 2f * q.Y * q.Z, -sqx + sqy - sqz + sqw);      // Roll

            //pitchYawRoll.Y = (float)Math.Atan2(2f * q.X * q.W + 2f * q.Y * q.Z, 1 - 2f * (sqz + sqw));      // Yaw 
            //pitchYawRoll.X = (float)Math.Asin(2f * (q.X * q.Z - q.W * q.Y));                                // Pitch 
            //pitchYawRoll.Z = (float)Math.Atan2(2f * q.X * q.Y + 2f * q.Z * q.W, 1 - 2f * (sqy + sqz));      // Roll 
        }

        return pitchYawRoll;
    }

除了pitch值为±PI时,所有我的实现都可以工作。

    Quaternion orientation0 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(0, PI, 0);
    Vector3 rotation = orientation0.ToEulerAngles();
    Quaternion orientation1 = Prototype1.Mathematics.ToolBox.QuaternionFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);

    Console.WriteLine(orientation0);
    Console.WriteLine(orientation1);     // Not the same quaternion values

为什么这个方法对于特定的值不起作用?如果它是奇点,那么算法并没有识别出它,而'test'的值会非常接近0。

告诉我们你在这些行中得到了什么值。第二组欧拉角是什么?它碰巧是(0,-PI,0){相同的旋转}吗?请记住,四元数是冗余表示:完全否定的四元数表示相同的旋转。 - JCooper
方向0 - {X:0 Y:0 Z:1 W:3.139165E-07} - user1423893
orientation1 - {X:-4.37114E-08 Y:-4.37114E-08 Z:-1 W:-3.139165E-07} 方向1 - {X:-4.37114E-08 Y:-4.37114E-08 Z:-1 W:-3.139165E-07} - user1423893
1个回答

3
旋转空间会围绕着自身旋转。显然,如果你绕任意一个轴旋转2π,你最终会回到起点。同样,如果你绕一个轴旋转π,这与绕同一轴旋转-π是相同的。或者,如果你绕一个轴旋转任意角度,这就相当于绕该轴的相反方向旋转该角度的负值。
所有这些都意味着你的四元数转换算法必须决定如何处理冗余。你在注释中提供的两个方向是相同的方向:(0,0,0,1)和(0,0,0,-1) [我更喜欢按字母顺序排列'w']。
你应该确保始终对四元数进行归一化,否则你最终会得到一些奇怪的漂移。除此之外,似乎发生的是,当你绕z轴旋转π时,浮点舍入误差或“小于”与“小于等于”的差异将表示沿圆圈推动到算法决定将角度表示为绕z轴旋转-π。那是相同的事情。
类似地,如果你绕任意轴旋转2π,你的四元数可能是(-1,0,0,0)。但如果你旋转0度,它将是(1,0,0,0)。然而,从这些四元数返回的欧拉角表示应该是(0,0,0)。

感谢您提供深入的答案。除了最初的标准化之外,为了正确处理浮点数问题,您会对代码进行哪些更改? - user1423893
@user1423893 浮点数问题的重要解决方法是规范化。关于冗余,没有太多可以做的。您是否需要每个方向都有唯一的表示? - JCooper
如果可能的话,我想让两个四元数具有相同的值(orientation0 == orientation1)。规范化并不能解决结果四元数值不同的问题。 - user1423893

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