ArduPilot姿态控制方法解析---(倾转分离)

2023-05-16

先给出一些预备知识:

  1. 欧拉角:即所谓的Roll , Pitch , Yaw. 而事实上,根据其定义,欧拉角的具有不同的表示。存在先绕哪个轴,后绕哪个轴转的差别(将旋转分解成绕三个轴的转角)。这种表示方法比较直观,但是存在万向锁(Gimbal Lock)问题,也称为奇异性问题。可以参考这个链接进行理解,这里不再赘述 点击这里跳转。

  2. 四元数:紧凑的,没有奇异性问题,较多在工程中应用。缺点是不够直观。假设某个旋转是绕着单位向量 n = [ n x , n y , n z ] T n=[n_x,n_y,n_z]^T n=[nx,ny,nz]T进行角度为 θ \theta θ的旋转,那么这个四元数定义为
    q = [ c o s θ 2 , n x s i n θ 2 , n y s i n θ 2 , n z s i n θ 2 ] T q=[cos{\theta \over 2}, n_xsin{\theta \over 2},n_ysin{\theta \over 2},n_zsin{\theta \over 2}]^T q=[cos2θ,nxsin2θ,nysin2θ,nzsin2θ]T

  3. 轴角法:任意旋转都是用一个旋转轴和一个旋转角来刻画。假设有一个单位旋转轴为 n n n,角度为 θ \theta θ的旋转,那么它对应的旋转向量为 n θ n\theta nθ。从旋转向量到旋转矩阵的转换过程由 罗德里格斯公式表明。
    R = c o s θ I + ( 1 − c o s θ ) n n T + s i n θ n ∧ R=cos\theta I + (1-cos\theta)nn^T+sin\theta n^{\land} R=cosθI+(1cosθ)nnT+sinθn

  4. 更多参考引用 :
    [飞控]姿态误差(四)-APM如何计算姿态误差
    [飞控]姿态误差(三)-四元数和轴角求误差
    [飞控]姿态误差(一)-欧拉角做差
    [飞控]倾转分离(三)-大总结
    [飞控]倾转分离(二)-PX4倾转分离,效果验证
    [飞控]倾转分离(一)-APM的倾转分离竟然没有效果?

  5. 通过B到A的四元数,根据公式计算出来的是A到B的旋转矩阵!!!
    四元数的二义性,从姿态A旋转到姿态B可以存在两种旋转方式,一个绕小圈,一个绕大圈,两个旋转对应的四元数互为相反数。

姿态本质是旋转,姿态的误差其实是旋转之间的误差,那必须还是个旋转,那么直接相减还能表示旋转吗?也就是说相减得到的角度转换成旋转矩阵还是个正交矩阵吗?

而且欧拉角是有隐藏条件的,就是旋转顺序,同样是三个角 (10,20.30) ,x-y-z,y-x-z顺序可是两个不同的旋转,如果我不告诉你它的旋转顺序,那其实它就是没用的三个数,因为它根本就没法还原成旋转。但是我就没见过有对控制器做先后顺序处理的,roll,pitch,yaw的控制器都是同时启动,没有顺序之分。

所以,凭什么姿态误差可以用欧拉角相减!!!
以下摘自“无人机干货铺”原文:
在这里插入图片描述
作者缜密的思路,说的简直太漂亮了!

ArduPilot姿态控制

同PX4一样,均采用的是四元数旋转完成姿态的控制。分成两个步骤完成,本次剖析代码如下:

