web-dev-qa-db-fra.com

"Moyenne" de plusieurs quaternions?

J'essaie de passer des matrices aux quaternions pour une animation squelettique dans mon programme OpenGL, mais j'ai rencontré un problème:

Étant donné un nombre d'unités quaternions, il me faut un quaternion qui, lorsqu'il est utilisé pour transformer un vecteur, donnera un vecteur représentant la moyenne du vecteur transformé par chaque quaternion individuellement. (avec des matrices je additionnerais simplement les matrices ensemble et diviserais par le nombre de matrices)

38
jonathan

J’ai essayé de Slerping les quaternions comme suggéré ici mais cela n’a pas fonctionné pour ce que j’essaie de faire (le modèle était déformé); Je peux trouver une meilleure solution).

3
jonathan

Contrairement à la croyance populaire dans l’industrie de l’informatique, il existe un algorithme simple pour résoudre ce problème, qui est robuste, précis et simple et qui provient de l’industrie aérospatiale. Il fonctionne dans le temps de manière linéaire en fonction du nombre de quaternions dont on fait la moyenne, plus un facteur constant (à peu près constant).

Soit Q = [a_1 * q_1 a_2 * q_2 ... a_n * q_n]

Où a_i est le poids du ième quaternion et q_i, le ième quaternion en cours de calcul de la moyenne, en tant que vecteur de colonne. Q est donc une matrice 4xN.

Le vecteur propre normalisé correspondant à la plus grande valeur propre de Q * Q ^ T est la moyenne pondérée. Puisque Q * Q ^ T est auto-adjoint et au moins positif, semi-défini, il existe des méthodes rapides et robustes pour résoudre ce problème propre. Le calcul du produit matrice-matrice est la seule étape qui augmente avec le nombre moyen d'éléments.

Voir cette note technique dans le Journal of Guidance, Control, and Dynamics de 2007 , qui est un document de synthèse de cette méthode et d’autres. À l'ère moderne, la méthode que j'ai citée ci-dessus fait un bon compromis entre fiabilité, robustesse et robustesse, et a déjà été publiée dans les manuels en 1978!

37
Jonathan

Voici l'implémentation de la fonction MATLAB que j'utilise pour calculer la moyenne des quaternions aux fins de l'estimation de l'orientation . vecteurs propres et valeurs propres. De nombreuses bibliothèques (y compris Eigen C++) peuvent le faire pour vous.

Vous pouvez lire la description/en-tête du fichier pour voir le calcul à partir du papier d'origine.

fichier matlab extrait de http://www.mathworks.com/matlabcentral/fileexchange/40098-tolgabirdal-averaging-quaternions :

% by Tolga Birdal
% Q is an Mx4 matrix of quaternions. weights is an Mx1 vector, a weight for
% each quaternion.
% Qavg is the weightedaverage quaternion
% This function is especially useful for example when clustering poses
% after a matching process. In such cases a form of weighting per rotation
% is available (e.g. number of votes), which can guide the trust towards a
% specific pose. weights might then be interpreted as the vector of votes 
% per pose.
% Markley, F. Landis, Yang Cheng, John Lucas Crassidis, and Yaakov Oshman. 
% "Averaging quaternions." Journal of Guidance, Control, and Dynamics 30, 
% no. 4 (2007): 1193-1197.
function [Qavg]=quatWAvgMarkley(Q, weights)

% Form the symmetric accumulator matrix
A=zeros(4,4);
M=size(Q,1);
wSum = 0;

for i=1:M
    q = Q(i,:)';
    w_i = weights(i);
    A=w_i.*(q*q')+A; % rank 1 update
    wSum = wSum + w_i;
end

% scale
A=(1.0/wSum)*A;

% Get the eigenvector corresponding to largest eigen value
[Qavg, ~]=eigs(A,1);

end
10
Gouda

Malheureusement, ce n'est pas si simple à faire, mais c'est possible. Voici un livre blanc expliquant les calculs à suivre: http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf

Consultez la page Wiki de Unity3D (avec le code): http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors

Aussi ce poste: http://forum.unity3d.com/threads/86898-Average-quaternions

10
Nathan Monteleone

Voici mon implémentation en python de l'algorithme de Tolga Birdal:

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]
3
consultit

