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.5843qinit to average: 0.47053 degreesqinit to simple_average: 0.47059 degreesaverage to simple_average: 0.00046228 degreesloop implementation to matrix implementation: 3.4151e-06 degrees
And here's the code:
%% Generate random unity quaternionrng(42); % set arbitrary seed for random number generatorM = 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 quaternionsQ_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/1221661average2 = 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 resultsimple_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 resultsdisp(['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']);