“Average” of multiple quaternions?

前端 未结 13 1615
执念已碎
执念已碎 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:58

    Since there are different approaches here, I wrote a Matlab script to compare them. These results seem to suggest that simply averaging and normalizing the quaternions (the approach from the unity wiki, called simple_average here) might be enough for cases where the quaternions are sufficiently similar and small deviations are acceptable.

    Here's the output:

    everything okay, max angle offset == 9.5843
    qinit to average: 0.47053 degrees
    qinit to simple_average: 0.47059 degrees
    average to simple_average: 0.00046228 degrees
    loop implementation to matrix implementation: 3.4151e-06 degrees
    

    And here's the code:

    %% Generate random unity quaternion
    rng(42); % set arbitrary seed for random number generator
    M = 100;
    qinit=rand(1,4) - 0.5;
    qinit=qinit/norm(qinit);
    Qinit=repmat(qinit,M,1);
    
    %% apply small perturbation to the quaternions 
    perturb=0.05; % 0.05 => +- 10 degrees of rotation (see angles_deg)
    Q = Qinit + 2*(rand(size(Qinit)) - 0.5)*perturb;
    Q = Q ./ vecnorm(Q, 2, 2); % Normalize perturbed quaternions
    Q_inv = Q * diag([1 -1 -1 -1]); % calculated inverse perturbed rotations
    
    %% Test if everything worked as expected: assert(Q2 * Q2_inv = unity)
    unity = quatmultiply(Q, Q_inv);
    Q_diffs = quatmultiply(Qinit, Q_inv);
    angles = 2*acos(Q_diffs(:,1));
    angles_deg = wrapTo180(rad2deg(angles));
    if sum(sum(abs(unity - repmat([1 0 0 0], M, 1)))) > 0.0001
        disp('error, quaternion inversion failed for some reason');
    else
        disp(['everything okay, max angle offset == ' num2str(max(angles_deg))])
    end
    
    %% Calculate average using matrix implementation of eigenvalues algorithm
    [average,~] = eigs(transpose(Q) * Q, 1);
    average = transpose(average);
    diff = quatmultiply(qinit, average * diag([1 -1 -1 -1]));
    diff_angle = 2*acos(diff(1));
    
    %% Calculate average using algorithm from https://stackoverflow.com/a/29315869/1221661
    average2 = quatWAvgMarkley(Q, ones(M,1));
    diff2 = quatmultiply(average, average2 * diag([1 -1 -1 -1]));
    diff2_angle = 2*acos(diff2(1));
    
    %% Simply add coefficients and normalize the result
    simple_average = sum(Q) / norm(sum(Q));
    simple_diff = quatmultiply(qinit, simple_average * diag([1 -1 -1 -1]));
    simple_diff_angle = 2*acos(simple_diff(1));
    simple_to_complex = quatmultiply(simple_average, average * diag([1 -1 -1 -1]));
    simple_to_complex_angle = 2*acos(simple_to_complex(1));
    
    %% Compare results
    disp(['qinit to average: ' num2str(wrapTo180(rad2deg(diff_angle))) ' degrees']);
    disp(['qinit to simple_average: ' num2str(wrapTo180(rad2deg(simple_diff_angle))) ' degrees']);
    disp(['average to simple_average: ' num2str(wrapTo180(rad2deg(simple_to_complex_angle))) ' degrees']);
    disp(['loop implementation to matrix implementation: ' num2str(wrapTo180(rad2deg(diff2_angle))) ' degrees']);
    
    0 讨论(0)
提交回复
热议问题