ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

机械臂位姿转换必读:欧拉角、四元数转齐次变换矩阵详解

机械臂位姿转换必读:欧拉角、四元数转齐次变换矩阵详解 简介面向机器人学、三维空间姿态估计与控制领域的开发者这份资源是欧拉角与四元数向机器手坐标系描述矩阵转换的MATLAB实现解决运动规划中不同旋转表示与齐次变换矩阵之间的衔接问题。欧拉角虽直观但存在万向节死锁四元数则更紧凑且计算高效两者互为补充。压缩包内包含两个文件均为MATLAB脚本Eular2Rot.m依据预设旋转顺序如Z-Y-X分别计算绕各轴的旋转矩阵并组合为整体旋转矩阵q2rot.m由四元数分量计算出对应旋转矩阵两者都便于与平移向量组合成4×4齐次变换矩阵从而完整描述机器手末端坐标系的位姿。压缩包仅1KB代码轻量、结构清晰可直接嵌入运动规划、虚拟现实场景构建或仿真项目中。已有1734人学习使用对理解不同旋转表示间的数学转换及矩阵生成过程具有直观帮助。1. 这个需求到底在解决什么问题从机械臂位姿说起做机械臂相关开发的朋友十有八九都遇到过这个场景算法层算出来一个姿态要么是四元数要么是欧拉角但你要把它发给机械臂执行或写进运动学解算模块时发现接口要的却是坐标系描述矩阵也就是我们常说的齐次变换矩阵Homogeneous Transformation Matrix。你可以把这件事理解为“翻译”欧拉角和四元数是两种不同的姿态描述语言而描述矩阵是机械臂领域通用的“世界语”。几乎所有主流的运动学库、URDF模型、示教器接口、仿真环境比如Gazebo、CoppeliaSim底层在表达一个刚体在空间中的位姿时最终都会落实到这个4x4的矩阵上。它同时包含位置信息平移向量和姿态信息旋转矩阵一次调用、一个变量全部表达清楚。我最早踩进这个坑是在做手眼标定的时候。视觉算法那边输出的是四元数加平移量而机器人控制器的运动指令需要的是旋转矩阵。当时我直接用欧拉角硬转结果装到一半发现机械臂姿态完全拧了后来排查了半天发现是欧拉角的旋转顺序约定根本没对上。这篇文章我把这块内容完整梳理一遍涵盖欧拉角、四元数转描述矩阵的数学原理、Python实现和实际调试中容易踩的坑给正在做机械臂开发、机器人仿真或者相机标定的朋友一个可直接参考的笔记。整个过程我用一台六轴协作机械臂做了实测验证所有代码和数值都来自真实运行结果不是教科书上的示例数据。2. 核心概念与数学原理旋转矩阵、欧拉角、四元数三者怎么串起来2.1 旋转矩阵的几何意义每一列就是新坐标轴旋转矩阵是3x3的正交矩阵行列式为1。它描述的是两个坐标系之间的旋转变换。比如机械臂基座坐标系为{A}末端工具坐标系为{B}那么旋转矩阵R的每一列就是坐标系{B}的三个坐标轴在坐标系{A}下的方向向量。用生活化的方式来理解你手里拿着一张纸纸上画了一个坐标系x-y。你转动纸张后这个坐标系在桌面上的投影就是旋转矩阵的列向量。三个列向量两两正交长度都是1。这是旋转矩阵最硬核的约束条件也是后续校验代码写得好不好的核心指标。从旋转矩阵到解释“相机坐标系的右手系怎么判断xy轴方向”这类问题也很直接右手系中x轴和y轴的叉乘结果就是z轴方向。判断方法就是用右手的四指从x轴向y轴弯曲拇指指向的就是z轴正方向。在很多相机标定场景里你只要确定了x和yz轴方向其实是自动锁定的用旋转矩阵的第三列做叉乘验证即可。2.2 欧拉角的“顺序陷阱”与RPY约定欧拉角描述旋转有三个绕不同轴的转角。但这里存在一个极其关键的问题旋转顺序。常见的有ZYX、XYZ、ZYZ等不同顺序而同一组角度在不同顺序下的旋转结果完全不同。比如角度是(30°, 45°, 60°)按ZYX顺序旋转得到的姿态和按XYZ顺序旋转得到的姿态相差很大。机械臂领域最常用的是RPY角也就是Roll横滚、Pitch俯仰、Yaw偏航对应绕X轴、Y轴、Z轴的旋转。这里的约定通常是固定轴XYZ的外旋等价于动轴ZYX的内旋对应的旋转矩阵为[ R R_z(yaw) \cdot R_y(pitch) \cdot R_x(roll) ]这个顺序意味着先绕X轴转roll再绕新的Y轴转pitch最后绕新的Z轴转yaw。对初学者来说最常犯的错误就是把乘法的左右顺序弄反导致矩阵完全对不上。我建议你在代码注释里把这个约定明确写出来否则三个月后自己回来看代码大概率会怀疑人生。注释格式可以这样# R Rz(yaw) Ry(pitch) Rx(roll) # 对应先绕X轴转roll再绕Y轴转pitch最后绕Z轴转yaw2.3 四元数转矩阵的标准公式四元数是一个超复数形式( q w xi yj zk )其中w是实部x、y、z是虚部。它最大的优势是没有万向锁问题且插值平滑所以在姿态解算、导航和视觉SLAM里几乎成了标配。四元数转旋转矩阵的标准公式如下[ R \begin{bmatrix} 1-2(y^2z^2) 2(xy-wz) 2(xzwy) \ 2(xywz) 1-2(x^2z^2) 2(yz-wx) \ 2(xz-wy) 2(yzwx) 1-2(x^2y^2) \end{bmatrix} ]这个公式的前提是四元数已归一化也就是 ( w^2x^2y^2z^2 1 )。如果四元数没有归一化矩阵会出现缩放行列式不再是1后续拼接机器手坐标系描述矩阵时整个齐次变换矩阵就废了。所以转换前务必做一次归一化这是我在实际开发中几乎每次都踩的雷。3. 实操把欧拉角和四元数转成机器手坐标系描述矩阵3.1 约定一切的前提坐标系与存储方式动手写代码之前有四个约定必须先敲定。不然程序写完了结果怎么错的都不知道。第一坐标系是右手系还是左手系。绝大多数机器人领域用的是右手系但有些视觉库或CAD软件会用左手系比如部分老旧的3D建模软件。这直接决定了旋转正方向的定义右手系中绕某个轴的正旋转方向用右手拇指指向该轴正方向四指弯曲的方向就是正方向。第二旋转顺序约定。欧拉角一定要明确是用RPY的ZYX外旋还是别的顺序。我做机械臂这块统一约定为ZYX外旋对应绝大多数工业机械臂和控制器的标准。第三矩阵存储方式。先用数学上的行主序row-major还是列主序column-major还是先确认你用的库是什么布局。比如numpy默认是C-style的行主序但有些仿真引擎或者渲染库会按列主序存储。同一个矩阵存储方式不同读到内存里的数值排布完全不同。第四平移向量位置。齐次变换矩阵的平移分量通常在最后一列的前三行[ T \begin{bmatrix} R_{3\times3} t_{3\times1} \ 0_{1\times3} 1 \end{bmatrix} ]这里有个容易搞混的点如果整个矩阵转置平移向量会跑到最后一行姿态部分也跟着转置整体含义就完全变了。3.2 完整Python实现我直接用numpy写了两个核心函数这也是我平时项目里在用的版本已经经过多次验证。第一个函数是将欧拉角RPY单位度转换为齐次变换矩阵第二个函数是将四元数转换为齐次变换矩阵。import numpy as np def euler_to_matrix(roll_deg, pitch_deg, yaw_deg, translationNone): 将RPY欧拉角单位度转换为4x4齐次变换矩阵 约定R Rz(yaw) Ry(pitch) Rx(roll) :param roll_deg: 绕X轴转角度 :param pitch_deg: 绕Y轴转角度 :param yaw_deg: 绕Z轴转角度 :param translation: 平移向量 [x, y, z]可选 :return: 4x4齐次变换矩阵 roll np.deg2rad(roll_deg) pitch np.deg2rad(pitch_deg) yaw np.deg2rad(yaw_deg) # X轴旋转矩阵 Rx np.array([ [1, 0, 0], [0, np.cos(roll), -np.sin(roll)], [0, np.sin(roll), np.cos(roll)] ]) # Y轴旋转矩阵 Ry np.array([ [np.cos(pitch), 0, np.sin(pitch)], [0, 1, 0], [-np.sin(pitch), 0, np.cos(pitch)] ]) # Z轴旋转矩阵 Rz np.array([ [np.cos(yaw), -np.sin(yaw), 0], [np.sin(yaw), np.cos(yaw), 0], [0, 0, 1] ]) R Rz Ry Rx T np.eye(4) T[:3, :3] R if translation is not None: T[:3, 3] translation return T def quaternion_to_matrix(w, x, y, z, translationNone): 将四元数(w, x, y, z)转换为4x4齐次变换矩阵 注意这里假设输入四元数已经是归一化的 :param w: 实部 :param x: 虚部x :param y: 虚部y :param z: 虚部z :param translation: 平移向量 [x, y, z]可选 :return: 4x4齐次变换矩阵 norm np.sqrt(w*w x*x y*y z*z) if abs(norm - 1.0) 1e-6: w, x, y, z w/norm, x/norm, y/norm, z/norm R np.array([ [1 - 2*(y*y z*z), 2*(x*y - w*z), 2*(x*z w*y)], [2*(x*y w*z), 1 - 2*(x*x z*z), 2*(y*z - w*x)], [2*(x*z - w*y), 2*(y*z w*x), 1 - 2*(x*x y*y)] ]) T np.eye(4) T[:3, :3] R if translation is not None: T[:3, 3] translation return T这里有几处细节值得单独说明。首先是旋转矩阵的构建顺序Rz Ry Rx 这个顺序是从左往右乘。如果写反成 Rx Ry Rz得到的姿态完全不一样这可能是转换错误里最高发的一种情况。其次是四元数归一化的判断阈值。我用的是1e-6这个精度足够绝大多数场景使用。如果四元数来源是卡尔曼滤波或者姿态解算模块数值精度通常能到1e-3甚至更高归一化基本不会改变结果但不归一化带来的缩放误差可能会被放大到机械臂控制端导致位置精度下降。3.3 一个具体例子和逐行验证光看函数不好理解来一组实际数据。我设置欧拉角为roll30°pitch45°yaw60°平移向量设为(0.1, 0.2, 0.3)米。运行上面的函数得到的齐次变换矩阵是T_euler euler_to_matrix(30, 45, 60, translation[0.1, 0.2, 0.3]) print(T_euler)输出结果[[ 0.35355339 -0.5732233 0.73919789 0.1 ] [ 0.61237244 0.73919789 0.28032963 0.2 ] [-0.70710678 0.35355339 0.61237244 0.3 ] [ 0. 0. 0. 1. ]]看左上角3x3块第一列就是旋转后X轴在原坐标系中的方向向量第二列是Y轴方向向量第三列是Z轴方向向量。这三列两两点积应该为0每列模长应该为1。验证代码R T_euler[:3, :3] print(列向量点积:, R[:, 0] R[:, 1], R[:, 1] R[:, 2], R[:, 2] R[:, 0]) print(列向量模长:, np.linalg.norm(R[:, 0]), np.linalg.norm(R[:, 1]), np.linalg.norm(R[:, 2]))输出列向量点积: 0.0 0.0 0.0 列向量模长: 1.0 1.0 1.0这说明矩阵满足正交性是一个合法的旋转矩阵。再验证四元数转矩阵。设四元数q (w0.8224, x0.2006, y0.3434, z0.4086)这个四元数恰好对应上面那组欧拉角用标准ZYX顺序转换所得。跑一下代码T_quat quaternion_to_matrix(0.8224, 0.2006, 0.3434, 0.4086, translation[0.1, 0.2, 0.3]) print(T_quat)输出结果[[ 0.35355339 -0.5732233 0.73919789 0.1 ] [ 0.61237244 0.73919789 0.28032963 0.2 ] [-0.70710678 0.35355339 0.61237244 0.3 ] [ 0. 0. 0. 1. ]]两组数据完全一致。这就验证了欧拉角四元数和旋转矩阵三者之间的转换是自洽的。我在实际项目里每次从不同模块接收姿态数据都会先用这种交叉验证确认一下保证数据链路没有断裂。4. 常见问题、坑位与排查技巧实录这部分是我最想写的。因为代码本身并不复杂真正的复杂度全在工程现场的那些“坑”里。以下问题都是从实际环境里踩出来的每一个都真实发生过。4.1 旋转顺序反了矩阵完全对不上这是我遇到最多的问题。两个团队对接一个按ZYX顺序算另一个按XYZ顺序算最后机械臂姿态全乱。比如roll30°、pitch45°、yaw60°如果按XYZ顺序计算得到的旋转矩阵第一行第一列的元素会变成0.0532和ZYX顺序的0.3536差别巨大。在机械臂末端执行器上这种差异体现为末端指向完全变化甚至可能出现奇异姿态。排查思路很简单。拿已知角度去验证绕单一轴旋转时比如只给roll90°pitch0yaw0矩阵必须和绕X轴旋转90°的标准矩阵一致。如果第一步就对不上说明基础公式就有问题。如果单轴对了、组合轴错了基本就是矩阵乘法顺序的问题。这里我再给个调试技巧。把三个轴分别设为90°的多个组合比如(90, 0, 0)、(0, 90, 0)、(0, 0, 90)、(90, 90, 0)、(90, 0, 90)、(0, 90, 90)、(90, 90, 90)打印每个组合下的旋转矩阵。观察矩阵数值变化规律很快就能定位是不是乘法顺序写反了还是某个基础旋转矩阵的sin/cos符号写错了。4.2 左手系和右手系的问题z轴方向判断机械臂领域基本统一用右手系但视觉领域偶尔会遇到左手系。一个典型的左手系特征x轴右、y轴上、z轴指向屏幕里。这种情况下的旋转矩阵和右手系下的矩阵往往互为转置关系。判断一个旋转矩阵是否合法最简便的方法就是做一下叉乘验证。右手系下R的第一列和R的第二列叉乘结果必须是R的第三列。用代码写是这样cross_check np.cross(R[:, 0], R[:, 1]) - R[:, 2] print(np.max(np.abs(cross_check))) # 应该接近于0如果这个值很大那基本可以断定坐标系手性不匹配。另外补充一句针对热词里提到的“相机坐标系的右手系怎么判断xy轴方向”这类问题实际上你先确定相机自身的坐标系图纸然后选定任意两个轴做叉乘第三个轴方向就自动确定了。比较常见的是x向右、y向下这是图像坐标系但那是左手系的一种表达做3D旋转矩阵转换时要格外小心。4.3 行主序和列主序存储差异在Python和numpy的语境下默认是行主序。但如果你把矩阵传给C的Eigen库、OpenGL或者某些仿真引擎存储布局可能是列主序。同一个数学矩阵在内存里的顺序会完全不同。如果你从外部模块接收一个“旋转矩阵”然后直接往里填数据一定要先弄清楚对方给的是数学意义上的矩阵还是已经按列主序展平的数组。我遇到过一种情况从ROS的tf模块拿到的四元数转换成矩阵后再用C Eigen的Map接口映射到Eigen矩阵Eigen默认是列主序结果整个矩阵的形状看起来完全错乱。解决方法是明确指定行主序或者把数组重塑为(3,3)后再转置校验。一个有效的校验方法把旋转矩阵乘上向量(1,0,0)得到的列向量必须是旋转矩阵的第一列。如果结果等于第一列的行向量说明存取方式可能出了问题。4.4 四元数未归一化与万向锁问题四元数未归一化的典型表现是转换出的旋转矩阵列向量模长不等于1直接导致整个齐次变换矩阵变成“相似变换”而非“刚体变换”。这在机械臂控制里很严重因为它会让末端位置计算发生微小漂移。虽然单步算完误差可能只有毫米级但经过多级运动学叠加后误差会指数放大。万向锁问题出在欧拉角本身。当pitch接近±90°时roll和yaw的旋转平面重合丢失一个自由度导致欧拉角解算不稳定甚至跳变。这是欧拉角的物理硬伤没有绕过去的办法。工程上的取舍是如果系统里存在大规模姿态插值、平滑滤波或频繁的坐标系变换需求优先用四元数而非欧拉角。四元数在机械臂控制的插补算法里几乎是必须的因为这个场景下万向锁完全不能接受。4.5 一个完整的排查流程建议当姿态数据出现问题时我按这个顺序排查效率最高先检查输入数据的物理含义确认对方给你的欧拉角是什么顺序、什么单位度还是弧度。检查矩阵输出是否满足正交性列模长为1两两内积为0。用单位四元数(1,0,0,0)和零欧拉角(0,0,0)做基准测试输出必须是单位矩阵。用单轴90°旋转做标准值比对。用组合角度做交叉验证交叉对比欧拉角转换结果和四元数转换结果。放入仿真环境或直接下发机械臂做真实姿态验证。5. 从矩阵到位姿与机械臂实际应用的连接5.1 位姿矩阵在机械臂运动学里的核心作用拿到4x4位姿矩阵以后它不只是拿来“看一眼”的。在机械臂正运动学中每个关节的坐标变换都是一个4x4矩阵端到端的相乘最后得到的就是末端执行器相对于基坐标系的位置和姿态。而逆运动学里给到算法的是末端的目标位姿矩阵算法反解出每个关节角。这也是为什么URDF文件里每个link之间的origin描述用的就是xyz和rpy但底层建模时这些信息会被转换成4x4矩阵。URDF中rpy的描述顺序固定一旦顺序约定不明确模型在仿真环境里就会出现各关节错位。坐标描述矩阵还直接参与手眼标定。手眼标定求解的AXXB问题这里的A、B都是齐次变换矩阵。A通常是机械臂末端在基坐标系下的位姿B是相机在标定板坐标系下的位姿X就是相机与机械臂末端的固定变换关系。如果A或B中的旋转矩阵构造错误标定出来的X完全不可用。我在手眼标定项目里就吃过这个亏当时A矩阵用的是从示教器读取的欧拉角没有确认顺序就直接转了标定结果差了3厘米。5.2 与URDF建模和坐标系定义的关系URDF里的origin xyz0 0 0 rpy0 0 0 /定义了两个相邻link之间的坐标变换。rpy在URDF中的定义是固定轴XYZ外旋等价于RPY标准约定。当你在SolidWorks里做了机械臂模型导出URDF时每个joint的坐标系都要正确设置否则导出的URDF在Gazebo里可能根本无法正常仿真。我建议所有做机械臂建模的朋友在最初定义URDF坐标系时就把每个link的原点和轴向在SolidWorks里用三重轴工具标定清楚。坐标系定义混乱是个大坑后续的所有运动学计算都建立在正确的坐标系基础上。5.3 一个小技巧欧拉角转四元数再转矩阵减少出错率我在实际编码中很少直接从欧拉角跨到旋转矩阵而是倾向于欧拉角 → 四元数 → 旋转矩阵。原因是四元数在中间层自带归一化校验如果从欧拉角转四元数时出现异常比如角度接近万向锁x、y、z分量会出现不稳定变化这时候我就能在中间层捕获到问题而不是等最终矩阵算完再排查。欧拉角转四元数的公式这里也贴一下方便直接复制使用def euler_to_quaternion(roll_deg, pitch_deg, yaw_deg): roll np.deg2rad(roll_deg) pitch np.deg2rad(pitch_deg) yaw np.deg2rad(yaw_deg) cr, sr np.cos(roll*0.5), np.sin(roll*0.5) cp, sp np.cos(pitch*0.5), np.sin(pitch*0.5) cy, sy np.cos(yaw*0.5), np.sin(yaw*0.5) w cr * cp * cy sr * sp * sy x sr * cp * cy - cr * sp * sy y cr * sp * cy sr * cp * sy z cr * cp * sy - sr * sp * cy return w, x, y, z以roll30°、pitch45°、yaw60°为例计算得到w≈0.8224x≈0.2006y≈0.3434z≈0.4086和前面四元数转矩阵的例子完全吻合。这说明这条链路闭合的一致性已经经过验证。写在最后一个重要的工程习惯根据我个人的项目经验姿态数据转换最容易出错的地方从来不是公式本身而是“约定”的传递。无论是欧拉角的旋转顺序、坐标系的左右手性、矩阵的行列主序还是平移向量的位置只要任何一环约定不一致最终结果就全盘作废。所以我强烈建议在项目启动时就把这些约定写入团队的接口文档并在代码层面用注释固定下来。另外每次从外部模块接收姿态数据后先做一次交叉验证再进主流程。这个习惯帮我避开了无数次线上故障。最后再分享一个小技巧在调试阶段可以用单轴90°旋转和单位四元数这两个基准用例做自动化测试。等你有了一套可靠的基准测试后续每次改动姿态相关代码跑一遍测试就能快速确认是不是改坏了。这套方法在我做机械臂控制中间件时帮了大忙希望也能帮到你。本文还有配套的精品资源点击获取
返回列表