我正在尝试在我的OpenGL程序中从矩阵切换到四元数以进行骨架动画,但我遇到了一个问题:
给定一些单位四元数,我需要得到一个四元数,当用于变换向量时,它将给出一个向量,该向量是每个四元数分别变换的向量的平均值。 (使用矩阵我只需将矩阵加在一起并除以矩阵的数量)
答案 0 :(得分:11)
不幸的是,这并不是非常简单,但它是可能的。这是一份白皮书,解释其背后的数学:http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf
查看Unity3D Wiki页面(带代码):http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
此帖也是:http://forum.unity3d.com/threads/86898-Average-quaternions
答案 1 :(得分:10)
Here is the implementation用于MATLAB函数,我用它来平均四元数以进行方向估计。 将MATLAB转换为任何其他语言是很简单的,除了这种特殊方法(Markley 2007)需要计算特征向量和特征值。有许多库(包括Eigen C ++)可以为您完成此任务。
您可以阅读文件的说明/标题,以查看原始论文中的数学。
从http://www.mathworks.com/matlabcentral/fileexchange/40098-tolgabirdal-averaging-quaternions获取的matlab文件:
% 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
答案 2 :(得分:4)
这是我在Tolga Birdal算法的python中的实现:
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 :(得分:3)
我尝试按照建议的here对四元数进行Slerping,但这对我正在尝试做的事情不起作用(模型被扭曲),所以我最终只是按每个四元数转换向量然后再做一个平均值(直到我能找到更好的解决方案)。
答案 4 :(得分:2)
您无法添加四元数。你能做的是找到一个在两个角度之间连续旋转的四元数,包括中途。四元数插值称为“slerp”,并有一个维基百科页面。这对于动画来说非常有用。在某些方面,slerp是在计算机图形中使用四元数的主要原因。
答案 5 :(得分:2)
有一个technical report from 2001表明平均值实际上非常接近,只要四元数靠近在一起即可。 (对于-q = q的情况,你可以通过预先将它们乘以-1来翻转指向另一个方向的那些,这样所有的四元数都包含在相同的半球中。
在this paper from 2007中草拟了更好的方法,其中涉及使用SVD。这与内森所引用的论文相同。我想补充说,不仅有C ++,还有Matlab implementation。从执行matlab代码附带的测试脚本开始,我可以说它为所涉及的四元数的小扰动(0.004 *均匀噪声)提供了非常好的结果:
qinit=rand(4,1);
Q=repmat(qinit,1,10);
% apply small perturbation to the quaternions
perturb=0.004;
Q2=Q+rand(size(Q))*perturb;
答案 6 :(得分:1)
使用四元数,你可以做同样的事情,但有小的修正: 1.如果其前一个和的点积为负,则在求平均值之前取消四元数。 2.如果您的库使用单位四元数,则归一化平均四元数,即平均值的结束。
平均四元数将代表近似平均旋转(最大误差约为5度)。
警告:如果旋转太不相同,可以打破不同方向的平均矩阵。
答案 7 :(得分:1)
由于这里有不同的方法,因此我编写了一个Matlab脚本进行比较。这些结果似乎表明,只要对四元数足够相似并且可以接受很小的偏差,只需对四元数进行平均和标准化(此处来自统一维基的方法,称为simple_average
)就足够了。
以下是输出:
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
这是代码:
%% 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']);
答案 8 :(得分:1)
Markley解决方案最简单的实现(在Python中为Numpy> = 3.6)将是:
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()]
其中Q
的大小为N×4。生成的四元数已被标准化。
在这种情况下,默认情况下权重等于1。否则,您可以给出大小为N的权重列表(每个四元数之一)。
就是这样。
答案 9 :(得分:0)
在计算无约束平均值时,四元数不是用于旋转的理想自由度集。
这是我大部分时间使用的(
[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;
}`
答案 10 :(得分:0)
Check here是我对四元数的加权平均以及Lp中值的解决方案。
答案 11 :(得分:0)
这里是GitHub Repo,其中包含此建议算法的实现:) https://github.com/christophhagen/averaging-quaternions
感谢和感谢christophhagen ofc;)