☰
Fanuc机器人坐标变换矩阵:从WPR欧拉角到工具偏移的实战指南
2026/10/15 7:33:42 网站建设 项目流程

简介:面向机器人工程师与自动化学习者的专业参考文档,系统研究Fanuc(发那科)工业机器人坐标变换矩阵。文档从用户坐标系定义出发,详细讲解用户坐标系与世界坐标系的转换关系,以及以用户坐标系为基础进行旋转变换后的坐标求解,重点对点WPR含义、绕X/Y轴旋转后WPR计算及工具坐标系方向进行了推导与图示说明。资源共1个PDF文件,压缩包大小约1.97MB,内容结构完整,包含绕X/Y/Z轴旋转矩阵及多次旋转组合原理等附录,并附有Python代码辅助计算。目前已有112人学习,适合需要深入理解机器人位姿换算、进行坐标变换编程或排查轨迹问题的工程师参考。读者通过文档可掌握从旋转矩阵反推WPR的方法,并能在实际项目中套用附录中的转换公式与代码,提升坐标处理效率。

1. Fanuc机器人的坐标变换矩阵:现场调不好十有八九是这里没算对

调试机器人时有个很经典的怪现象:示教器点位看着都对,程序逻辑也没改过,但一涉及工具方向偏移、输送线跟踪、双机协作对位,末端位置就悄悄跑偏,甚至越偏越远。多数情况下问题不在伺服精度,而在坐标变换矩阵。Fanuc机器人的坐标变换矩阵是理解这整件事的钥匙:位置寄存器里那六个数字、用户坐标系UFRAME、工具坐标系UTOOL,在系统内部都是一组组矩阵在参与运算。搞明白它,你就能把「感觉玄学的偏移」变成「提前算得出来的数值结果」。这篇笔记写给需要自己写偏移逻辑、做上位机坐标计算脚本,或者刚接手现场调试的工程师。下文从坐标系本质讲到换算代码,再落到两个高频场景和五条避坑记录。

2. 坐标系约定的矩阵本质:从World/User/Tool到变换链

Fanuc示教器里有三类坐标系作为运动参考:World、User、Tool。很多人用了一年也只把它们当成「下拉框里的一个选项」,其实每个坐标系都对应一个齐次变换矩阵,程序最终算出来的目标点就是几个矩阵串起来的结果。

2.1 三个核心坐标系在矩阵里到底是什么

World坐标系固定在机器人底座上,原点一般在底座安装面中心,Z轴竖直向上。这是一切计算的根,其他坐标系的最终参考都可以追溯到它。User坐标系是使用者在工件侧定义的坐标系,标定结果用矩阵 T_world_user 表示,含义是用户坐标系原点在世界坐标系下的位置,以及三个轴的方向。Tool坐标系定义的是工具中心点TCP相对于法兰盘坐标系的位姿,标定结果记为 T_flange_tool。

为什么强调「矩阵」而不是「坐标」?因为一个坐标系用数学语言描述就是4x4的齐次矩阵。前3x3是旋转矩阵,反映三个坐标轴指向;第4列前3行是原点位置;最后一行固定为0 0 0 1。Fanuc在示教器上做三点法标定UFRAME、三点法或六点法标定UTOOL,本质上就是在解这个矩阵的未知数。理解了这一点,再看示教器里的UFRAME[1]、UTOOL[1],它们不是「一个点」,而是「一套位姿」。

三个矩阵串起来就是完整变换链。以用户坐标系编程为例,程序里目标点P是用户坐标系下的TCP位姿 T_user_tcp。机器人执行时先把它转到世界系:T_world_tcp = T_world_user · T_user_tcp。接着结合工具坐标系求法兰盘需要的位姿 T_world_flange = T_world_tcp · (T_flange_tool)⁻¹,最后对这个位姿做运动学反解得到关节角。这条链在程序里是一行行指令,在数学上每步都是矩阵乘法。这也是为什么改一个UFRAME,程序里所有目标点都会跟着变。