Vous ne pouvez pas ajouter de quaternions. Ce que vous pouvez faire est de trouver un quaternion qui tourne continuellement entre deux angles, y compris à mi-chemin. L’interpolation des quaternions est appelée "slerp" et comporte une page wikipedia. C'est un truc très utile pour l'animation. À certains égards, slerp est la principale raison d'utiliser des quaternions en infographie.

2
burningbright

Il existe un rapport technique de 2001 qui indique que la moyenne est en fait une assez bonne approximation, à condition que les quaternions soient proches les uns des autres. (dans le cas de -q = q, vous pouvez simplement inverser ceux qui pointent dans l'autre sens en les multipliant par -1, de sorte que tous les quaternions impliquent la vie dans la même demi-sphère.

Une approche encore meilleure est esquissée dans cet article de 2007 , qui implique l’utilisation d’un SVD. C'est le même papier que Nathan a référencé. Je voudrais ajouter qu’il n’ya pas seulement un C++, mais aussi une implémentation de Matlab . En exécutant le script de test fourni avec le code matlab, je peux affirmer qu'il donne d'excellents résultats pour les petites perturbations (0,004 * bruit uniforme) des quaternions concernés:

qinit=Rand(4,1);
Q=repmat(qinit,1,10);

% apply small perturbation to the quaternions 
perturb=0.004;
Q2=Q+Rand(size(Q))*perturb;
2
adrelino

Avec les quaternions, vous pouvez faire la même chose, mais avec une petite correction: 1. Annulez quaternion avant de calculer la moyenne si son produit scalaire avec la somme précédente est négatif. 2. Normalisez le quaternion moyen, la fin de la moyenne, si votre bibliothèque fonctionne avec des quaternions unitaires.

Le quaternion moyen représentera une rotation approximative moyenne (erreur maximale de 5 degrés environ). 

AVERTISSEMENT: une matrice moyenne d’orientations différentes peut être cassée si les rotations sont trop différentes.

1
minorlogic

Puisqu'il existe différentes approches ici, j'ai écrit un script Matlab pour les comparer. Ces résultats semblent suggérer que le simple calcul de la moyenne et de la normalisation des quaternions (l'approche du wiki d'unité, appelé simple_average ici) pourrait suffire dans les cas où les quaternions sont suffisamment similaires et où de petites déviations sont acceptables.

Voici le résultat:

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

Et voici le 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
Fritz

Les quaternions ne constituent pas un ensemble idéal de DDL à utiliser pour les rotations lors du calcul d’une moyenne non contrainte.

Voici ce que j'utilise le plus souvent (

[MethodImpl(MethodImplOptions.AggressiveInlining)]
    internal static Vector3 ToAngularVelocity( this Quaternion q )
    {
        if ( abs(q.w) > 1023.5f / 1024.0f)
            return new Vector3();
            var angle = acos( abs(q.w) );
            var gain = Sign(q.w)*2.0f * angle / Sin(angle);

        return new Vector3(q.x * gain, q.y * gain, q.z * gain);
    }


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    internal static Quaternion FromAngularVelocity( this Vector3 w )
    {
        var mag = w.magnitude;
        if (mag <= 0)
            return Quaternion.identity;
        var cs = cos(mag * 0.5f);
        var siGain = sin(mag * 0.5f) / mag;
        return new Quaternion(w.x * siGain, w.y * siGain, w.z * siGain, cs);

    }

    internal static Quaternion Average(this Quaternion refence, Quaternion[] source)
    {

        var refernceInverse = refence.Inverse();
        Assert.IsFalse(source.IsNullOrEmpty());
        Vector3 result = new Vector3();
        foreach (var q in source)
        {
            result += (refernceInverse*q).ToAngularVelocity();
        }

        return reference*((result / source.Length).FromAngularVelocity());
    }
     internal static Quaternion Average(Quaternion[] source)
    {
        Assert.IsFalse(source.IsNullOrEmpty());
        Vector3 result = new Vector3();
        foreach (var q in source)
        {
            result += q.ToAngularVelocity();
        }

        return (result / source.Length).FromAngularVelocity();
    }
     internal static Quaternion Average(Quaternion[] source, int iterations)
    {
        Assert.IsFalse(source.IsNullOrEmpty());
        var reference = Quaternion.identity;
        for(int i = 0;i < iterations;i++)
        {
            reference = Average(reference,source);

        }
        return reference;

    }`
0
user3290232