我有一个旋转四元数,想提取绕上方向轴(偏航)的旋转角度。我正在使用XNA,据我所知,它没有内置的函数可以实现这一点。如何最好地完成此操作?
谢谢任何帮助, Venatu
我有一个旋转四元数,想提取绕上方向轴(偏航)的旋转角度。我正在使用XNA,据我所知,它没有内置的函数可以实现这一点。如何最好地完成此操作?
谢谢任何帮助, Venatu
四元数表示旋转是一种基于轴和角度的变化。如果您围绕轴x、y、z旋转r弧度,则您的四元数q为:
q[0] = cos(r/2);
q[1] = sin(r/2)*x;
q[2] = sin(r/2)*y;
q[3] = sin(r/2)*z;
如果你想创建一个只绕y轴旋转的四元数,你需要将x和z轴清零,然后重新归一化四元数:
q[1] = 0;
q[3] = 0;
double mag = sqrt(q[0]*q[0] + q[2]*q[2]);
q[0] /= mag;
q[2] /= mag;
如果你想得到结果的角度:
double ang = 2*acos(q[0]);
这里假设四元数表示为:w,x,y,z。如果q[0]和q[2]都是0或接近于0,那么得到的四元数应该是{1,0,0,0}。
给定一个四元数q,您可以按以下方式计算滚转,俯仰和偏航:
var yaw = atan2(2.0*(q.y*q.z + q.w*q.x), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
var pitch = asin(-2.0*(q.x*q.z - q.w*q.y));
var roll = atan2(2.0*(q.x*q.y + q.w*q.z), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
这应该适用于xyz顺序的本征TAIT-BRYAN旋转。对于其他旋转顺序,需要使用外禀和proper-euler旋转进行其他转换。
注意:我已经验证了下面的代码,与维基百科的方程式和Pixhawk的文档相符,是正确的。
如果您正在处理无人机/航空领域,以下是该代码(直接从DJI SDK中取出)。q0、q1、q2、q3分别对应四元数的w、x、y、z分量。另外请注意,偏航角、俯仰角和滚转角在某些文献中可能分别被称为航向角、姿态角和旋转角。
float roll = atan2(2.0 * (q.q3 * q.q2 + q.q0 * q.q1) , 1.0 - 2.0 * (q.q1 * q.q1 + q.q2 * q.q2));
float pitch = asin(2.0 * (q.q2 * q.q0 - q.q3 * q.q1));
float yaw = atan2(2.0 * (q.q3 * q.q0 + q.q1 * q.q2) , - 1.0 + 2.0 * (q.q0 * q.q0 + q.q1 * q.q1));
如果你需要计算全部三个,则可以使用以下函数来避免重复计算公共项:
//Source: http://docs.ros.org/latest-lts/api/dji_sdk_lib/html/DJI__Flight_8cpp_source.html#l00152
EulerianAngle Flight::toEulerianAngle(QuaternionData data)
{
EulerianAngle ans;
double q2sqr = data.q2 * data.q2;
double t0 = -2.0 * (q2sqr + data.q3 * data.q3) + 1.0;
double t1 = +2.0 * (data.q1 * data.q2 + data.q0 * data.q3);
double t2 = -2.0 * (data.q1 * data.q3 - data.q0 * data.q2);
double t3 = +2.0 * (data.q2 * data.q3 + data.q0 * data.q1);
double t4 = -2.0 * (data.q1 * data.q1 + q2sqr) + 1.0;
t2 = t2 > 1.0 ? 1.0 : t2;
t2 = t2 < -1.0 ? -1.0 : t2;
ans.pitch = asin(t2);
ans.roll = atan2(t3, t4);
ans.yaw = atan2(t1, t0);
return ans;
}
QuaternionData Flight::toQuaternion(EulerianAngle data)
{
QuaternionData ans;
double t0 = cos(data.yaw * 0.5);
double t1 = sin(data.yaw * 0.5);
double t2 = cos(data.roll * 0.5);
double t3 = sin(data.roll * 0.5);
double t4 = cos(data.pitch * 0.5);
double t5 = sin(data.pitch * 0.5);
ans.q0 = t2 * t4 * t0 + t3 * t5 * t1;
ans.q1 = t3 * t4 * t0 - t2 * t5 * t1;
ans.q2 = t2 * t5 * t0 + t3 * t4 * t1;
ans.q3 = t2 * t4 * t1 - t3 * t5 * t0;
return ans;
}
关于Eigen库的注释
如果你正在使用Eigen库,则它有另一种方法来进行此转换,但是这可能不如上面的直接代码优化:
Vector3d euler = quaternion.toRotationMatrix().eulerAngles(2, 1, 0);
yaw = euler[0]; pitch = euler[1]; roll = euler[2];
希望您知道偏航角、俯仰角和横滚角不适用于任意旋转。欧拉角存在奇点(请参见上面的链接)和不稳定性。请看David Sachs演示中的38:25:
http://www.youtube.com/watch?v=C7JQ7Rpwn2k
祝好运!
四元数由两个部分组成:一个三维向量部分和一个标量部分。
四元数的向量部分描述了每个轴独立旋转的角度,因此只需要将向量部分的x和y分量清零,并保留z分量即可求解向量项:
// Don't modify qz
double qx = 0;
double qy = 0;
标量项表示旋转的大小。对于单位四元数(如用于表示姿态的四元数),整个四元数的大小必须为1。因此,可以通过以下方式解决标量项:
double qw = sqrt(1 - qx*qx - qy*qy - qz*qz);
由于qx和qy都是零,标量分量可以表示为:
double qw = sqrt(1 - qz*qz);
double qx = 0;
double qy = 0;
// Don't modify qz
double qw = sqrt(1 - qz*qz);
从四元数到偏航、俯仰和翻滚的转换取决于定义四元数和偏航、俯仰和翻滚的约定。对于给定的约定,有许多“几乎正确”的转换可以适用于大多数角度,但只有一个真正正确的转换可以适用于所有角度,包括南极和北极,在那里“几乎正确”的转换会产生万向锁(虚假翻转和旋转)。
请参阅此教程以获取更多信息:
acos()
总是返回一个正值。所以你会得到0 <= ang < 2*pi
。但是如果你想要一个负角度(例如,如果0表示向前,+/-角度表示左/右转),你只需要这样做:if (ang > pi) ang -= 2*pi
- JCooperatan(q[2],q[0])*2
,而不是计算2*acos(q0/sqrt(q0*q0+q2*q2))
,这也已经归一化为[-pi, pi]。 - chtz