坐标系矩阵表示现场标定手段程序里引用方式
World默认基准出厂标定坐标系下拉选World
UserT_world_user三点法(原点+X方向+Y方向)UFRAME[n]
ToolT_flange_tool三点法/六点法UTOOL[n]

2.2 齐次变换矩阵与Fanuc的PR表达:六个数是矩阵的压缩形式

位置寄存器PR是Fanuc存放位置数据的基本单元,显示为X、Y、Z、W、P、R六个数字。前三个是位置,后三个是姿态欧拉角。姿态部分并不是旋转矩阵本身,而是旋转矩阵的压缩编码。同一个旋转矩阵可能对应多组WPR数值,这是后面避坑章节里多解性的来源。

把一个PR还原成4x4矩阵,结构如下:

T = [[R_xx, R_xy, R_xz, X], [R_yx, R_yy, R_yz, Y], [R_zx, R_zy, R_zz, Z], [ 0, 0, 0, 1]]

其中3x3的R由WPR按固定角约定算出。在Fanuc绝大多数机型的手册描述里,W、P、R分别表示绕参考坐标系的X、Y、Z轴旋转,等效矩阵顺序是 R = Rz(R)·Ry(P)·Rx(W)。这里的顺序不能凭感觉换,一旦换算脚本把顺序写成其他组合,出来的方向会差得很远。第3章会给完整代码,这里先记住一个结论:PR不是黑匣子,它就是矩阵的压缩包。

提示:同一组WPR在User和Tool坐标系下显示数值可能不同,因为参考系变了。矩阵是唯一不变的,显示数值会跟着参考系变。排查偏移问题时先确认你读的是哪个坐标系下的PR。

2.3 用正运动学验证矩阵:Python复算法兰盘位姿

正运动学解决一个基础问题:给定六个关节角,末端法兰盘在哪?它用的正是坐标系变换。六轴机器人从底座到法兰,每经过一个关节就把坐标系沿着连杆平移旋转一次,整个过程就是矩阵连乘。用标准DH参数写一个通用正解函数。注意不同型号的DH表和工具坐标系零点定义不同,下面给的是通用框架,请以你手上机型手册的参数为准。

import numpy as np def dh_transform(a, d, alpha, theta): # 标准DH参数:a为连杆长度,d为连杆偏距,alpha为连杆转角,theta为关节角 ct, st = np.cos(theta), np.sin(theta) ca, sa = np.cos(alpha), np.sin(alpha) return np.array([ [ct, -st*ca, st*sa, a*ct], [st, ct*ca, -ct*sa, a*st], [0, sa, ca, d], [0, 0, 0, 1] ]) def forward_kinematics(dh_params, joint_angles): # dh_params: 每行是 (a, d, alpha),joint_angles是六个关节弧度 T = np.eye(4) for (a, d, alpha), q in zip(dh_params, joint_angles): T = T @ dh_transform(a, d, alpha, q) return T

逻辑说明:每一段dh_transform构造的是相邻连杆坐标系之间的齐次变换,循环里把矩阵依次乘起来,最终得到法兰盘相对底座坐标系的位姿。参数说明:alpha和theta的单位是弧度,传入前要先np.deg2rad;a和d的单位是毫米,注意你手上的时序参数是否统一;关节角零点因机型而异,机器人处于机械零位时关节角不一定全是0。

验证方法:把示教器上读到的当前关节角代入,对比输出矩阵的平移列和示教器当前世界坐标。误差在0.001毫米量级说明DH参数和角度换算正确;差几毫米甚至几十毫米,多半是DH表里a和alpha有一列填反,或者角度忘了转弧度。我一般用「先算正解再对比示教器」的方式排查外围脚本里的矩阵问题,比直接调偏移逻辑快得多。

3. 把WPR换算成旋转矩阵:Fanuc姿态的数学拆解

第2章把PR理解成矩阵的压缩形式,这一章要亲手解开它。所有坐标变换都建立在WPR与旋转矩阵互转之上,这里写错一个符号,后面的工具偏移、输送线跟踪全都会跟着错。

3.1 固定角还是随动角:WPR的旋转顺序先说清

