matlab和Eigen库中的一些旋转矩阵(方向余弦矩阵)、四元数和欧拉角之间的转换和绘图的注意事项

最近用matlab和Eigen库中的一些旋转矩阵(方向余弦矩阵)、四元数和欧拉角之间的转换和绘图,弄得我有些头疼,把遇到的问题记录一下,以防以后又脑阔疼....有不同的理解可以再评论区批评指正~

主要问题有两个

1、matlab工具箱自带的的姿态转换函数定义有歧义(quat2eul()和quat2angle( )定义不一样),会导致转换出来的欧拉角结果出错;

2、eigen库中使用欧拉角转换eulerAngles()时,得到的结果每轴相差180度(看起来不太对,其实真正结果时对的)

  • 定义

首先要明确一下欧拉角、旋转矩阵、四元数的在表示旋转时的定义,参考:

https://zh.wikipedia.org/wiki/%E6%AC%A7%E6%8B%89%E8%A7%92

https://zh.wikipedia.org/wiki/%E6%97%8B%E8%BD%AC%E7%9F%A9%E9%98%B5

https://zh.wikipedia.org/wiki/%E5%9B%9B%E5%85%83%E6%95%B8

很可惜地,對於夾角的順序和標記,夾角的兩個軸的指定,並沒有任何常規。科學家對此從未達成共識。每當用到歐拉角時,我們必須明確的表示出夾角的順序,指定其參考軸。

这就是欧拉角很烦人的原因...很多情况下,我感觉在说欧拉角的时候,并没有提到其的旋转轴的顺序,故在画图的时候一般使用欧拉角来表示比较直观,就非常非常的头疼(也可能是我太菜了orz)...

所以,对于姿态表示欧拉角是不唯一的,而旋转矩阵和四元数是唯一的!

欧拉角的表示方式有24种,即2×(3*2*2)种,2代表内旋和外旋,即是固定坐标轴转还是按转动后的坐标轴转的;3代表第一次旋转的3个轴向,接着2代表除了上次的那个轴外的另外两个轴向,还有一个2也是代表除了上次的那个轴外的另外两个轴向。

内旋(intrinsic rotations) = 旋转轴(rotated axis)

外旋(extrinsic rotations) = 固定轴(static/fixed axis)

https://zhuanlan.zhihu.com/p/85108850

所以我们在使用欧拉角转旋转矩阵和四元数的时候,要首先分清时内旋还是外旋,然后在分清其旋转轴向...

比如该图代表的就是按转动后的坐标轴(蓝色的)计算欧拉角的,即为内旋

 

在一般的导航定位,比如无人车、无人机等的欧拉角表示,如果没有说明,则很可能是内旋的ZYX顺序。

 

  • Matlab函数转换

一般情况下,我们做完位姿解算,会使用matlab画出误差曲线来判断结果是否准确,这个时候一般用欧拉角来绘图比较直观,然而在绘图的时候,有时候会感觉画出来的欧拉角很奇怪,比如欧拉角上下相反...

在MATLAB工具包使用了两种不同的方式来表示四元数,在空航天工具箱中使用的四元数惯例本质上是其机器人学工具箱中使用的四元数惯例的共轭。 有关这些区别的更多详细信息,请参见以下链接:

https://ww2.mathworks.cn/matlabcentral/answers/352465-what-is-the-aerospace-blockset-quaternion-convention?s_tid=answers_rc1-2_p2_MLT

https://ww2.mathworks.cn/matlabcentral/answers/465053-rotation-order-of-quatrotate

Robotics Toolbox的是quat2eul( )

Aerospace Toolbox的是quat2angle( )

https://ww2.mathworks.cn/matlabcentral/answers/523677-quat2eul-quat-and-dcm2angle-r-difference-for-zyx-sequence

并且在matlab中,四元数的定义是a+bi+cj+dk,即w、x、y、z.

https://ww2.mathworks.cn/help/robotics/ref/quaternion.html

