【问题标题】:dot product of two quaternion rotations两个四元数旋转的点积
【发布时间】:2014-02-26 03:05:57
【问题描述】:

我知道两个四元数的点(或内)积是旋转之间的角度(包括轴旋转)。这使得点积等于四元数超球面上两点之间的角度。
但是,我无法找到如何实际计算点积。

任何帮助将不胜感激!

当前代码:

public static float dot(Quaternion left, Quaternion right){
    float angle;

    //compute

    return angle;
}

定义为 Quaternion.w、Quaternion.x、Quaternion.y 和 Quaternion.z。

注意:可以假设四元数是标准化的。

【问题讨论】:

    标签: java math rotation quaternions dot-product


    【解决方案1】:

    四元数的点积就是 4D 中的标准欧几里得点积:

    dot = left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w
    

    那么你要找的角度就是点积的arccos(注意点积不是角度):acos(dot)

    但是,如果您正在寻找两个四元数之间的相对旋转,例如从 q1q2,您应该计算相对四元数 q = q1^-1 * q2,然后找到与q 关联的旋转。

    【讨论】:

    • @Luke:我认为您误解了这个答案,它有两个不同的部分。被视为 4D 向量的四元数之间的角度是这个答案的第一部分(acos(dot) 的东西)。将您从一个四元数带到另一个四元数的最小角度旋转是此答案的第二部分(与第一部分不同)。您的答案只是答案第二部分的详细信息(即,获取上面列出的 q 的实际最小旋转角度)。
    • @JamesTursa OP 要求“旋转之间的角度(包括轴旋转)”,以单个标量值的形式 - 而不是将一个四元数映射到另一个四元数的四元数 -所以答案的后半部分实际上并没有回答问题。前半部分的答案仍然不正确,因为acos需要乘以2,并且由于四元数“双覆盖”问题,在调用acos之前必须取点积的绝对值。
    • @Luke:我读这篇文章的前半部分试图回答 OP 关于将四元数视为超球面上的简单 4D 点(OP 自己的话)。因此,IMO 的 acos(dot) 答案对于此特定请求是正确的。这篇文章中提到的 q 东西与此不同(将四元数视为 3D 旋转),并且张贴者指出了这一点。 q 的内容与您的答案完全相同,只是没有详细说明如何实际找到与 q 相关的旋转……您在答案中提供了这些详细信息。所以我看不出有什么矛盾。
    • 但是您不能将四元数直接视为 4d 点,因为由于“双重覆盖问题”,q-q 都表示完全相同的旋转。您必须首先进行归一化,使两个四元数位于双覆盖空间的同一半球,然后您可以将它们视为 4d 向量,然后点积实际上是有意义的。我在下面扩展了我的答案,以描述针对双重封面问题的规范化。
    【解决方案2】:

    请注意:从数值的角度来看,acos(dot) 非常不稳定。

    如前所述,q = q1^-1 * q2 和角度 = 2*atan2(q.vec.length(), q.w)

    【讨论】:

    • 请与我在答案中链接的备忘单中的atan2 公式进行比较——我不明白其中的区别。
    【解决方案3】:

    应该是 2 x acos(dot) 来获得四元数之间的角度。

    【讨论】:

    • 确定对此有信心吗?我认为这是不正确的。请仔细检查。 (参考:3dgep.com/understanding-quaternions
    • 四元数点积的范围是 1.0 到 0.0,而 3d 方向向量点积的范围是 1.0 到 -1.0 .. 所以将 arcos(dot) 加倍似乎是合乎逻辑的
    • @Isometriq 对于单位/旋转四元数,它们的点积范围 \in [-1...1]。我也相信这是错误的。接受的答案应该是正确的。
    • 两个归一化向量的点积给出了向量之间夹角的余弦值,因此acos(dot)(几乎)就足够了,不需要乘以 2。但是,我认为您遇到的是双封面问题:q-q 代表相同的旋转。有关详细信息,请参阅我的答案(以及为什么我说“(几乎)”)。
    【解决方案4】:

    计算两个四元数之间角度的“正确方法”

    实际上没有两个四元数之间的角度这样的东西,只有一个四元数通过乘法将一个四元数带到另一个四元数。但是,您可以通过计算两个四元数之间的差异(例如 qDiff = q1.mul(q2.inverse()),或者您的库可能能够使用类似 qDiff = q1.difference(q2) 的调用直接计算它)来测量该映射变换的总旋转角度,然后测量四元数轴的角度(你的四元数库可能有一个例程,例如ang = qDiff.angle())。

    请注意,您可能需要固定该值,因为测量围绕轴的角度不一定会给出“短途”旋转,例如:

    if (ang > Math.PI) {
        ang -= 2.0 * Math.PI;
    } else if (ang < -Math.PI) {
        ang += 2.0 * Math.PI;
    }
    

    使用点积测量两个四元数的相似度

    更新: See this answer instead.

    我假设在最初的问题中,将四元数视为 4d 向量的目的是启用一种简单的方法来测量两个四元数的相似性,同时仍然牢记四元数表示旋转。 (从一个四元数到另一个四元数的实际旋转映射本身就是一个四元数,而不是一个标量。)

    有几个答案建议使用点积的acos。 (首先要注意:四元数必须是单位四元数才能起作用。)但是,其他答案没有考虑“双重封面问题”:q-q 都代表完全相同的旋转。

    acos(q1 . q2)acos(q1 . (-q2)) 都应该返回相同的值,因为 q2-q2 代表相同的旋转。但是(x == 0 除外),acos(x)acos(-x) 不会返回相同的值。因此,平均而言(给定随机四元数),acos(q1 . q2) 在一半的时间里不会给你你所期望的,这意味着它不会给你一个度量 q1q2 之间的角度,假设你关心q1q2 代表旋转。因此,即使您只打算使用点积或点积的acos 作为相似度度量,来测试q1q2 在旋转效果方面的相似度,答案你get 会有一半的时间是错误的。

    更具体地说,如果您试图简单地将四元数视为 4d 向量,并计算 ang = acos(q1 . q2),您有时会得到您期望的 ang 的值,而其余时间则是您实际想要的值(考虑到双封面问题)将是PI - acos(-q1 . q2)。您获得的这两个值中的哪一个将在这些值之间随机波动,具体取决于 q1q2 的计算方式!

    要解决这个问题,您必须对四元数进行归一化,使它们位于双覆盖空间的同一个“半球”中。有几种方法可以做到这一点,老实说,我什至不确定其中哪一种是“正确”或最佳方式。在某些情况下,它们确实会产生与其他方法不同的结果。任何关于上述三种归一化形式中哪一种是正确或最佳的反馈都将不胜感激。

    import java.util.Random;
    import org.joml.Quaterniond;
    import org.joml.Vector3d;
    
    public class TestQuatNorm {
        private static Random random = new Random(1);
    
        private static Quaterniond randomQuaternion() {
            return new Quaterniond(
                    random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1,
                    random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1)
                    .normalize();
        }
    
        public static double normalizedDot0(Quaterniond q1, Quaterniond q2) {
            return Math.abs(q1.dot(q2));
        }
    
        public static double normalizedDot1(Quaterniond q1, Quaterniond q2) {
            return
                (q1.w >= 0.0 ? q1 : new Quaterniond(-q1.x, -q1.y, -q1.z, -q1.w))
                .dot(
                   q2.w >= 0.0 ? q2 : new Quaterniond(-q2.x, -q2.y, -q2.z, -q2.w));
        }
    
        public static double normalizedDot2(Quaterniond q1, Quaterniond q2) {
            Vector3d v1 = new Vector3d(q1.x, q1.y, q1.z);
            Vector3d v2 = new Vector3d(q2.x, q2.y, q2.z);
            double dot = v1.dot(v2);
            Quaterniond q2n = dot >= 0.0 ? q2 
                    : new Quaterniond(-q2.x, -q2.y, -q2.z, -q2.w);
            return q1.dot(q2n);
        }
    
        public static double acos(double val) {
            return Math.toDegrees(Math.acos(Math.max(-1.0, Math.min(1.0, val))));
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 1000; i++) {
                var q1 = randomQuaternion();
                var q2 = randomQuaternion();
    
                double dot = q1.dot(q2);
                double dot0 = normalizedDot0(q1, q2);
                double dot1 = normalizedDot1(q1, q2);
                double dot2 = normalizedDot2(q1, q2);
    
                System.out.println(acos(dot) + "\t" + acos(dot0) + "\t" + acos(dot1)
                    + "\t" + acos(dot2));
            }
        }
    }
    

    还要注意:

    1. 众所周知,acos 在数值上不太准确(考虑到一些最坏情况的输入,最多有一半的最低有效数字可能是错误的);
    2. acos 的实现在 JDK 标准库中异常缓慢;
    3. acos 返回 NaN 如果其参数稍微超出 [-1,1],这对于偶数单位四元数的点积很常见——因此您需要在之前将点积的值绑定到该范围打电话给acos。请参阅上面代码中的这一行:
            return Math.toDegrees(Math.acos(Math.max(-1.0, Math.min(1.0, val))));
    

    根据this cheatsheet 等式。 (42),有一种更稳健、更准确的方法来计算两个向量之间的角度,用atan2 替换acos(尽管请注意,这也不能解决双覆盖问题,因此您需要使用其中之一应用以下规范之前的上述规范化形式):

    ang(q1, q2) = 2 * atan2(|q1 - q2|, |q1 + q2|)
    

    我承认我不明白这个公式,因为四元数减法和加法没有几何意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-05
      • 1970-01-01
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-14
      相关资源
      最近更新 更多