「绕参考坐标系旋转」和「绕自身坐标系随动旋转」是两种完全不同的欧拉角约定,同样的三个角度值生成的矩阵可能完全不同。Fanuc的WPR按固定角处理,在多数机型手册里描述为绕参考坐标系的X、Y、Z轴依次旋转,等效旋转矩阵是 R = Rz(R)·Ry(P)·Rx(W)。固定角XYZ与随动角ZYX恰好等价,所以有些资料里也会把Fanuc WPR写成ZYX欧拉角,知道这一点,看各家资料时就不会被绕晕。

为什么顺序这么重要?因为旋转矩阵乘法不可交换:先绕X转30度再绕Z转90度,与先绕Z转90度再绕X转30度,结果完全不同。示教器上看起来只是三个数,换算顺序错了,末端指向可能南辕北辙。判断换算对不对,最直接的办法是拿一个已知PR验证:绕Z转90度,期望矩阵把X轴映射到Y轴方向。这个方法在3.3节实际走一遍。

3.2 欧拉角、旋转矩阵、四元数互换代码

下面是工程里直接能用的三个函数:欧拉角转旋转矩阵、旋转矩阵反算WPR、旋转矩阵转四元数。反算里对俯仰角接近±90度做了分支处理,这是万向锁问题,不是代码写错,是这个姿态下数学上无法唯一反解。

import numpy as np def euler_to_rot(w_deg, p_deg, r_deg): # Fanuc WPR:绕固定轴依次转X、Y、Z,等效R=Rz(r)*Ry(p)*Rx(w) w = np.deg2rad(w_deg) p = np.deg2rad(p_deg) r = np.deg2rad(r_deg) Rx = np.array([ [1, 0, 0], [0, np.cos(w), -np.sin(w)], [0, np.sin(w), np.cos(w)]]) Ry = np.array([ [np.cos(p), 0, np.sin(p)], [0, 1, 0], [-np.sin(p), 0, np.cos(p)]]) Rz = np.array([ [np.cos(r), -np.sin(r), 0], [np.sin(r), np.cos(r), 0], [0, 0, 1]]) return Rz @ Ry @ Rx def rot_to_wpr(R): # 反算WPR,p范围限制在[-90,90],超出会出现万向锁 p = np.arcsin(-R[2, 0]) if abs(np.cos(p)) > 1e-6: w = np.arctan2(R[2, 1], R[2, 2]) r = np.arctan2(R[1, 0], R[0, 0]) else: # 万向锁:姿态无法唯一分解,固定r为0,w取一个可行值 w = np.arctan2(-R[1, 2], R[1, 1]) r = 0.0 return np.rad2deg([w, p, r]) def rot_to_quat(R): # 旋转矩阵转四元数,标准算法,返回[w, x, y, z] tr = np.trace(R) if tr > 0: s = np.sqrt(tr + 1.0) * 2 qw = 0.25 * s qx = (R[2, 1] - R[1, 2]) / s qy = (R[0, 2] - R[2, 0]) / s qz = (R[1, 0] - R[0, 1]) / s else: # 其他分支用于处理对角线元素较大时,避免开方负数 if R[0, 0] > R[1, 1] and R[0, 0] > R[2, 2]: s = np.sqrt(1.0 + R[0, 0] - R[1, 1] - R[2, 2]) * 2 qw = (R[2, 1] - R[1, 2]) / s qx = 0.25 * s qy = (R[0, 1] + R[1, 0]) / s qz = (R[0, 2] + R[2, 0]) / s elif R[1, 1] > R[2, 2]: s = np.sqrt(1.0 + R[1, 1] - R[0, 0] - R[2, 2]) * 2 qw = (R[0, 2] - R[2, 0]) / s qx = (R[0, 1] + R[1, 0]) / s qy = 0.25 * s qz = (R[1, 2] + R[2, 1]) / s else: s = np.sqrt(1.0 + R[2, 2] - R[0, 0] - R[1, 1]) * 2 qw = (R[1, 0] - R[0, 1]) / s qx = (R[0, 2] + R[2, 0]) / s qy = (R[1, 2] + R[2, 1]) / s qz = 0.25 * s return np.array([qw, qx, qy, qz])