一般我们定义的四元数是按右手法则的,即拇指朝向旋转轴(xyz的方向),四指指向旋转方向(w代表旋转大小),这和Robotics Toolbox的是quat2eul( ) 的函数是吻合的,而Aerospace Toolbox的是quat2angle( )则是左手法则,故差一个共轭!

这些在使用相应函数时都要注意。

另外,这个工具箱有时候不好安装,要激活matlab的权限,对有的破解版来说比较麻烦,所以我觉得还是直接自己写一个转换代码比较方便T_T

这里代码的定义是一般无人机使用的欧拉角的定义,即内旋的ZYX

function [rpy] = q2euler(q)
% [qx qy qz qw] --> [roll, pitch, yaw] 

	qx = q(1);
	qy = q(2);
	qz = q(3);
    qw = q(4);
	
	roll = atan2(2*(qw*qx+qy*qz), 1-2*(qx*qx+qy*qy));
	pitch = asin(2*(qw*qy-qz*qx));
	yaw = atan2(2*(qw*qz+qx*qy), 1-2*(qy*qy+qz*qz));

    rpy = [roll, pitch, yaw];
end
function [rpy] = c2euler(c)
%UNTITLED2 此处显示有关此函数的摘要
%   此处显示详细说明
    rpy(1) = atan2(c(3,2),c(3,3));
    rpy(2) = atan2(-c(3,1),sqrt(c(3,2)^2+c(3,3)^2));
    rpy(3) = atan2(c(2,1),c(1,1));
end

具体请参考

https://www.cnblogs.com/tiandsp/p/10733607.html

  • Eigen库转换

在Eigen库中有互相转换的函数,见如下代码示例

旋转矩阵转欧拉角时,若该旋转矩阵是之前由欧拉角转换来的,想得到之前的欧拉角的值,则旋转轴的顺序必须一致,如从ZYX顺序来的,则使用eulerAngles()是得用ZYX顺序,即(2,1,0),得到的为ypr的值。

注意,Eigen库中eulerAngles()是默认内旋的方式

* \returns the Euler-angles of the rotation matrix \c *this using the convention defined by the triplet (\a a0,\a a1,\a a2)
  *
  * Each of the three parameters \a a0,\a a1,\a a2 represents the respective rotation axis as an integer in {0,1,2}.
  * For instance, in:
  * \code Vector3f ea = mat.eulerAngles(2, 0, 2); \endcode
  * "2" represents the z axis and "0" the x axis, etc. The returned angles are such that
  * we have the following equality:
  * \code
  * mat == AngleAxisf(ea[0], Vector3f::UnitZ())
  *      * AngleAxisf(ea[1], Vector3f::UnitX())
  *      * AngleAxisf(ea[2], Vector3f::UnitZ()); \endcode
  * This corresponds to the right-multiply conventions (with right hand side frames).
  * 
  * The returned angles are in the ranges [0:pi]x[-pi:pi]x[-pi:pi].

