尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

旋转矩阵左右乘本质解析:固定系与动系视角下的机器人学与三维视觉核心概念

旋转矩阵左右乘本质解析:固定系与动系视角下的机器人学与三维视觉核心概念 1. 项目概述从“绕晕了”到“想通了”如果你正在学习机器人学、计算机视觉、三维图形学或者无人机控制那么“旋转矩阵”这个概念你大概率是绕不过去的。而当你第一次看到关于旋转矩阵“左乘”和“右乘”的讨论时那种感觉就像是在看一本天书——每个字都认识连在一起就不知道在说什么了。更让人头疼的是不同的教材、不同的代码库、不同的坐标系约定对左右乘的解释可能截然不同甚至互相矛盾。我曾经也在这个问题上卡了很久调试代码时因为乘反了顺序导致机器人姿态诡异、三维模型扭曲浪费了大量时间在排查这种“低级错误”上。所以这篇内容的目的非常直接彻底讲清楚旋转矩阵到底是什么以及更重要的左乘和右乘到底在什么场景下代表什么意义让你以后看到相关公式和代码时能立刻反应出来它的物理含义而不是死记硬背。这不是一篇数学公式的简单罗列而是结合我多年在机器人SLAM和三维重建中的实际踩坑经验帮你建立起一个清晰、牢固的直觉。我们会从最基本的点旋转开始一步步推导到坐标系变换最后深入到固定坐标系与动坐标系视角下的左右乘本质。相信我看完之后你会对“旋转”有一个全新的、通透的理解。2. 旋转矩阵的本质不仅仅是“旋转”在深入左右乘之前我们必须夯实基础真正理解旋转矩阵是什么。很多初学者会把它简单理解为一个“让点转起来”的数学工具这没错但太片面了限制了后续的理解。2.1 作为“动作”对向量的旋转操作这是最直观的理解。在三维空间中我们有一个点或者说一个向量v [x, y, z]^T。我们想让它绕某个轴比如Z轴旋转 θ 角度。旋转矩阵R就是一个 3x3 的矩阵左乘这个向量得到旋转后的新向量vv R * v这个R的具体形式比如绕Z轴旋转是R_z(θ) [ cosθ, -sinθ, 0; sinθ, cosθ, 0; 0, 0, 1 ]你可以把它想象成一个“函数”或“操作符”输入一个旧向量输出一个旋转后的新向量。在这个视角下旋转矩阵描述的是一个“动作”发生在同一个坐标系下。向量v和v的坐标值都是相对于同一个“世界坐标系”或“参考坐标系”来描述的。注意这里有一个关键细节旋转矩阵R是一个正交矩阵即R^T * R I单位矩阵且其行列式det(R) 1。这保证了旋转操作只改变向量的方向不改变其长度。如果行列式为-1那就是包含了镜像反射操作不是纯旋转了。在后续讨论中我们默认R都是这种正规的旋转矩阵。2.2 作为“状态”坐标系姿态的描述这是更强大也是更容易引发混淆的理解。想象一下我们不仅有一个点还有两个坐标系一个固定的“世界坐标系”{W}和一个固连在物体比如机器人、相机上的“物体坐标系”{B}。现在我不再直接旋转一个点而是问物体坐标系{B}相对于世界坐标系{W}的姿态是什么答案就是一个旋转矩阵R_wb。这个矩阵的每一列具有极其清晰的物理意义它们就是物体坐标系{B}的三个坐标轴X_b, Y_b, Z_b的单位向量在世界坐标系{W}下的坐标值。举个例子假设{B}的 X 轴指向世界系的 Y 方向Y 轴指向世界系的负 X 方向Z 轴与世界系 Z 轴相同。那么X_b在{W}下的坐标是[0, 1, 0]^TY_b在{W}下的坐标是[-1, 0, 0]^TZ_b在{W}下的坐标是[0, 0, 1]^T把这三个列向量并排放在一起就得到了旋转矩阵R_wbR_wb [ 0, -1, 0; 1, 0, 0; 0, 0, 1 ]这个矩阵描述了{B}相对于{W}的“状态”。在这个视角下旋转矩阵是一个“描述符”或“量度”它建立了两个坐标系之间方向的关系。2.3 两种视角的统一矩阵的“双向解读”神奇的地方在于同一个旋转矩阵R_wb可以完成两种工作坐标变换已知一个点p在{B}系下的坐标为p_b那么它在{W}系下的坐标p_w为p_w R_wb * p_b。这里R_wb作为“动作”将{B}系下的描述 “旋转”到了{W}系下。向量变换已知一个向量v在{W}系下的坐标为v_w那么它在{B}系下看到的坐标v_b为v_b R_wb^T * v_w R_bw * v_w。因为R_wb的逆就是它的转置R_bw这个逆矩阵描述的正是{W}相对于{B}的姿态。这里已经埋下了左右乘困惑的种子当我们说“用旋转矩阵乘一个向量”时必须明确这个矩阵代表的是哪个坐标系相对于哪个坐标系的姿态以及这个向量是在哪个坐标系下描述的。混淆这一点就会导致左右乘的误用。3. 左右乘的终极意义固定系与动系视角之争好了基础打牢了现在进入核心战场左乘和右乘。网络上大多数让人困惑的解释根源在于没有区分清楚两种根本不同的“旋转叠加”场景。3.1 场景一在固定坐标系下连续旋转左乘法则假设我们有一个向量v初始时在世界系{W}下。我们现在要对他进行两次旋转先绕{W}系的 Z 轴转 α 角再绕旋转后的{W}系的 Y 轴注意这个Y轴方向在第一次旋转后并没有改变因为坐标系是固定的转 β 角。从操作顺序上看是R_y(β)这个动作发生在R_z(α)之后。那么最终向量v应该是v R_y(β) * ( R_z(α) * v ) ( R_y(β) * R_z(α) ) * v根据矩阵乘法的结合律我们可以先计算两个旋转矩阵的乘积R R_y(β) * R_z(α)然后用这个合成的R左乘v。这里的关键是新旋转矩阵R_y(β)左乘在已有的旋转结果R_z(α)*v上或者说左乘在复合旋转矩阵的左侧。物理意义在固定坐标系下进行连续旋转旋转矩阵按发生的时间顺序从左到右依次左乘。你可以想象成每一次旋转指令都是相对于最原始的、静止不动的那个固定坐标系发出的。实操心得在机器人学中当你说“机械臂先绕世界系的X轴转30度再绕世界系的Z轴转45度”时你就是在描述固定系旋转对应的变换矩阵就是R R_z(45°) * R_x(30°)注意阅读顺序是从右向左发生。很多关于欧拉角如航空航天常用的Z-Y-X顺序的公式推导默认就是这种固定轴旋转。3.2 场景二在动坐标系下连续旋转右乘法则这是更容易混淆但也更符合直觉的场景。想象你的手机动坐标系{B}初始姿态和世界系{W}对齐。现在你执行两个动作先绕手机自身的 Z 轴也就是你竖起手机时指向天空的轴旋转 α 角比如横屏变竖屏。再绕旋转后的手机自身的 Y 轴也就是你现在手机侧面的轴旋转 β 角比如前后俯仰。注意第二步旋转所绕的 Y 轴是经过第一步旋转之后手机自身的新 Y 轴而不是世界系的 Y 轴。如何用数学描述设初始时手机系{B}相对于世界系{W}的旋转为R_wb0 I单位矩阵。第一次旋转绕动系{B}自身的 Z 轴转 α。这个旋转矩阵记为R_b1表示在动系{B}下发生的旋转。旋转后新姿态R_wb1 R_wb0 * R_b1 I * R_b1 R_b1。这里R_b1右乘在了R_wb0上。第二次旋转绕新的动系{B1}自身的 Y 轴转 β。这个旋转矩阵记为R_b2。旋转后最终姿态R_wb2 R_wb1 * R_b2 (R_wb0 * R_b1) * R_b2。因此最终描述手机姿态的矩阵是R_wb_final R_b1 * R_b2。这里的关键是新的旋转矩阵R_b2右乘在了当前描述动系姿态的矩阵R_wb1上。物理意义在动坐标系下进行连续旋转旋转矩阵按发生的时间顺序从右到左依次右乘在连乘式中表现为从左到右但每个新矩阵都是乘在右边。每一次旋转指令都是相对于“当前时刻”的、随着物体一起运动的那个坐标系发出的。实操心得这是更符合人体运动学和大多数机器人关节链式结构的思考方式。比如一个机械臂第二个关节的旋转是相对于第一个关节旋转后的坐标系进行的。在变换矩阵T包含旋转和平移的表示中我们通常用T T1 * T2 * T3来表示从基座到末端执行器的变换其中每个T_i都是相对于上一个连杆坐标系的这就是典型的右乘链式法则。3.3 左右乘的对照与记忆诀窍为了更清晰我们用一个表格来对比特性固定坐标系旋转 (Fixed-axis)动坐标系旋转 (Current-axis)参照系始终相对于最初的、静止的固定坐标系。始终相对于上一次旋转后的、运动着的物体自身坐标系。矩阵乘法顺序左乘R_final R_last * ... * R_2 * R_first右乘R_final R_first * R_2 * ... * R_last阅读顺序从右向左发生。R_first先发生R_last最后发生。从左向右发生。R_first先发生R_last最后发生。欧拉角常用领域经典力学、航空航天外旋。机器人学、计算机图形学内旋。直观比喻像舞台上的追光灯。灯固定坐标系不动指挥员依次发出“绕灯自身的X轴转”、“绕灯自身的Y轴转”的指令。像你自己转身。你先绕自己的脚Z轴转再绕你新的身体侧面Y轴鞠躬。第二次旋转的轴随着第一次旋转而改变了。一个强大的记忆诀窍我自创的 把描述物体当前姿态的矩阵记作R_wb世界系到物体系。如果有一个新的旋转R_new是相对于世界系固定系定义的那么更新姿态为R_wb R_new * R_wb(新矩阵左乘旧姿态)。如果有一个新的旋转R_new是相对于物体自身系动系定义的那么更新姿态为R_wb R_wb * R_new(新矩阵右乘旧姿态)。核心就一句话新旋转相对于哪个坐标系就把它的矩阵乘在描述该坐标系姿态的矩阵的哪一边。相对于谁就靠近谁。4. 在具体技术中的体现与避坑指南理解了理论我们来看看在具体领域里这些概念是如何体现的以及有哪些“坑”。4.1 机器人学与SLAM中的位姿表示在机器人同时定位与建图SLAM和机器人运动学中我们常用变换矩阵T4x4矩阵包含旋转R和平移t来表示位姿。T [ R, t; [0, 0, 0, 1] ]假设有坐标系{A},{B},{C}。T_ab表示从{A}到{B}的变换。变换的链式法则T_ac T_ab * T_bc。这意味着先从{A}到{B}再从{B}到{C}。这是标准的右乘链因为T_bc是相对于{B}系描述的它右乘在T_ab上。这是机器人学中的绝对主流约定。姿态更新动系视角机器人当前姿态为T_wb。它根据自身传感器如IMU、轮式编码器测量到一个小位移旋转R_b和平移t_b都是在机器人自身坐标系{B}下测量的。那么更新后的世界系姿态为T_wb T_wb * T_bb其中T_bb [R_b, t_b; 0, 1]。 这完美符合“动系旋转右乘”的规则。T_bb是相对于动系{B}的所以它右乘。踩坑实录1坐标系变换方向最经典的错误是混淆T_ab和T_ba。T_ab能将{B}系下的点p_b变换到{A}系p_a T_ab * p_b。它的逆T_ba T_ab^{-1}才是反方向的变换。很多开源库的接口设计不同务必在调用前阅读文档明确其是“从A到B”还是“从B到A”。一个快速检查方法想象{B}与{A}重合然后将{B}沿{A}的X轴正方向移动1米。那么T_ab中的平移向量t应该是[1, 0, 0]^T。用这个简单例子去验证你对库函数的理解。4.2 计算机图形学与游戏引擎在OpenGL、Unity、Unreal等引擎中变换同样至关重要但“习惯”可能不同。局部坐标与世界坐标一个游戏模型的变换通常由其“局部变换矩阵”定义相对于父节点。最终的世界变换是通过将局部矩阵右乘或左乘取决于引擎父节点的世界矩阵得到的。这本质上也是链式变换。旋转顺序与万向节死锁当使用欧拉角例如Pitch, Yaw, Roll时必须明确其旋转顺序和是固定轴还是动轴。Unity默认是Z-X-Y顺序对应Roll, Pitch, Yaw且是相对于局部坐标系动系的旋转。这意味着在代码中你设置transform.Rotate(x, y, z)时引擎内部是以你指定的顺序进行右乘。而万向节死锁正是动系旋转内旋无法避免的数学缺陷。矩阵乘法顺序在着色器中我们常用列向量v变换是M * v。对于多个变换例如先缩放S再旋转R最后平移T世界变换矩阵通常是M T * R * S。注意这个顺序因为v T * (R * (S * v)) (T * R * S) * v。缩放最先应用于模型所以它最靠近向量v在矩阵连乘中最右边平移最后应用所以在最左边。这和旋转的左右乘概念不同是变换应用顺序的体现。踩坑实录2API的默认约定不同图形API和引擎的默认坐标系左手系/右手系、矩阵行主序/列主序、旋转正方向都可能有差异。例如DirectX常用左手系而OpenGL传统上用右手系。在将一个算法从论文实现到代码时第一件事就是进行坐标系和旋转约定的转换否则模型会显示为镜像或旋转方向相反。一个务实的做法是在代码中定义一个清晰的“世界坐标系”例如X右Y上Z前所有外部数据模型、传感器数据都先转换到这个约定下再进行处理。4.3 实践中的排查技巧与代码示例当你的机器人走位飘忽、3D模型旋转错乱时如何快速定位是不是左右乘问题构造最小测试用例不要用复杂场景调试。写一个简单的测试定义一个向量v [1, 0, 0]^T。进行两次非常简单的旋转比如先绕Z轴转90度再绕你认为的Y轴转90度。用手算或心里推导出正确结果然后与你的程序输出对比。固定系旋转左乘R_final R_y(90) * R_z(90)。计算后对v应用。动系旋转右乘R_final R_z(90) * R_y(90)。计算后对v应用。 看看你的程序输出匹配哪一种。这能立刻告诉你代码的默认约定。检查库函数文档像Eigen、ROS tf、numpy等常用库。以Eigen为例// Eigen 中角度轴、四元数、旋转矩阵的转换和应用非常清晰 #include Eigen/Geometry Eigen::Vector3d v(1, 0, 0); // 创建一个绕Z轴旋转90度的旋转矩阵固定轴定义 Eigen::Matrix3d R_z Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d::UnitZ()).toRotationMatrix(); // 创建一个绕Y轴旋转90度的旋转矩阵 Eigen::Matrix3d R_y Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d::UnitY()).toRotationMatrix(); // 模拟固定系旋转左乘先Z后Y相对于固定坐标系 Eigen::Vector3d v_fixed R_y * R_z * v; // 新旋转左乘 std::cout Fixed-axis result: v_fixed.transpose() std::endl; // 模拟动系旋转右乘先Z后Y相对于自身坐标系 Eigen::Vector3d v_current R_z * R_y * v; // 注意这里v的变换顺序看起来和上面一样但含义不同。 // 更准确的动系模拟假设初始旋转R_z然后绕新Y轴转。这等价于 R_final R_z * R_y。 // 因为R_y是定义在第一次旋转后的动系上的在连乘中它放在右边。 std::cout Current-axis result: v_current.transpose() std::endl;运行这段代码观察两个结果的巨大差异。这能帮你建立直觉。绘制坐标系辅助理解在纸上画图或者使用简单的绘图工具如Matplotlib的3D轴。每进行一次旋转就画出新的坐标系。这是解决复杂链式变换最可靠的方法没有之一。利用旋转矩阵的正交性验证如果你计算或得到一个旋转矩阵R计算一下R * R.transpose()是否非常接近单位矩阵以及det(R)是否接近1。如果不是说明你的矩阵计算过程可能已经因为顺序错误而破坏了旋转矩阵的性质。5. 从矩阵到四元数概念的延伸在现代应用中尤其是需要平滑插值如动画或避免万向节死锁的场合四元数比旋转矩阵和欧拉角更常用。但左右乘的概念依然存在。一个单位四元数q也可以表示旋转。对于向量v其旋转为v q * v * q^{-1}这里*是四元数乘法。在表示坐标系变换时同样有用四元数q_wb表示物体系{B}相对于世界系{W}的旋转。有一个相对于世界系的旋转q_new_fixed则更新姿态为q_wb q_new_fixed * q_wb(左乘)。有一个相对于物体自身系的旋转q_new_current则更新姿态为q_wb q_wb * q_new_current(右乘)。四元数乘法的顺序约定与旋转矩阵完全一致。很多四元数库如Eigen的*运算符重载就遵循这个规则。因此只要你理解了矩阵框架下的左右乘切换到四元数只是换了一种数学工具物理概念是相通的。最后我个人最深刻的体会是不要试图去死记硬背“左乘是固定轴右乘是动轴”这样的口诀。一定要回到物理场景和坐标系定义上去理解。每次遇到旋转叠加的问题就问自己两个问题1. 这个新的旋转是相对于哪个坐标系定义的2. 当前这个矩阵描述的是哪个坐标系到哪个坐标系的变换把这两个问题回答清楚乘法顺序就自然而然地确定了。把这个思维过程变成习惯旋转矩阵就不再是拦路虎而会成为你描述和理解三维空间运动的得力工具。
返回列表