参数说明:三个函数都以度为单位输入输出,内部只处理弧度;rot_to_wpr返回的WPR范围是w在(-180,180]内、p在[-90,90]内、r在(-180,180]内,跟示教器显示范围基本一致;万向锁时的分解不是唯一答案,如果程序要做连续姿态插值,建议改用四元数而不是欧拉角。

逻辑说明:rot_to_quat主分支用的是工程上常用的Shepperd方法,先判断矩阵迹大于0走主分支,否则走最大对角分量分支,目的是数值稳定。离线脚本里如果没有科学计算库,这份手写实现足够用;如果有,直接用现成的Rotation函数一行搞定,但那份依赖在纯离线现场不一定装得上。

3.3 用零位和90度旋转验证换算结果

代码写完先别急着接机器人,先做两个数学自检。第一个是零位:W=P=R=0时,euler_to_rot应该输出单位矩阵。第二个是绕Z转90度:PR为[0,0,0, 0,0,90]时,旋转矩阵应该把X轴映射到Y轴方向,也就是R[0,0]接近0,R[1,0]接近1。用下面两行就能验证:

print(euler_to_rot(0, 0, 0)) print(euler_to_rot(0, 0, 90))

再把结果用rot_to_wpr反算,看能不能回到[0,0,90]。回不来就优先检查角度到弧度的转换有没有遗漏,其次是检查矩阵乘法的顺序。这三步走完,第4章的偏移计算才敢往它上面搭。

4. 两个高频场景的坐标变换实战:工具偏移与用户坐标系旋转

坐标变换不是拿来考试的,实际现场最有价值的两个用途:一个是在已知姿态下沿工具方向做偏移,另一个是整体旋转用户坐标系,让同一条程序适配多个工位。

4.1 沿工具Z方向偏移:矩阵乘法替代示教器微调

现场需求很常见:让焊枪沿着当前枪姿方向往前伸5毫米,或者让吸盘沿工具Z方向下压2毫米。新手容易直接改PR里的Z值,改完发现位置变了但方向不对,因为PR里的Z是参考坐标系下的Z,不是工具自身的Z。正确做法是把工具坐标系下的偏移量,先通过当前姿态矩阵旋转到世界坐标系,再叠加到位置上。

def offset_along_tool(pose_deg, d_xyz): # pose_deg: [x, y, z, w_deg, p_deg, r_deg] 示教器读出的当前PR pos = np.array(pose_deg[:3], dtype=float) R = euler_to_rot(pose_deg[3], pose_deg[4], pose_deg[5]) # 工具坐标系下的偏移量乘当前姿态矩阵,转到世界坐标系 new_pos = pos + R @ np.array(d_xyz, dtype=float) return [round(v, 3) for v in new_pos] + pose_deg[3:]

调用示例:沿工具Z正方向偏移10毫米,d_xyz填[0,0,10];沿工具X方向偏移,填[10,0,0]。姿态部分原样返回,因为只平移不旋转。逻辑说明:R @ d_xyz表示把d_xyz从工具坐标系方向换算到世界坐标系方向。如果写反成d_xyz @ R,numpy会直接报维度错误;要是你手写了矩阵乘法,就很容易把左右顺序做反还不报错,这是后面避坑章节里的一条。

机器人程序侧怎么用?把算出来的新PR填进位置寄存器,再执行L指令。离线脚本算完后手动录入示教器时,注意WPR显示的小数位。四舍五入到0.001度对大多数焊接场景够用,但对精密对位场景,最好直接通过上位机写入寄存器,而不是手抄。

提示:如果偏移方向反了,把d_xyz里对应分量取负即可,不要改代码里的旋转矩阵。方向符号问题先试一次再批量改。

4.2 用户坐标系整体旋转:多工位对称件的快速切换

当一个工件在转台上旋转90度后再次加工,你可以把程序目标点全改一遍,也可以只改一个UFRAME。UFRAME代表用户坐标系相对世界坐标系的位姿,旋转工件在数学上等价于给这个矩阵的旋转部分右乘一个旋转量。

