“Average” of multiple quaternions?

前端 未结 13 1616
执念已碎
执念已碎 2020-12-12 17:31

I\'m trying to make the switch from matrices to quaternions for skeletal animation in my OpenGL program, but I\'ve encountered a problem:

Given a number of unit quat

相关标签:
13条回答
  • 2020-12-12 17:51

    Unfortunately it isn't terribly simple to do, but it is possible. Here's a whitepaper explaining the math behind it: http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf

    Check out the Unity3D Wiki page (The below code sample is from the same article): http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors

    //Get an average (mean) from more then two quaternions (with two, slerp would be used).
    //Note: this only works if all the quaternions are relatively close together.
    //Usage: 
    //-Cumulative is an external Vector4 which holds all the added x y z and w components.
    //-newRotation is the next rotation to be added to the average pool
    //-firstRotation is the first quaternion of the array to be averaged
    //-addAmount holds the total amount of quaternions which are currently added
    //This function returns the current average quaternion
    public static Quaternion AverageQuaternion(ref Vector4 cumulative, Quaternion newRotation, Quaternion firstRotation, int addAmount){
    
        float w = 0.0f;
        float x = 0.0f;
        float y = 0.0f;
        float z = 0.0f;
    
        //Before we add the new rotation to the average (mean), we have to check whether the quaternion has to be inverted. Because
        //q and -q are the same rotation, but cannot be averaged, we have to make sure they are all the same.
        if(!Math3d.AreQuaternionsClose(newRotation, firstRotation)){
    
            newRotation = Math3d.InverseSignQuaternion(newRotation);    
        }
    
        //Average the values
        float addDet = 1f/(float)addAmount;
        cumulative.w += newRotation.w;
        w = cumulative.w * addDet;
        cumulative.x += newRotation.x;
        x = cumulative.x * addDet;
        cumulative.y += newRotation.y;
        y = cumulative.y * addDet;
        cumulative.z += newRotation.z;
        z = cumulative.z * addDet;      
    
        //note: if speed is an issue, you can skip the normalization step
        return NormalizeQuaternion(x, y, z, w);
    }
    
    public static Quaternion NormalizeQuaternion(float x, float y, float z, float w){
    
        float lengthD = 1.0f / (w*w + x*x + y*y + z*z);
        w *= lengthD;
        x *= lengthD;
        y *= lengthD;
        z *= lengthD;
    
        return new Quaternion(x, y, z, w);
    }
    
    //Changes the sign of the quaternion components. This is not the same as the inverse.
    public static Quaternion InverseSignQuaternion(Quaternion q){
    
        return new Quaternion(-q.x, -q.y, -q.z, -q.w);
    }
    
    //Returns true if the two input quaternions are close to each other. This can
    //be used to check whether or not one of two quaternions which are supposed to
    //be very similar but has its component signs reversed (q has the same rotation as
    //-q)
    public static bool AreQuaternionsClose(Quaternion q1, Quaternion q2){
    
        float dot = Quaternion.Dot(q1, q2);
    
        if(dot < 0.0f){
    
            return false;                   
        }
    
        else{
    
            return true;
        }
    }
    

    Also this post: http://forum.unity3d.com/threads/86898-Average-quaternions

    0 讨论(0)
  • 2020-12-12 17:51

    Check here for my solution to weighted averaging as well as Lp median of quaternions.

    0 讨论(0)
  • 2020-12-12 17:52

    Here is a GitHub Repo with the implementation of this suggested algorithmn :) https://github.com/christophhagen/averaging-quaternions

    Thanks and credits to christophhagen ofc ;)

    0 讨论(0)
  • 2020-12-12 17:54

    The easiest implementation (with Numpy in Python>=3.6) of Markley's solution would be:

    import numpy as np
    def q_average(Q, W=None):
        if W is not None:
            Q *= W[:, None]
        eigvals, eigvecs = np.linalg.eig(Q.T@Q)
        return eigvecs[:, eigvals.argmax()]
    

    where Q is of size N-by-4. The resulting quaternion is already normalized.

    In this case the weights are equal to 1 by default. Otherwise you can give a list of weights of size N (one per quaternion.)

    That's it.

    0 讨论(0)
  • 2020-12-12 17:56

    This is my implementation in python of Tolga Birdal's algorithm:

    import numpy as np
    
    def quatWAvgMarkley(Q, weights):
        '''
        Averaging Quaternions.
    
        Arguments:
            Q(ndarray): an Mx4 ndarray of quaternions.
            weights(list): an M elements list, a weight for each quaternion.
        '''
    
        # Form the symmetric accumulator matrix
        A = np.zeros((4, 4))
        M = Q.shape[0]
        wSum = 0
    
        for i in range(M):
            q = Q[i, :]
            w_i = weights[i]
            A += w_i * (np.outer(q, q)) # rank 1 update
            wSum += w_i
    
        # scale
        A /= wSum
    
        # Get the eigenvector corresponding to largest eigen value
        return np.linalg.eigh(A)[1][:, -1]
    
    0 讨论(0)
  • 2020-12-12 17:58

    I tried Slerping the quaternions as suggested here but that didn't work for what I'm trying to do (model was distorted), so I simply ended up transforming the vectors by each quaternion and then doing an average (until I can find a better solution).

    0 讨论(0)
提交回复
热议问题