// thrust_heading_rotation_angles - calculates two ordered rotations to move the att_from_quat quaternion to the att_to_quat quaternion.
// The first rotation corrects the thrust vector and the second rotation corrects the heading vector.
void AC_AttitudeControl::thrust_heading_rotation_angles(Quaternion& att_to_quat, const Quaternion& att_from_quat, Vector3f& att_diff_angle, float& thrust_vec_dot)
{
    Matrix3f att_to_rot_matrix; // rotation from the target body frame to the inertial frame.
    att_to_quat.rotation_matrix(att_to_rot_matrix);          
    Vector3f att_to_thrust_vec = att_to_rot_matrix * Vector3f(0.0f, 0.0f, 1.0f);

    Matrix3f att_from_rot_matrix; // rotation from the current body frame to the inertial frame.
    att_from_quat.rotation_matrix(att_from_rot_matrix);
    Vector3f att_from_thrust_vec = att_from_rot_matrix * Vector3f(0.0f, 0.0f, 1.0f);

    // the dot product is used to calculate the current lean angle for use of external functions
    _thrust_angle = acosf(constrain_float(Vector3f(0.0f,0.0f,1.0f) * att_from_thrust_vec,-1.0f,1.0f));

    // the cross product of the desired and target thrust vector defines the rotation vector
    Vector3f thrust_vec_cross = att_from_thrust_vec % att_to_thrust_vec;

    // the dot product is used to calculate the angle between the target and desired thrust vectors
    thrust_vec_dot = acosf(constrain_float(att_from_thrust_vec * att_to_thrust_vec, -1.0f, 1.0f));

    // Normalize the thrust rotation vector
    float thrust_vector_length = thrust_vec_cross.length();
    if (is_zero(thrust_vector_length) || is_zero(thrust_vec_dot)) {
        thrust_vec_cross = Vector3f(0, 0, 1);
        thrust_vec_dot = 0.0f;
    } else {
        thrust_vec_cross /= thrust_vector_length;
    }
    Quaternion thrust_vec_correction_quat;
    thrust_vec_correction_quat.from_axis_angle(thrust_vec_cross, thrust_vec_dot);

    // Rotate thrust_vec_correction_quat to the att_from frame
    thrust_vec_correction_quat = att_from_quat.inverse() * thrust_vec_correction_quat * att_from_quat;

    // calculate the remaining rotation required after thrust vector is rotated transformed to the att_from frame
    Quaternion yaw_vec_correction_quat = thrust_vec_correction_quat.inverse() * att_from_quat.inverse() * att_to_quat;

    // calculate the angle error in x and y.
    Vector3f rotation;
    thrust_vec_correction_quat.to_axis_angle(rotation);
    att_diff_angle.x = rotation.x;
    att_diff_angle.y = rotation.y;

    // calculate the angle error in z (x and y should be zero here).
    yaw_vec_correction_quat.to_axis_angle(rotation);
    att_diff_angle.z = rotation.z;

    // Todo: Limit roll an pitch error based on output saturation and maximum error.

    // Limit Yaw Error based on maximum acceleration - Update to include output saturation and maximum error.
    // Currently the limit is based on the maximum acceleration using the linear part of the SQRT controller.
    // This should be updated to be based on an angle limit, saturation, or unlimited based on user defined parameters.
    if (!is_zero(_p_angle_yaw.kP()) && fabsf(att_diff_angle.z) > AC_ATTITUDE_ACCEL_Y_CONTROLLER_MAX_RADSS / _p_angle_yaw.kP()) {
        att_diff_angle.z = constrain_float(wrap_PI(att_diff_angle.z), -AC_ATTITUDE_ACCEL_Y_CONTROLLER_MAX_RADSS / _p_angle_yaw.kP(), AC_ATTITUDE_ACCEL_Y_CONTROLLER_MAX_RADSS / _p_angle_yaw.kP());
        yaw_vec_correction_quat.from_axis_angle(Vector3f(0.0f, 0.0f, att_diff_angle.z));
        att_to_quat = att_from_quat * thrust_vec_correction_quat * yaw_vec_correction_quat;
    }
}

算法步骤:
1.通过对齐 当前机体坐标系 z 轴 和 期望机体坐标系 z 轴 得到 tilt 误差
2.把 tilt 误差 转到 地理系 或者 转到 机体系
3.总误差 - tilt 误差 = torsion 误差
4.限制 torsion 误差 可以使用「限幅」方法 或者「衰减」方法
5.把限制后的 torsion 和 tilt 组合成新的总误差

注:

  1. ArduPilot代码中,att_to_thrust_vec 和 att_from_thrust_vec 均是在NED坐标系下,所以采用轴角法得到的tilt是在NED坐标系下,算法中将其旋转到 机体坐标系下进行运算。
  2. 矩阵乘法在满足算法步骤3的前提下,也要满足坐标系的可以转换前提。针对步骤3,我们设定中间步骤为tb1,可以得到如下的转换关系 (cb->tb1)’ 乘以(N->cb)’ 乘以 (N->tb) ,结果为tb1->tb。上述转换关系需要好好理解。

