英文:
Calculating Euler angles from Rotation matrix in C++
问题
我有这个Python代码:
from scipy.spatial.transform import Rotation
r = Rotation.from_matrix(R)
angles = r.as_euler("zyx", degrees=True)
print(f'angles = {angles}')
其中 `R` 是 `3x3` 旋转矩阵。
我还有这段C++代码,它的目的是与Python代码完全相同:
Eigen::Map<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> eigen_R(R.ptr<float>());
Eigen::Quaternionf q(eigen_R);
Eigen::Vector3f euler = q.toRotationMatrix().eulerAngles(0, 1, 2);
double angle_x = euler[0];
double angle_y = euler[1];
double angle_z = euler[2];
// 将角度转换为度数
// double deg_factor = 180.0 / M_PI;
double deg_factor = 1.0;
float half_circle = M_PI * deg_factor;
float yaw_dim = angle_y > 0 ? half_circle : -half_circle;
_headPose[0] = (angle_y * deg_factor); // 偏航
_headPose[1] = (angle_x * deg_factor); // 俯仰
_headPose[2] = (angle_z * deg_factor); // 横滚
问题是:如果我使用以下 `R` 矩阵运行Python和C++代码:
R = [[ 0.99640366 -0.05234712 0.06662979]
[ 0.05348801 0.99844889 -0.01545452]
[-0.06571744 0.01896283 0.99765807]]
它们会输出相同的结果,即
偏航 = 3.82043
俯仰 = 0.887488
横滚 = 3.00733
但如果 `R` 矩阵等于
R = [[ 0.99321075 -0.08705603 0.07715995]
[ 0.08656997 0.99619924 0.00962846]
[-0.0777049 -0.00288336 0.99697223]]
然后Python代码输出:
偏航 = 4.425338029132474,俯仰 = -0.5533284872549677,横滚 = 5.0092369943283375
当C++输出:
偏航 = 175.575
俯仰 = 179.447
横滚 = -174.991
实际上,我正在跟踪一个移动物体的角度,当从(0,0,0)度点出现细微变化时,C++中的角度会出现反转。为了获得正确的角度,我需要做类似以下的操作:
_headPose[0] = yaw_dim - (angle_y * deg_factor); // 偏航
_headPose[1] = (angle_x * deg_factor) - half_circle; // 俯仰
_headPose[2] = (angle_z * deg_factor) - half_circle; // 横滚
我的角度知识非常生疏,我不明白为什么如此微小的变化会导致完全不同方向的值。请建议如何修复它。
英文:
I have this code in Python:
from scipy.spatial.transform import Rotation
r = Rotation.from_matrix(R)
angles = r.as_euler("zyx",degrees=True)
print(f'angles = {angles}')
Where R
is 3x3
rotation matrix.
And I have this code in C++ which is supposed to do exactly the same thing as Python bit:
Eigen::Map<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> eigen_R(R.ptr<float>());
Eigen::Quaternionf q(eigen_R);
Eigen::Vector3f euler = q.toRotationMatrix().eulerAngles(0, 1, 2);
double angle_x = euler[0];
double angle_y = euler[1];
double angle_z = euler[2];
// Convert angles to degrees
//double deg_factor = 180.0 / M_PI;
double deg_factor = 1.0;
float half_circle = M_PI * deg_factor;
float yaw_dim = angle_y > 0 ? half_circle : -half_circle;
_headPose[0] = (angle_y * deg_factor); // yaw
_headPose[1] = (angle_x * deg_factor); // pitch
_headPose[2] = (angle_z * deg_factor); // roll
The issue: if I run both Python and C++ codes with this R
matrix:
R = [[ 0.99640366 -0.05234712 0.06662979]
[ 0.05348801 0.99844889 -0.01545452]
[-0.06571744 0.01896283 0.99765807]]
They give the same results which is
Yaw = 3.82043
Pitch = 0.887488
Roll = 3.00733
But if R
matrix equals to
R = [[ 0.99321075 -0.08705603 0.07715995]
[ 0.08656997 0.99619924 0.00962846]
[-0.0777049 -0.00288336 0.99697223]]
Then Python code outputs:
yaw = 4.425338029132474, pitch = -0.5533284872549677, roll = 5.0092369943283375
When C++ outputs:
Yaw = 175.575
Pitch = 179.447
Roll = -174.991
In reality I am tracking the angle of a moving object and when there is a subtle change from (0, 0, 0) degrees point, angles in C++ become reversed for some reason. And in order to get the right angles I need to do something like:
_headPose[0] = yaw_dim - (angle_y * deg_factor); // yaw
_headPose[1] = (angle_x * deg_factor) - half_circle; // pitch
_headPose[2] = (angle_z * deg_factor) - half_circle; // roll
My knowledge on angles are very rusty and I do not understand why such a subtle change result in completely differently oriented values. Please advise how to fix it
答案1
得分: 3
从Python中的预期答案(以及scipy上略微含糊的文档)来看,序列“zyx”似乎表示外部旋转,首先是关于z轴,然后是y轴,最后是x轴。用元素矩阵表示的矩阵将是Rx.Ry.Rz。
“外部”(我更喜欢“主动”)旋转是物体的旋转,但坐标框架保持固定。而“内部”(我更喜欢“被动”)旋转是物体保持不变,但坐标参考框架旋转。
从中,您可以逐个提取角度。Y(围绕y轴的旋转)来自顶部角的sin(Y)的孤立值。这应该为Y提供一个在[-90,90]度范围内的唯一解(这是您的代码似乎存在问题的地方)。然后,只要cos(Y)不为零(即Y既不是-90度也不是90度),您可以从最后一列的底部两个元素中得到一个明确的X,同时您可以从顶行的前两个元素中得到一个明确的Z。如果Y是+-90度,您会遇到一个令人讨厌的称为万向锁的歧义情况 - 在这种情况下,解不是唯一的,您只能预测X-Z或X+Z。(您的测试案例不会陷入这个陷阱。)
以下代码为两个测试案例产生了预期的答案。请注意,我提供了关于不同轴的明确旋转,而不是您特定的“偏航”、“俯仰”和“横滚”的定义 - 如果我使用您的约定,那绝对不会得到我工程同事的认可,因为这绝对不是用于船舶或飞机的约定。
在非常苛刻的注意事项上,您实际上使用的是“Tait-Bryan角”,而不是“欧拉角” - 您的三个旋转轴都不同。
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
const double PI = 4.0 * atan( 1.0 ), RADTODEG = 180.0 / PI;
//=====================================================================
vector<double> getAngles( const string &seq, const vector<vector<double>> &R, bool asDegrees )
{
const double EPS = 1.0e-6;
double X{}, Y{}, Z{};
if ( seq == "zyx" ) // 主动旋转;先绕z轴,然后y轴,最后x轴
{ // 矩阵为Rx.Ry.Rz
Y = asin( R[0][2] ); // [-pi/2,pi/2]内的唯一角度
if ( abs( abs( R[0][2] ) - 1.0 ) < EPS ) // 噢!万向锁。X和Z有无限选择
{
X = atan2( R[2][1], R[1][1] ); // 从众多选择中选择一个
Z = 0.0;
}
else // (-pi,pi]内的唯一解
{
X = atan2( -R[1][2], R[2][2] ); // atan2提供了正确的象限和唯一解
Z = atan2( -R[0][1], R[0][0] );
}
}
else
{
cout << "尚未编写此旋转序列。\n";
}
if ( asDegrees ) { X *= RADTODEG; Y *= RADTODEG; Z *= RADTODEG; }
return { X, Y, Z };
}
//=====================================================================
int main()
{
vector<vector<double>> R1 = { { 0.99640366, -0.05234712, 0.06662979 },
{ 0.05348801, 0.99844889, -0.01545452 },
{ -0.06571744, 0.01896283, 0.99765807 } };
vector<vector<double>> R2 = { { 0.99321075, -0.08705603, 0.07715995 },
{ 0.08656997, 0.99619924, 0.00962846 },
{ -0.0777049, -0.00288336, 0.99697223 } };
auto angles = getAngles( "zyx", R1, true );
cout << "案例1:按顺序:\n";
cout << "绕z轴旋转: " << angles[2] << '\n'
<< "绕y轴旋转: " << angles[1] << '\n'
<< "绕x轴旋转: " << angles[0] << '\n';
angles = getAngles( "zyx", R2, true );
cout << "\n案例2:按顺序:\n";
cout << "绕z轴旋转: " << angles[2] << '\n'
<< "绕y轴旋转: " << angles[1] << '\n'
<< "绕x轴旋转: " << angles[0] << '\n';
}
输出:
案例1:按顺序:
绕z轴旋转: 3.00733
绕y轴旋转: 3.82044
绕x轴旋转: 0.887486
案例2:按顺序:
绕z轴旋转: 5.00924
绕y轴旋转: 4.42534
绕x轴旋转: -0.553328
英文:
From the intended answer in Python (and the slightly ambiguous documentation on scipy) the sequence "zyx" appears to mean extrinsic rotations, first about z, then y, then x. The matrix for this would be Rx.Ry.Rz in terms of elemental matrices.
"Extrinsic" (I prefer "active") rotations are rotations of the object, but keeping the coordinate frame fixed. "Intrinsic" (I prefer "passive") rotations are those where the object stays constant but the coordinate reference frame is rotated.
The matrix product Rx.Ry.Rz is
From this you can pick out the angles one by one. Y (the rotation about the y-axis) comes from the isolated value for sin(Y) in the top corner. This should give a unique solution for Y in the range [-90,90] degrees. (This is where your code appears to be wrong). Then, provided cos(Y) is not zero (i.e. Y is not either -90 degrees or 90 degrees) you can get an unambiguous X from the bottom two elements of the last column, whilst you can get an unambiguous Z from the first two elements of the top row. If Y is +-90 degrees you get a nasty ambiguity called gimbal lock - in this case, solutions are not unique and you can only predict either X-Z or X+Z. (Your test cases do not fall into this pit.)
The following code produces the intended answers for the two test cases. Note that I have given explicit rotations about the different axes, rather than your particular definition of "yaw", "pitch" and "roll" - I would never live it down amongst my engineering colleagues if I use your convention, which is definitely not what is used for a ship or an aeroplane.
On a really pedantic note you are actually using "Tait-Bryan angles", rather than "Euler angles" - all your three axes of rotation are different.
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
const double PI = 4.0 * atan( 1.0 ), RADTODEG = 180.0 / PI;
//=====================================================================
vector<double> getAngles( const string &seq, const vector<vector<double>> &R, bool asDegrees )
{
const double EPS = 1.0e-6;
double X{}, Y{}, Z{};
if ( seq == "zyx" ) // Active rotation; z-axis, followed by y-axis, followed by x-axis
{ // Matrix would be Rx.Ry.Rz
Y = asin( R[0][2] ); // Unique angle in [-pi/2,pi/2]
if ( abs( abs( R[0][2] ) - 1.0 ) < EPS ) // Yuk! Gimbal lock. Infinite choice of X and Z
{
X = atan2( R[2][1], R[1][1] ); // One choice amongst many
Z = 0.0;
}
else // Unique solutions in (-pi,pi]
{
X = atan2( -R[1][2], R[2][2] ); // atan2 gives correct quadrant and unique solutions
Z = atan2( -R[0][1], R[0][0] );
}
}
else
{
cout << "This sequence of rotations not coded yet.\n";
}
if ( asDegrees ) { X *= RADTODEG; Y *= RADTODEG; Z *= RADTODEG; }
return { X, Y, Z };
}
//=====================================================================
int main()
{
vector<vector<double>> R1 = { { 0.99640366, -0.05234712, 0.06662979 },
{ 0.05348801, 0.99844889, -0.01545452 },
{ -0.06571744, 0.01896283, 0.99765807 } };
vector<vector<double>> R2 = { { 0.99321075, -0.08705603, 0.07715995 },
{ 0.08656997, 0.99619924, 0.00962846 },
{ -0.0777049, -0.00288336, 0.99697223 } };
auto angles = getAngles( "zyx", R1, true );
cout << "Case 1: in order:\n";
cout << "Rotation about z-axis: " << angles[2] << '\n'
<< "Rotation about y-axis: " << angles[1] << '\n'
<< "Rotation about x-axis: " << angles[0] << '\n';
angles = getAngles( "zyx", R2, true );
cout << "\nCase 2: in order:\n";
cout << "Rotation about z-axis: " << angles[2] << '\n'
<< "Rotation about y-axis: " << angles[1] << '\n'
<< "Rotation about x-axis: " << angles[0] << '\n';
}
Output:
Case 1: in order:
Rotation about z-axis: 3.00733
Rotation about y-axis: 3.82044
Rotation about x-axis: 0.887486
Case 2: in order:
Rotation about z-axis: 5.00924
Rotation about y-axis: 4.42534
Rotation about x-axis: -0.553328
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论