def rotate_user_frame(uframe_pose_deg, axis, angle_deg): # uframe_pose_deg: UFRAME的PR值 [x, y, z, w_deg, p_deg, r_deg] pos = np.array(uframe_pose_deg[:3], dtype=float) R_uf = euler_to_rot(uframe_pose_deg[3], uframe_pose_deg[4], uframe_pose_deg[5]) # 轴角转旋转矩阵:罗德里格斯公式 axis = np.array(axis, dtype=float) axis = axis / np.linalg.norm(axis) a = np.deg2rad(angle_deg) K = np.array([ [0, -axis[2], axis[1]], [axis[2], 0, -axis[0]], [-axis[1], axis[0], 0]]) R_rot = np.eye(3) + np.sin(a) * K + (1 - np.cos(a)) * (K @ K) # 用户系绕自身原点旋转:右乘旋转矩阵 R_new = R_uf @ R_rot return [round(v, 3) for v in pos] + [round(v, 3) for v in rot_to_wpr(R_new)]

核心就一句话:R_new = R_uf @ R_rot。右乘表示新旋转发生在用户坐标系自己的坐标轴下,左乘则变成绕世界坐标系旋转,这是最容易混淆的地方。旋转轴传[0,0,1]就是绕用户系Z轴转,angle_deg为正时按右手定则,Z轴朝上时逆时针转。逻辑说明:这里用了罗德里格斯公式把轴角转换为旋转矩阵,K矩阵是轴向量的反对称矩阵,是标准实现。如果旋转中心不是用户系原点,还要处理平移部分,具体见4.3。

4.3 变换链书写顺序:左乘右乘搞反就翻车

矩阵运算里顺序不是小事。左乘是「绕参考坐标系旋转」,右乘是「绕当前坐标系旋转」。在4.2里旋转用户坐标系绕自身轴,所以右乘。沿工具偏移那个例子,严格说是「向量从工具系变到世界系」的坐标变换,用的是当前姿态矩阵左乘偏移向量,和这里说的矩阵左乘右乘是两个层面的问题,但都是同一条逻辑链上的事,混了就会翻车。

绕任意点旋转用户系时,平移不能沿用4.2的代码。公式是 P_new = P + R_uf @ P_rot - R_new @ P_rot,其中P_rot是旋转中心在用户系下的坐标。原因是:绕任意点旋转,旋转中心在变换前后必须保持同一个世界位置,所以平移部分要额外补偿。这一步省略的话,旋转后的用户系原点会跑掉,程序所有点都偏。我在现场见过有人改UFRAME只更新姿态、忘了平移,结果工件旋转后对位偏差到几十毫米,排查了一下午才找到是这个公式少了最后一项。

5. 坐标变换调试的五个常见坑:现象、原因与解决

下面五条是我在现场调试时真正踩过的坑,按现象、原因、解决三栏写,调试时可以直接对照排查。

5.1 反算的WPR和示教器对不上

现象:脚本算出来的WPR明明是从正确矩阵反解的,和示教器上原来输入的WPR数值却不一样,角度差得离谱。

原因:欧拉角反解天然有多解性,同一个姿态至少对应两组以上WPR组合,过程中还可能越过万向锁;示教器只是把其中一组显示出来,你的反解选了另一组。

解决:不要直接对比WPR数值。把两组WPR各自转成旋转矩阵,比较矩阵每个分量是否一致。矩阵一致就说明姿态没算错,剩下只是表示方式差异。这份偏差在示教器上看着吓人,但只要矩阵对,机器人跑起来就是对的。

5.2 工具方向偏移越偏越远

现象:沿工具Z方向偏移10毫米,第一次对完,第二个点开始位置逐点漂移,像滚雪球一样。

原因:常见是把偏移后的点当新基准,下次偏移又取示教器显示值累加;或者示教器显示的是四舍五入后的WPR,误差逐次累加。

解决:每次偏移都从原始基准PR出发,用同一个矩阵计算;不要在已偏移过的结果上二次读取再偏移。若必须连续偏移,在TP程序里用PR运算指令完成,避免人工录入引入舍入误差。