然后我将代码转换成Matlab进行仿真。
其中蓝色是中间姿态,红色是目标姿态,绿色为初始姿态。
在这里插入图片描述

在这里插入图片描述
结论为:无论初始姿态和最终姿态给什么样子,均能够较好的跟踪上,但是第一步并没有较好的完成对齐。且ArduPilot飞控在本部分存在问题,最后将第一次选择的z方向给省略了,用第二次的torsion的z来表示最后的Z值,这会存在一定程度上的误差。

//总误差: att_from_quat.inverse()*att_to_quat
//当前姿态的逆*期望姿态 就是 期望姿态-当前姿态的意思 得到的是总误差
//旋转误差: 倾斜误差的逆*总误差 就是 总误差减去倾斜误差的意思 得到的是 剩余的旋转误差                                       
//向量的旋转需要使用四元数旋转公式,四元数的连乘类似于旋转矩阵 可以约去坐标系

注:矩阵乘法有结合律。所以上述注释放在程序中没有问题。
附:无人机干货铺的Matlab代码,我这里仅仅保留PX4的,用来做对比用,后面也会自己做一份。

global x_axis_last;
global y_axis_last;
global z_axis_last;
x_axis_last=[1,0,0];
y_axis_last=[0,1,0];
z_axis_last=[0,0,1];
NED=[0 0 0];
Cur_angle=[10 10 10];    
Des_angle=[45 45 45];
%根据 roll pitch yaw 的外环增益 计算 权重
attitude_gain =[6.5 , 6.5 , 2.8];
roll_pitch_gain = (attitude_gain(1) + attitude_gain(2)) / 2.0;
yaw_w = constrain_float(attitude_gain(3) / roll_pitch_gain, 0.0, 1.0)
attitude_gain(3) = roll_pitch_gain;

%弧度
cur_angle_radian=[Cur_angle(1)*pi/180,Cur_angle(2)*pi/180,Cur_angle(3)*pi/180] ;  
des_angle_radian=[Des_angle(1)*pi/180,Des_angle(2)*pi/180,Des_angle(3)*pi/180];

%这个画图画的是在以N系为基础的旋转 即 地理坐标系的旋转
plot_body_cur_axis_in_NED([0,0,0],NED,['k','k','k'],'-',2) 
plot_body_cur_axis_in_NED([0,0,0],Cur_angle,['g','g','g'],'-',1) 
plot_body_cur_axis_in_NED([0,0,0],Des_angle,['r','r','r'],'-',1) 

%当前姿态 Ccur->N
cur_dcm= euler2dcm(cur_angle_radian(1),cur_angle_radian(2),cur_angle_radian(3));
q=euler2qual( cur_angle_radian(1),cur_angle_radian(2),cur_angle_radian(3) );
q=Q_normalize(q);
%当前姿态的旋转矩阵 取出z轴列(z轴单位向量) Zcur(N;)
e_z=cur_dcm*[0;0;1];
%期望姿态 Ctar->N
des_dcm = euler2dcm( des_angle_radian(1),des_angle_radian(2),des_angle_radian(3) );
qd=euler2qual( des_angle_radian(1),des_angle_radian(2),des_angle_radian(3) );
qd=Q_normalize(qd);
%期望姿态的旋转矩阵 取出z轴列(z轴单位向量) Ztar(N)
e_z_d=des_dcm*[0;0;1];

%PX4_Q(当前,期望) 得到的是 当前->期望的旋转 Q cur-> tb1
qd_red=PX4_Q(e_z,e_z_d);%%增量 可以加在任何坐标系下 可以等同于机体坐标系下想旋转  

if (abs(qd_red(1)) > (1 - 1e-5) || abs(qd_red(2)) > (1 - 1e-5))         
    qd_red = qd;
else 
    %把转轴 转到N系
    %q(cb->n)^-1 qd_red q(cb->n)
    %把 误差 转到 N系
    %q(cb->n) *  q(cb->n)^-1 qd_red q(cb->n)  = qd_red q(cb->n) 
    %得到地理系下的误差 Qtb1-N 
    qd_red = Q_multipli(qd_red , q);  %
    % 画出Qtb1在地理系下的坐标
    %plot_body_tar_axis_in_NED_Q([0,0,0],NED ,qd_red ,['c','c','c'],':',5) 
