
搞工业机器人调试的朋友应该都有过这种经历示教器上明明显示的是[0, 0, 90]这样的欧拉角打开 RAPID 程序一看robtarget里却是一串[0.7071, 0, 0, 0.7071]的四元数视觉系统给过来的姿态又是四元数格式要是从国外论坛抄一段转换代码转出来的角度还跟示教器对不上。ABB 机器人里姿态表示这件事看着只是格式问题实际踩起坑来非常折磨人。这篇文章想把我做 ABB 机器人视觉引导和离线编程时围绕四元数和欧拉角转换积累的实战经验一次性讲清楚先讲清楚两种表示方法的底层逻辑再给出可以直接抄走的转换代码最后把常见的姿态跳变、角度对不上等问题的排查思路整理出来。1. 为什么要折腾姿态表示ABB 里的几种“语言”1.1 程序内部存的是四元数只要修改过 RAPID 程序就会在robtarget里看到类似的声明CONST robtarget pPoint : [[652.74, 58.43, 245.10], [0.7071, 0, 0, 0.7071], [0,0,0,0], [9E09,9E09,9E09,9E09,9E09,9E09]];这里第一项[652.74, 58.43, 245.10]是 TCP 的坐标位置第二项[0.7071, 0, 0, 0.7071]就是姿态四个分量对应 ABB 定义的四元数q1, q2, q3, q4其中q1是标量部分q2, q3, q4是向量部分。这个姿态如果拿到示教器上看会显示成[0, 0, 90]意思是在 Z 轴上转了 90 度。很多人第一次看到四元数会发怵但 RAPID 程序的存储格式就是这样的你绕不开。ABB 之所以在程序底层用它是因为四元数做姿态插补、坐标系旋转时计算稳定没有欧拉角那种“万向锁”问题。所以无论你在示教器上怎么输入最终落到代码里姿态必然是四个数。1.2 示教器显示的是欧拉角ABB 示教器上“位置与姿态”界面里显示的是rx, ry, rz这种形式的欧拉角。这个显示方式有两个关键点第一ABB 用的欧拉角顺序是 ZYX也就是先绕 Z 轴旋转再绕新的 Y 轴旋转最后绕新的 X 轴旋转。很多人在这一步就栽了因为其他软件、其他机器人厂家用的顺序可能完全不同比如有的用 XYZ有的用 ZXZ顺序不一样同一组角度表达的姿态完全不一样。第二示教器上显示的 rx、ry、rz 是角度制单位范围通常是 -180 到 180。而后面你写的转换代码如果直接用math.atan2计算出来的是弧度制需要换算。这个单位问题也是转换后“对不上”的高频原因。1.3 旋转矩阵是中间验证工具四元数和欧拉角之间并不是直接硬转的最稳的做法是先转成旋转矩阵再由旋转矩阵转成另一种表示。用旋转矩阵做中间桥梁好处是每一步都可以单独验算比如把两个姿态矩阵乘起来理论上应该得到单位矩阵这样就能确认转换公式没有写错。我在实际项目里几乎不会直接背四元数转欧拉角的公式而是先在纸上把旋转矩阵写出来再用代码实现。因为网上能找到的公式符号约定差异太大直接抄很容易踩坑。后面我会给出我验证过的一个版本。2. 姿态转换前必须吃透的原理2.1 欧拉角到底绕了哪几根轴欧拉角的本质是“通过三次绕轴旋转来表达空间中任意姿态”。ABB 的 ZYX 顺序可以直观理解为先绕固定坐标系 Z 轴转一个偏航角再绕旋转后的 Y 轴转一个俯仰角最后绕再旋转后的 X 轴转一个滚转角。这其实和航空领域常用的 RPYRoll-Pitch-Yaw约定是一致的只是有的资料用固定坐标系来描述有的用运动坐标系来描述说法不同数学结果是一样的。这里要特别注意“万向锁”现象。当中间那个轴即 pitch俯仰角转到正负 90 度时第一次的 Z 轴旋转和第三次的 X 轴旋转会变成绕同一根轴于是姿态的自由度从三个变成两个欧拉角表示从“唯一确定”退化为“无穷多解”。在机器人上表现就是你只转动了一个角度示教器上另外两个角度也在跟着变非常诡异。2.2 四元数是怎么表示旋转的四元数可以理解成“用一个旋转轴加一个旋转角度来表示姿态”。比如一个单位四元数q [w, x, y, z]其中向量部分(x, y, z)是旋转轴的方向w和旋转角度θ的关系是w cos(θ/2)。之所以有半角关系是为了保证连续两次旋转可以用四元数乘法简单复合。四元数看起来是四个数但它要求模长恒等于 1也就是w² x² y² z² 1。实际计算中如果做过多次姿态累加四元数往往不会严格保持模长为 1这时候必须先做归一化否则旋转半径会逐步漂移转换出来的欧拉角误差也会越来越大。我见过不少同事调视觉程序时姿态刚开始是对的跑了几小时后慢慢偏掉最后查出来就是代码里少了归一化这一步。2.3 转换公式从哪来以 ABB 的 ZYX 顺序为例旋转矩阵可以写成R Rz(yaw) * Ry(pitch) * Rx(roll)展开后得到 3x3 矩阵其中关键元素是R[0][0] cos(yaw)*cos(pitch) R[1][0] sin(yaw)*cos(pitch) R[2][0] -sin(pitch) R[2][1] cos(pitch)*sin(roll) R[2][2] cos(pitch)*cos(roll)而单位四元数对应的旋转矩阵中对应的元素是R[0][0] 1 - 2*(y² z²) R[1][0] 2*(x*y w*z) R[2][0] 2*(x*z - w*y) R[2][1] 2*(y*z w*x) R[2][2] 1 - 2*(x² y²)两边对比就能反推出角度pitch asin(2*(w*y - x*z)) yaw atan2(2*(x*y w*z), 1 - 2*(y² z²)) roll atan2(2*(y*z w*x), 1 - 2*(x² y²))如果你在别处看到的公式和这套有符号差异不要急着怀疑数学先确认那套代码用的旋转顺序是什么。KUKA 机器人的 XYZ 欧拉角、某些视觉库的固定轴约定反解公式都会不同。2.4 为什么不直接拿欧拉角算这段时间做姿态插补时我深有体会欧拉角做线性插值会产生不自然的旋转路径。比如从姿态 A 插补到姿态 B如果拿欧拉角按分量线性插值中间过程可能在某个轴上绕很远而四元数的球面线性插值Slerp走的是旋转最短路径。所以实际项目中通常是外部输入欧拉角为了方便人看和修改转成四元数后存储在目标点中如果要做轨迹平滑中间插值计算也全部在四元数层面完成最后显示时再转回欧拉角给人看。理解了这个逻辑你就知道为什么不能抱怨“为什么要搞这么复杂”。3. 核心代码实现Python 与 RAPID 双版本3.1 用一段 Python 把四元数换成欧拉角Python 是做离线数据处理时最顺手的工具。下面这段代码我用了很久基于 ABB 的 ZYX 顺序输入 ABB 的四元数q1, q2, q3, q4输出角度制的 rx、ry、rzimport math def quaternion_to_euler_zyx(q1, q2, q3, q4): # ABB四元数定义: q1为标量, q2/q3/q4为向量 w, x, y, z q1, q2, q3, q4 # 归一化防止累积误差导致的漂移 norm math.sqrt(w*w x*x y*y z*z) if norm 1e-12: return 0.0, 0.0, 0.0 w, x, y, z w/norm, x/norm, y/norm, z/norm # 注意: 欧拉角使用ZYX顺序 # roll 绕X轴, pitch 绕Y轴, yaw 绕Z轴 # pitch 反解时做了范围裁剪防止 asin 出入超出 [-1, 1] sp 2.0 * (w*y - x*z) sp max(-1.0, min(1.0, sp)) pitch math.asin(sp) # 万向锁边界处理pitch 接近正负90度时yaw 与 roll 退化 if abs(sp) 0.999999: # 将退化自由度分配到 rollyaw 置 0 yaw 0.0 roll math.atan2(2.0*(w*x y*z), 1.0 - 2.0*(x*x y*y)) else: yaw math.atan2(2.0*(x*y w*z), 1.0 - 2.0*(y*y z*z)) roll math.atan2(2.0*(y*z w*x), 1.0 - 2.0*(x*x y*y)) # 弧度转角度方便直接与示教器对照 return math.degrees(roll), math.degrees(pitch), math.degrees(yaw)用前面提到的四元数[0.7071, 0, 0, 0.7071]试一下输出应该是(0.0, 0.0, 90.0)。我在现场验证时会先把示教器上显示的角度和这个结果对着看确认没问题再批量处理数据。3.2 反向转换欧拉角生成四元数从欧拉角转四元数是逆过程。如果你在示教器上手动调整出一个姿态想写进 RAPID 代码就可以用这段def euler_zyx_to_quaternion(rx, ry, rz): # 输入角度制欧拉角对应 ABB 的 rx/ry/rz rx math.radians(rx) ry math.radians(ry) rz math.radians(rz) cx math.cos(rx * 0.5) sx math.sin(rx * 0.5) cy math.cos(ry * 0.5) sy math.sin(ry * 0.5) cz math.cos(rz * 0.5) sz math.sin(rz * 0.5) q1 cx*cy*cz sx*sy*sz q2 sx*cy*cz - cx*sy*sz q3 cx*sy*cz sx*cy*sz q4 cx*cy*sz - sx*sy*cz # 做一个归一化保证生成的四元数模长为1 norm math.sqrt(q1*q1 q2*q2 q3*q3 q4*q4) return q1/norm, q2/norm, q3/norm, q4/norm反向验证一下刚才的(0, 0, 90)转出来应该得到[0.7071, 0, 0, 0.7071]。这种“来回往返验证”是我强烈建议养成的习惯能筛掉大量低级符号错误。3.3 在 RAPID 里原地处理有些场景需要在机器人控制器里直接做转换比如自动生成目标点或通过传感器数据动态修改目标姿态。RAPID 里当然也能写只是不如 Python 方便。下面是一个把欧拉角转成四元数的函数直接返回pose类型FUNC pose EulerToPose(num x, num y, num z, num rx, num ry, num rz) VAR num radX : rx * 3.14159265358979 / 180; VAR num radY : ry * 3.14159265358979 / 180; VAR num radZ : rz * 3.14159265358979 / 180; VAR num cx : cos(radX/2); VAR num sx : sin(radX/2); VAR num cy : cos(radY/2); VAR num sy : sin(radY/2); VAR num cz : cos(radZ/2); VAR num sz : sin(radZ/2); VAR pos origin; VAR orient q; origin : [x, y, z]; q.q1 : cx*cy*cz sx*sy*sz; q.q2 : sx*cy*cz - cx*sy*sz; q.q3 : cx*sy*cz sx*cy*sz; q.q4 : cx*cy*sz - sx*sy*cz; RETURN [origin, q]; ENDFUNC在 RAPID 中运行时需要注意变量的位姿数据类型是orient四元数字段是q1/q2/q3/q4不要写错。实际使用中我建议把这种函数放在一个独立的.sys文件里方便不同程序统一调用避免维护多份代码。3.4 精度与归一化注意事项机器人的控制器和上位机之间经常有浮点精度差异。ABB 的 robtarget 在示教器上显示小数点后三到四位但在 RAPID 内部计算精度更高。你在做转换时要注意两点第一四元数必须归一化。我在代码里都加了归一化步骤因为视觉系统输出的四元数偶尔会有细微误差直接使用的话姿态矩阵会带有缩放虽然数值上很小但累积起来会让工具坐标出现肉眼可见的偏差。第二角度范围处理。示教器显示的是 -180 到 180 度但某些转换库返回的是 0 到 360 度。写代码时最好统一规范成 -180 到 180 度否则同一姿态可能在不同软件里显示成不同的数值排查起来极其痛苦。4. 实战场景从视觉系统到机器人程序的完整流程4.1 视觉给四元数机器人要欧拉角我在做 3D 视觉引导抓取时最常见的流程是视觉系统输出物体位姿通常是以四元数形式给出旋转部分机器人需要把这些数据写入robtarget。如果直接把视觉输出的四元数粘贴到 RAPID 程序里除非姿态恰好是简单的 0 度或 90 度否则你根本没法人眼判断这个姿态对不对。所以我的处理习惯是先用 Python 把视觉给的四元数转成欧拉角打印出来跟示教器上的姿态对比确认方向没问题后再把四元数直接填进robtarget。注意我最终写入 RAPID 程序的依然是四元数因为程序内部格式就要求四元数欧拉角只是给人看的中间量。这一步看似多此一举实际上能避免很多现场调试时“机器人乱动”的惊吓。4.2 手头只有欧拉角想验证程序里的四元数另一种常见场景是程序已经写好了里面有固定目标点你想知道这个点对应的 TCP 姿态到底是什么朝向。一种快速方法是直接把四元数用 3.1 节的函数转成欧拉角再对照示教器查看。如果现场没有 Python 环境也可以在 RobotStudio 里用“同步到 RAPID”功能在虚拟示教器上直接查看目标点的姿态。有次我排查一个调用转移问题目标点从 A 坐标系转到 B 坐标系后程序里面四元数看起来合理但机器人运动到目标点总差一个旋转角度。我就是在 RobotStudio 里同时打开两个点的手动操纵画面来回切换观察欧拉角变化才定位到是坐标系变换时旋转顺序写反了。4.3 姿态校准时如何确认顺序如果你是从其他软件拿姿态比如从离线编程仿真软件、CAD 导出的数据、或者第三方视觉库里拿四元数一定不要想当然认为它们和 ABB 的旋转顺序一致。我吃过一次亏视觉系统用的是 XYZ 欧拉角我按 ABB 的 ZYX 逻辑去转换结果机器人姿态完全不对。避免这个问题的办法是“标定验证法”在 ABB 示教器上先手动转到一个特殊姿态比如绕 X 轴转 90 度记录四元数然后在视觉系统或第三方软件里做同样的旋转对比两者给出的四元数。如果一致说明旋转顺序一致如果不一致就用前面代码的“正反向验证”来反推到底哪个分量多转了。这类方向的确认最好在项目启动第一天就做掉否则后面所有姿态都有隐患。4.4 别忽略 robconf 配置参数robtarget里除了位置和姿态第三项[0,0,0,0]以及后面的外轴参数也不能忽略。robconf是机器人的轴配置参数它决定了当前姿态下机器人各轴处于哪种“手势”。很多人在改姿态四元数时只改了第二项没有同步考虑 robconf结果机器人报“目标点不可达”或者关节角发生大范围跳变。在实际修改程序时最稳妥的做法是在示教器上示教好基准点然后用上位机只修改位置和姿态保留原有 robconf。如果 RoboStudio 提示配置不匹配就重新示教一次该姿态让系统自动更新 robconf。这件事是纯姿态转换之外很容易被忽略的坑。5. 常见问题与排查技巧实录5.1 转换结果和示教器不一致先别急着怀疑公式大概率是旋转顺序或单位的问题。我遇到过的案例里十次有八次是角度制弧度制混用剩下两次是旋转顺序不一致。排查建议先拿一个简单姿态自测比如绕 X 轴 90 度绕 Y 轴 90 度绕 Z 轴 90 度分别转成四元数看是否能对应上。逐项过完通常就能定位问题。5.2 一写入四元数姿态就乱跳如果写入后机器人姿态出现剧烈变化先检查四元数有没有归一化。一个模长 0.98 的四元数看起来和标准四元数没什么差别但经过控制器内部计算后旋转路径会被放大最终姿态完全偏离预期。另外四元数有两个等价表示q和-q表示同一个姿态比如原地姿态既可以是[1,0,0,0]也可以是[-1,0,0,0]。有些算法喜欢取标量为正有些则不关心如果转换代码里没有统一程序里可能出现两个数值相反但实际相同的点表面看起来像“乱跳”。5.3 万向锁边界怎么处理当 pitch 接近 90 度时yaw 和 roll 的解不唯一这时候再用常规公式去算atan2 的分子分母可能都是 0返回的角度不稳定。我的处理方式是在 3.1 节代码里写的那样检测到 pitch 接近正负 90 度时固定 yaw 为 0把旋转全部归到 roll。这样至少能给出一个一致的、可复现的结果。如果项目要求这个特殊姿态必须精确建议直接用四元数来描述和传递不要在人机交互界面上显示欧拉角了。5.4 问题排查速查表整理了一张表方便现场排查症状可能原因处理办法角度值整体偏差几十度旋转顺序不对用简单姿态逐步验证旋转顺序某轴角度差 180 度四元数符号取反统一规范四元数标量符号姿态轻微偏移且随时间增大四元数未归一化转换前后都做归一化程序点写入后机器人跳变robconf 不匹配重新示教目标点pitch 90 度附近角度乱跳万向锁奇异固定 yaw 或改用四元数角度范围显示成 300 度弧度/角度换算或范围处理不一致统一输出 -180 到 180 度这张表是我在项目调试中不断补全的结果。有一次客户现场半夜打电话说机器人转到了奇怪的角度后来排查下来就是视觉系统输出的四元数没有归一化换算成欧拉角后差了好几度输出点直接在笛卡尔空间里出现了不可预期的姿态。5.5 一些长期经验和建议写这类转换代码我个人的习惯是把“正向转换”“反向转换”“旋转矩阵生成”“矩阵验证”这四个函数全部封装成一个工具模块每次接到新项目先跑一遍自检用例。自检用例也很简单就是那几个特殊角度0 度、90 度、180 度以及一个随机角度用正反转换互相验证。如果这些用例都能通过那么后续项目里使用这个模块就很放心。另外一个容易被忽略的点是不同的 ABB 机器人系统版本示教器显示欧拉角的界面可能略有差异但底层robtarget的四元数格式基本是一致的。所以只要代码里严格按 ABB 的 Q1-Q4 顺序处理跨项目复用是没问题的。最后建议现场调试时一定先在慢速模式下试运行新写入的目标点确认姿态方向符合预期后再提速安全永远比效率更重要。