"Moyenne" de plusieurs quaternions?
j'essaie de passer des matrices aux quaternions pour l'animation squelettique dans mon programme OpenGL, mais j'ai rencontré un problème:
Donné un certain nombre de quaternions unitaires, j'ai besoin d'obtenir un quaternion que lorsqu'il est utilisé pour transformer un vecteur de donner un vecteur qui est la moyenne du vecteur transformé par chaque quaternion individuellement. (avec des matrices, je voudrais simplement ajouter les matrices ensemble et diviser par le nombre de matrices)
9 réponses
contrairement à la croyance populaire dans l'industrie de l'infographie, il existe un algorithme simple pour résoudre ce problème qui est robuste, précis et simple qui vient de l'industrie aérospatiale. Il s'exécute dans le temps linéaire dans le nombre de quaternions faisant l'objet d'une moyenne plus un facteur (large) 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 est le ième quaternion faisant l'objet d'une moyenne, comme vecteur de colonne. Q est donc une matrice 4xN.
Voir le note technique dans le Journal de l'Orientation, de Contrôle, et de la Dynamique de 2007, qui est un résumé de ce et d'autres méthodes. Dans l'ère moderne, la méthode que j'ai citée ci-dessus fait un bon compromis pour la fiabilité et la robustesse de la mise en œuvre, et a déjà été publié dans les manuels en 1978!
malheureusement, ce n'est pas si simple à faire, mais c'est possible. Voici un livre blanc expliquant les maths derrière lui: http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf
consultez la page Wiki Unity3D (avec le code):http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
Aussi ce post: http://forum.unity3d.com/threads/86898-Average-quaternions
Voici la mise en oeuvre pour la fonction MATLAB que j'utilise pour faire la moyenne des Quaternions pour l'estimation de l'orientation. Il est facile de convertir le MATLAB à n'importe quelle autre langue, sauf que cette méthode particulière (Markley 2007) exige le calcul des vecteurs propres et des valeurs propres. Il existe de nombreuses bibliothèques (y compris Eigen c++) qui peuvent le faire pour vous.
Vous pouvez lire la description / en-tête du fichier pour voir les maths de l'original papier.
fichier matlab tiré 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
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]
vous ne pouvez pas ajouter les quaternions. Ce que vous pouvez faire est de trouver un quaternion qui tourne en continu entre deux angles, y compris à mi-chemin. Quaternion interpolation est connu sous le nom de" slerp " et a une page wikipedia. C'est un truc très utile pour l'animation. À certains égards, slerp est la raison principale de l'utilisation de quaternions dans l'infographie.
j'ai essayé Slerping quaternions comme l'a suggéré ici mais cela n'a pas fonctionné pour ce que j'essaie de faire (le modèle a été déformé), donc j'ai simplement fini par transformer les vecteurs par chaque quaternion et ensuite faire une moyenne (jusqu'à ce que je puisse trouver une meilleure solution).
Il y a un rapport technique de 2001 qui indique que la moyenne est en fait une assez bonne approximation, à condition que les quaternions soient rapprochés. (pour le cas de-q=q, Vous pouvez simplement retourner ceux qui pointent dans l'autre direction 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, ce qui implique l'utilisation d'un SVD. C'est le même que Nathan a mentionné. Je voudrais ajouter qu'il n'y a pas seulement un C++, mais aussi un Matlab mise en œuvre. En exécutant le script de test qui est fourni avec le code matlab, je peux dire qu'il donne de très bons résultats pour les petites pertubations (0.004 * bruit uniforme) des quaternions impliquées:
qinit=rand(4,1);
Q=repmat(qinit,1,10);
% apply small perturbation to the quaternions
perturb=0.004;
Q2=Q+rand(size(Q))*perturb;
avec quaternions vous pouvez faire la même chose, mais avec une petite correction: 1. Nier quaternion avant de faire la moyenne si son produit de point avec la somme précédente est négative. 2. Normaliser le quaternion Moyen, une fin de moyenne, si votre bibliothèque fonctionne avec les quaternions unité.
le quaternion moyen représente approximativement la rotation moyenne (erreur max d'environ 5 degrés).
attention: la matrice moyenne de différentes orientations peut être brisée si les rotations trop différent.
les Quaternions ne sont pas un ensemble idéal de DOF à utiliser pour les rotations lors du calcul d'une moyenne sans contrainte.
Voici ce que j'utilise la plupart du temps (
[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;
}`