end

%Qtar->tb1 = Qtb1->n^-1 * Qtar->n
q_mix = Q_multipli( Q_INV(qd_red) , qd );
% 画出 目标 位置 减去 旋转误差 在地理系下的坐标
%plot_body_tar_axis_in_NED_Q([0,0,0],Des_angle ,Q_INV(q_mix) ,['r','r','r'],'--',3) 

%优化旋转误差
q_mix = q_mix*signNoZero(q_mix(1));
q_mix(1) = constrain_float(q_mix(1), -1, 1);
q_mix(4) = constrain_float(q_mix(4), -1, 1);

%限制旋转误差  组合成新的旋转 qd= Qtb1->N * Qtar->tb1 = Qtar->N
qd = Q_multipli( qd_red , [cos(yaw_w * acos(q_mix(1))), 0, 0, sin(yaw_w * asin(q_mix(4)))]);

%新的 Qtar->cur
qe = Q_multipli( Q_INV(q) , qd) ;

plot_body_tar_axis_in_NED_Q([0,0,0],Cur_angle ,qe ,['b','b','b'],'--',3) 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ArduPilot姿态控制方法解析---(倾转分离) 的相关文章

  • GEM TSU Interface Details and IEEE 1588 Support

    GEM TSU Interface Details and IEEE 1588 Support Chapter 1 IntroductionChapter 2 GEM TSU Clock SourcePCW 中的 GEM TSU 时钟源选择
  • ZYNQ UltraScale+ MPSoC Linux + ThreadX AMP玩法

    ZYNQ UltraScale 43 MPSoC Linux 43 ThreadX AMP玩法 ZYNQ UltraScale 43 MPSoC与ZYNQ 7000架构比较目标 一 创建Linux1 修改kernel2 修改设备树编译 am
  • Zipwire

    文章目录 Chapter 55 Zipwire55 1 Chip specific Zipwire information52 2 Overview55 3 Introduction55 4 Zipwire Block Diagram55
  • arm启动过程详解

    ARM芯片的启动程序的分析和总结 2009 02 04 14 35 26 标签 xff1a 杂谈 分类 xff1a ARM 1 综述 xff1a 目前大多基于ARM芯片的系统都是一个比较复杂的片上系统 xff0c 多数硬件模块都是可配置的
  • 数据读写的乒乓操作

    数据读写的乒乓操作 文中一部分从其他博客中学习到 xff0c 加入了自己实际应用的过程 在重要数据的解帧与处理过程中 xff0c 为了确保数据的实时性与可靠性 xff0c 我们一般对收到的数据存储到芯片的RAM或Flash xff08 掉电
  • 生命在于学习——Linux提权

    本篇文章仅用于学习记录 xff0c 不得用于其他违规用途 一 内核漏洞提权 1 常规查找 查看内核版本信息 uname span class token operator span a uname span class token oper
  • 前入式JUC常用类源码分析

    CountDownLatch span class token keyword public span span class token keyword class span span class token class name Coun
  • dockerfile中的命令:run, cmd, entrypoint, copy和add

    总结一下 xff0c run 可以有多个 xff0c cmd 和entrypoint 只能有一个 xff08 常用来跑app xff09 cmd 可以被docker 指令overwrite xff0c entrypoint不可以 此命令会在
  • Qt类关系一览表

    Qt类关系一览表
  • ros之pid

    PID口诀 参数整定找最佳 xff0c 从小到 大顺序查 先是比例后积分 xff0c 最后再把微分加 曲线振荡很频繁 xff0c 比例度盘要放大 曲线漂浮绕大湾 xff0c 比例度盘往小扳 曲线偏离回复慢 xff0c 积分时间往下降 曲线波
  • 导 Kinect2库,opencv库,pcl库

    导 Kinect2库 opencv库 pcl库 Kinect2驱动安装 https blog csdn net qq 15204179 article details 107706758 ndfreenect2 cmake Kinect2
  • ros中订阅/map话题,获取地图尺寸,获取机器人原点origin,获取地图分辨率resolution (c++,python,waitForMessage,wait_for_message)

    ros中订阅 map话题 获取地图尺寸 获取机器人原点position 获取地图分辨率resolution 1 nbsp include lt ros ros h gt include lt nav msgs OccupancyGrid h
  • geometry_msgs::TransformStamped与geometry_msgs::PoseStamped消息互转

    geometry msgs TransformStamped与geometry msgs PoseStamped消息互转 下面是一个将geometry msgs TransformStamped转换为geometry msgs PoseSt
  • ros中获取小车当前位置

    ros中获取小车当前位置 一 tf2获取小车当前位置 xff1a include 34 tf2 ros transform listener h 34 include 34 tf2 ros buffer h 34 include 34 tf
  • 记一次被正点原子坑了的经历

    被正点原子坑 xff0c 不是买了他们的板子 xff0c 而是用了他们的一个Lwip的配置文件lwipopt h文件 xff0c 事情是这样的 xff0c 我现在开发的这个项目用lwip的库 xff0c 版本是1 4 1 xff0c 上手的
  • SecureCRT连接超时设置-The semaphore timeout period has expired

    设置 设置后就不会出现短时间没有操作连接超时的局面 xff0c 要求需要再次重连的问题 FR 徐海涛 xff08 hunk Xu xff09 QQ技术交流群 xff1a 386476712
  • CANoe-Trace-CAN Error

    CANoe软件CAN Error排查经验案例 系统模拟充电桩 xff0c 和实车车辆通过枪线连接 xff0c 启动充电流程 xff0c 在Trace界面看到CAN1有故障 xff0c CAN Error xff0c 并且是TxError 原
  • Makefile的使用

    Makefile规则 规则解析 一个简单的 Makefile 文件包含一系列的 规则 xff0c 样式如下 xff1a 目标 target 依赖 prerequiries lt tab gt 命令 command 目标 xff08 targ
  • STM32用一个定时器输出多路不同频率及占空比的PWM(输出比较模式)

    我们使用STM32输出PWM时会使用定时器的PWM输出模式来进行生成 xff0c 但是这样子生成PWM是有局限的 xff0c 它只能生成四路频率相同的PWM xff0c 当你设定了TIMx PSC xff08 预分频寄存器 xff09 和T
  • 虚拟机的安装与开发环境的配置

    前言 最近开始接触嵌入式 xff0c 之前也就学过C语言 xff0c 但是都是是非常基础的一些知识 知识最高峰的时期就是为了过国家计算机二级C 而一通猛学 下面就放入我最近学习笔记的内容 一 Linux开发环境的搭建 下载VirtualBo