5.3 输送线跟踪偏移点漂移

现象:加上编码器跟踪后,机器人一开始跟得上,运行一段后TCP逐渐偏离工件特征点。

原因:编码器增量被直接加到世界坐标系的X上,而输送线方向与用户坐标系X轴并不平行;或者即使平行,增量缺少从输送线坐标系到用户坐标系的旋转转换。

解决:先把输送线移动向量放到输送线坐标系里,再用输送线相对用户系的姿态矩阵旋转到用户系,最后叠加到目标点。这个矩阵在标定输送线方向时应一并求出,别图省事用标量加法。

5.4 用户坐标系旋转后姿态翻转

现象:想让UFRAME绕Z轴逆时针转90度,执行后机器人的姿态看起来像是绕Y轴转了90度,工件对不上。

原因:把绕世界系和应用户系搞混,或者在rot_to_wpr反解时遇到万向锁,示教器重新解析后显示了另一组等价角度,视觉上就像翻了另一个轴。

解决:先在一个已知点上验证。把当前点世界系坐标代入公式,算完再用新UFRAME反算回世界系,看结果和原坐标是否相等。这一步能同时验掉旋转方向和旋转中心两个疑点。

5.5 重定位后Z轴对不准

现象:用坐标变换把工件从一个工位搬到另一个工位,位置基本对上,但工具Z轴始终压不下去,角度偏几度。

原因:常见是只更新了平移部分,姿态矩阵沿用老数据,或者旋转矩阵某一行填错;另一种是把工具坐标系的姿态矩阵和用户坐标系的姿态矩阵直接相乘,缺少中间法兰系。

解决:画一遍完整变换链 T_world_tcp = T_world_uf · T_uf_tcp,然后看T_world_tcp第三列是否为实际需要的工具Z方向。把这一步写在纸上,比对着代码猜快得多。

注意:前三条是「程序或脚本算错了」,后两条是「坐标系定义和反解约定搞混」,排查时先分清是哪一类,别一上来就怀疑伺服精度。上述都查过且矩阵无误,再考虑机器人本体标定问题。

6. 现场验证手法与一个小工具:算完怎么确认没算错

6.1 三遍验证法:空跑、单轴点动、千分表打表

变换矩阵算完别急着跑自动。我先花五分钟做三遍验证:第一遍,把程序倍率降到10%,空跑一遍新点位,看TCP轨迹方向和姿态过渡是否合理,视线别离开工具。第二遍,把坐标系切到Tool,用单轴点动Z,确认工具Z轴指向和期望方向一致,这一步能挡掉一大半姿态矩阵方向问题。第三遍,用千分表或激光跟踪仪打点,量实际位置和计算值的差。空跑看方向、点动试姿态、打表验位置,三件事做完再上自动,翻车概率大幅下降。

6.2 一个Python小脚本:输入当前PR输出变换后的PR

配合第3、4章的函数,我在笔记本里常年放这样一个脚本,现场改PR、验偏移都靠它,几秒钟出结果。

# 依赖上面3.2节和4.1节的函数 if __name__ == '__main__': raw = input('当前PR: X,Y,Z,W,P,R = ').split(',') pose = [float(x) for x in raw] raw_d = input('工具偏移 dX,dY,dZ = ').split(',') d_xyz = [float(x) for x in raw_d] new_pr = offset_along_tool(pose, d_xyz) print('新PR: %.3f, %.3f, %.3f, %.3f, %.3f, %.3f' % tuple(new_pr))

参数说明:输入输出单位保持和示教器一致,位置毫米、角度度;工具偏移三个值默认在工具坐标系下,想沿世界坐标偏移,直接把d_xyz换成世界系下的增量即可,其他逻辑不用改。跑完以后把输出写入位置寄存器,再按6.1的三遍验证法走一遍。

我自己的习惯是:每次改完坐标变换,先在白纸上写一遍变换链,分清当前坐标在哪个参考系下、下一步是左乘还是右乘,再动代码。这套习惯帮我少翻了很多次车。坐标变换矩阵这件事,最大的成本从来不是公式本身,而是现场出问题时不知道往哪查。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询