int main(int argc, char **argv)
{
Eigen::Vector3d rpy_raw, ypr;
    rpy_raw << 30, 60, 150;
    rpy_raw = rpy_raw * M_PI / 180;

    Eigen::Matrix3d c;

    //输入部分为YPR,所以转换的输出也是YPR
    c = Eigen::AngleAxisd(rpy_raw[2], Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(rpy_raw[1], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(rpy_raw[0], Eigen::Vector3d::UnitX());
    ypr = c.eulerAngles(2, 1, 0);

    cout << ypr.transpose() * 180 / M_PI << endl;

    return 0;
}

这个是按ZYX转的

下面这个是按照XYZ方式旋转的

//主函数
int main(int argc, char **argv)
{

    Eigen::Vector3d rpy_raw, rpy_1;
    rpy_raw << 30, 60, 150;
    rpy_raw = rpy_raw * M_PI / 180;

    Eigen::Matrix3d c;

    //输入部分为RPY,所以转换的输出也是RPY
    c = Eigen::AngleAxisd(rpy_raw[0], Eigen::Vector3d::UnitX()) * Eigen::AngleAxisd(rpy_raw[1], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(rpy_raw[2], Eigen::Vector3d::UnitZ());
    // c = Eigen::AngleAxisd(rpy_raw[2], Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(rpy_raw[1], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(rpy_raw[0], Eigen::Vector3d::UnitX());
    rpy_1 = c.eulerAngles(0, 1, 2);

    cout << rpy_1.transpose() * 180 / M_PI << endl;

    return 0;
}

若时四元数的话,只需要先把四元数转成旋转矩阵,再转成欧拉角即可

    Eigen::Quaterniond q;
    Eigen::Matrix3d c;
    c = q.toRotationMatrix();

回到最初的问题,为什么使用eulerAngles()有时候得到的欧拉角会相差180度

代码如下

int main(int argc, char **argv)
{
    Eigen::Vector3d rpy_raw;
    rpy_raw << -1, 2, 3;
    rpy_raw = rpy_raw * M_PI / 180;

    Eigen::Matrix3d c;
    c = Eigen::AngleAxisd(rpy_raw[2], Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(rpy_raw[1], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(rpy_raw[0], Eigen::Vector3d::UnitX());
    Eigen::Vector3d rpy_tran = c.transpose().eulerAngles(2, 1, 0) / M_PI * 180;
    std::cout << rpy_tran[2] << " " << rpy_tran[1] << " " << rpy_tran[0] << std::endl;

    return 0;

输出结果

-178.896 -178.055 176.964

正确的结果应该时-1,2,3的,我们转换得到的结果貌似每轴和正确结果都相差180度

回到eulerAngles()定义中去,有一句话

The returned angles are in the ranges [0:pi]x[-pi:pi]x[-pi:pi].

即row的角度范围时【0-180°】,当我们row小于0时,Eigen库会把YZ两轴旋转180度,从而达到让X轴旋转角度大于0°的目的,各位可以试一试按ZYX的方式旋转,将ZY分别旋转180度,原本X为负的旋转角就变成了正的!

这就是Eigen库输出的欧拉角看起来不太对的原因了!其实我们用到欧拉角,主要还是在画图中,比较直观,计算姿态的话直接用旋转矩阵和四元数,就没有这么多麻烦事了~

<think>我们正在编写一个用于IMU姿态解算的四元数矩阵运算。根据引用内容,姿态解算涉及四元数积分、方向余弦矩阵等概念。我们将分步骤实现: 1. 四元数基本运算(乘法、共轭、归一化等) 2. 四元数积分(用于更新姿态) 3. 四元数旋转矩阵转换 4. 矩阵运算(方向余弦矩阵的更新欧拉角提取) 注意:由于IMU数据存在噪声漂移,实际应用中通常需要结合滤波算法(如ESKF[^2]),但本主要关注四元数矩阵运算。 步骤: 1. 定义四元数结构体或类,包含四个分量(w, x, y, z) 2. 实现四元数运算:乘法、加法、标量乘法、共轭、模、归一化等 3. 实现四元数积分:利用陀螺仪数据(角速度)更新四元数(采用一阶龙格塔法或其他方法) 4. 实现四元数方向余弦矩阵旋转矩阵)的转换 5. 实现从方向余弦矩阵中提取欧拉角(滚、俯仰、偏航)的函数 6. 提供矩阵运算(如矩阵乘法、置)的支持 注意:在姿态解算中,我们通常使用单位四元数表示旋转,因此需要经常归一化。 代码结构(以C语言为例,因为嵌入式平台常用C): 我们将创建一个头文件一个源文件。 头文件(attitude_math.h): - 定义四元数结构体 - 函数声明:四元数运算、积分、转换等 源文件(attitude_math.c): - 实现各函数 由于用户要求编写的方法,我们将提供核心函数的实现思路代码示例。 参考引用[1][3]中的资料,特别是方向余弦矩阵的构建欧拉角的提取。 下面我们逐步实现: </think>### 编写IMU姿态解算的四元数矩阵运算的方法 #### 1. **核心数学原理** - **四元数表示旋转姿态**: 使用单位四元数 $q = [q_w, q_x, q_y, q_z]$ 表示三维旋转,避免欧拉角奇异性问题[^1]。姿态更新需满足约束 $||q||=1$。 - **方向余弦矩阵(DCM)**: 作为本体坐标系到全局坐标系的转换矩阵,其元素由坐标轴夹角的余弦构成[^3]: $$ C = \begin{bmatrix} i\cdot I & i\cdot J & i\cdot K \\ j\cdot I & j\cdot J & j\cdot K \\ k\cdot I & k\cdot J & k\cdot K \end{bmatrix} $$ #### 2. **关键运算模块实现** ```c // 四元数基本运算 typedef struct { float w, x, y, z; } Quaternion; // 四元数乘法(姿态复合) Quaternion quat_mult(Quaternion q1, Quaternion q2) { return (Quaternion){ q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z, q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y, q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x, q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w }; } // 四元数积分(角速度更新) Quaternion quat_integrate(Quaternion q, float gx, float gy, float gz, float dt) { Quaternion dq = {0, 0.5f*dt*gx, 0.5f*dt*gy, 0.5f*dt*gz}; return quat_normalize(quat_mult(q, dq)); // 需归一化 } // 四元数方向余弦矩阵 void quat_to_dcm(Quaternion q, float dcm[3][3]) { float qw2 = q.w*q.w, qx2 = q.x*q.x, qy2 = q.y*q.y, qz2 = q.z*q.z; dcm[0][0] = qw2 + qx2 - qy2 - qz2; dcm[0][1] = 2*(q.x*q.y - q.w*q.z); dcm[0][2] = 2*(q.x*q.z + q.w*q.y); dcm[1][0] = 2*(q.x*q.y + q.w*q.z); dcm[1][1] = qw2 - qx2 + qy2 - qz2; dcm[1][2] = 2*(q.y*q.z - q.w*q.x); dcm[2][0] = 2*(q.x*q.z - q.w*q.y); dcm[2][1] = 2*(q.y*q.z + q.w*q.x); dcm[2][2] = qw2 - qx2 - qy2 + qz2; } ``` #### 3. **工程优化策略** 1. **数值稳定性**: - 定期归一化四元数:$q \leftarrow q / ||q||$ - 使用泰勒展开替代三角函数(嵌入式优化) 2. **传感器融合**: ```c // 简化的ESKF修正示例 void eskf_correction(Quaternion* q, float accel[3]) { float gravity[3] = {0, 0, -1}; // 全局坐标系重力 float predicted_g[3] = quat_rotate(*q, gravity); // 当前姿态预估的重力 float error = cross_product(accel, predicted_g); // 计算误差 *q = quat_mult(*q, exp_map(error * Kp)); // 指数映射修正 } ``` 3. **内存优化**: - 使用32位浮点避免四元数漂移 - 矩阵运算采用行优先存储 #### 4. **测试验证方法** - **静态测试**:验证重力对齐误差 < 0.5° - **动态测试**:对比商业(如Eigen, ROS tf) - **奇异场景**:高速旋转(>300°/s)下的收敛性测试 #### 5. **推荐开发资源** 1. 数学基础:Ken Shoemake《Quaternion Calculus》[^1] 2. 开源参考: - NASA核心算法:`cfe_es_rotation.c` - Madgwick滤波器(C实现) 3. 硬件加速:STM32的Cordic协处理单元优化三角函数 > **关键点**:实时系统中需保证单次姿态解算周期 ≤ IMU采样间隔(通常1ms),建议采用预编译矩阵模板提升性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文锦渡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值