随机推荐

  • C语言:基础知识

    基础C语言 基本知识 如何在虚拟机上写代码 1 打开终端 xff08 Ctrl xff0b Alt xff0b t xff09 xff0c mkdir day01 创建目录 cd 目录名 2 vim file c xff0c 进入vim文本
  • 详解scanf、gets、getchar和getch 使用及其原理。

    scanf gets getchar和getch 使用及其原理 一 说在最前 xff1a 回车及换行 概念 在计算机还没有出现之前 xff0c 有一种叫做电传打字机 在电传打字机打字时 xff0c 在每行后面加两个表示结束的字符 xff0c
  • 小项目:学生成绩管理系统

    学生成绩管理系统 前言 xff1a 此项目不具有商业价值 xff0c 旨在总结C语言所学知识点及各知识点在项目中的运用 xff0c 主要锻炼编码能力 xff0c 程序设计能力 xff0c 对业务逻辑的理解能力 文章目录 学生成绩管理系统系统
  • C语言:指针学习以及理解

    C语言 xff1a 关于指针学习以及理解 文章目录 C语言 xff1a 关于指针学习以及理解一 什么是指针二 为什么使用指针 什么情况下使用指针三 如何使用指针四 使用指针要注意的问题五 指针与数组的关系六 指针的运算七 指针与const配
  • 二叉树的性质及计算式

    二叉树的性质 1 一般二叉树的性质 2 完全二叉树的性质 3 满二叉树性质
  • 消息队列函数(msgget、msgctl、msgsnd、msgrcv)详细说明

    消息队列函数由msgget msgctl msgsnd msgrcv四个函数组成 下面的表格列出了这四个函数的函数原型及其具体说明 1 msgget函数原型 msgget 得到消息队列标识符或创建一个消息队列对象 所需头文件 include
  • 什么是C++

    什么是C 43 43 C 43 43 是一种使用广泛的计算机程序设计语言 它是一种通用程序设计语言 xff0c 支持多重编程模式 xff0c 例如过程化程序设计 数据抽象 面向对象程序设计 泛型程序设计和设计模式等 比雅尼 斯特劳斯特鲁普博
  • 小项目:网络版ATM(C)

    网络版ATM 设计结构体 span class token macro property span class token directive keyword ifndef span STRUCT H span span class tok
  • c++中 new的使用方法

    c 43 43 中 xff0c new的用法很灵活 xff0c 这里进行了简单的总结 1 new 分配这种类型的一个大小的内存空间 并以括号中的值来初始化这个变量 2 new 分配这种类型的n个大小的内存空间 并用默认构造函数来初始化这些变
  • C中表达式、语句、代码块

    表达式 Expression 表达式 61 运算符 43 操作数 表达式必须有一个执行结果 xff0c 这个结果必须是一个值 xff0c 例如 3 4 43 5 的结果 17 xff0c a 61 c 61 d 61 10 的结果是 10
  • #ifndef #define #endif 防止头文件被重复引用

    想必很多人都看过 头文件中的 ifndef define endif 防止该头文件被重复引用 但是是否能理解 被重复引用 是什么意思 是不能在不同的两个文件中使用include来包含这个头文件吗 xff1f 如果头文件被重复引用了 xff0
  • C/C++报错:全局变量重定义或是多次定义

    很多人可能直接把全局变量写进 h文件 xff0c 然后用多个文件包含这个头文件 xff0c 编译时就会报错 xff1a 变量重定义 头文件的作用就是要给外部提供接口使用的 xff0c 所以请记住 xff0c 只在 h中做声明 xff0c 在
  • 字符串查找函数:strchr、strrchr、strchrnul、strstr、strrstr

    strchr char strchr const char str int ch 功能 xff1a 寻找字符串中某字符第一次出现的位置 参数 str 要查找的字符串或字符串指针 ch 要查找的字符 返回值 成功返回一个指向在字符串str中第
  • PX4 代码中 position_estimator_inav(互补滤波)理解

    Local position estimator 为卡尔曼滤波估计 position estimator inav 需要解决三个问题 xff1a 如何由加速度进行速度和位置估计 xff1b 加速度偏差的计算 xff1b 如何使用融合过程 借
  • ArduPilot-sitl仿真-Mission Planner联合显示

    ArduPilot sitl仿真 Mission Planner联合显示 To start the simulator first change directory to the vehicle directory For example
  • Ros_PX4_Mavros从零入门--哪些坑

    写在前面的话 xff1a 一定要确保mavros安装成功 我在安装mavros的时候采用的是官网默认的安装方式 xff0c 期间一定有Error出现 xff0c 没有成功 xff0c 导致后面需要补充安装一些东西 验证是否安装成功 xff0
  • PX4-mavros之Roslaunch使用 及流程介绍

    首先在Ros工程目录src下 xff0c 创建新的功能包 使用命令 catkin create pkg px4 launch 注意此处不需要添加任何依赖了 然后建立一个launch文件夹 xff0c 如下图所示 2 改写launch文件即可
  • 聊一聊那些应该了解的大佬(飞控,人工智能方向)

    写在前面的话 xff1a 以下内容与图片大多来自于网络 xff0c 如有侵权 xff0c 请告知我进行修改 部分评论仅为个人观点 xff0c 无人机方面大牛很多 xff0c 无法一一评说 xff0c 在此随意发挥 也欢迎各位看官补充完善 R
  • ArduPilot+mavros+gazebo+QGC联合仿真初体验

    首先给出最终效果图 xff1a 实现内容与PX4官网代码功能类似 xff0c 四旋翼飞机自动起飞至2 5m高度 xff0c 悬停一定时间 xff0c 然后自主降落 记录如下几个需要注意的地方 xff1a 一共使用到三个文件夹 xff0c 其
  • ArduPilot姿态控制方法解析---(倾转分离)

    先给出一些预备知识 xff1a 欧拉角 xff1a 即所谓的Roll Pitch Yaw 而事实上 xff0c 根据其定义 xff0c 欧拉角的具有不同的表示 存在先绕哪个轴 xff0c 后绕哪个轴转的差别 xff08 将旋转分解成绕三个轴