
先说一个很常见的场景你拿到一台六轴机械臂网上找到一份MDH参数表想验证正运动学结果怎么算都对不上实机。反复检查坐标系最后发现是把theta偏移量当成零了。我当年也在这个坑里趴了三天。这篇写给所有正在啃运动学、做ROS机械臂开发、或者准备用手眼标定却总被坐标变换卡住的人。我会从坐标系建立讲到正运动学矩阵计算最后给出可以直接抄的Python验证思路。1. MDH不是“多一个beta”而是坐标系挂载方式的改变1.1 标准DH与MDH的换序逻辑先澄清一个流传很广的误传。很多人一说MDH就以为是在标准DH参数表里额外加一个绕Y轴的beta角。其实经典意义上的MDHModified DH也就是Craig在《机器人学导论》里的写法仍然是四个参数alpha、a、d、theta。它没有增加参数改变的是坐标系附着位置和矩阵乘法顺序。标准DH里坐标系i被固定在第i个连杆的远端。换一句话说描述相邻连杆关系时先沿着前一个关节的Z轴转/移再沿着当前关节的X轴转/移。MDH正好反过来坐标系i被固定在第i个连杆的近端先绕当前连杆的X轴转/移再绕下一个关节的Z轴转/移。这个差异体现在齐次变换矩阵上就是标准DHT RotZ(θ) * TransZ(d) * TransX(a) * RotX(α)MDHT RotX(α) * TransX(a) * RotZ(θ) * TransZ(d)为什么把RotX提到最前面因为当我们从基座开始一节一节往末端推的时候每个连杆的输入端才是我们唯一能稳定确定的几何参考。对于大多数串联六轴机械臂标准DH也能用可一旦遇到树形结构比如双臂机器人或闭链结构比如并联机构标准DH定义x轴时会碰到“公垂线不唯一”的尴尬。MDH规定坐标系永远从上一关节的Z轴出发、沿着公法线建立X规则更统一代码写起来也更不容易串位。1.2 什么时候必须用MDH我个人的建议是如果你做的是串联六轴很多教科书里的标准DH也够用但如果你做的是UR、Panda这类协作臂开发或者打算后续做参数标定、手眼标定直接上MDH会更省事。原因有两方面。第一UR这类机器人的关节2和关节3是近似平行的用标准DH做参数辨识时平行轴之间的微小角度扰动会让a和d的参数估计来回跳MDH把X轴旋转提前让数值更稳定。第二MoveIt!和ROS社区大量现成运动学插件、开源项目都习惯用Modified DH的约定你从仓库里拉下来的机器人描述包几乎都能看到对应MDH的RPY描述提前统一坐标系风格能避免后面和TF树对不上。另外有些协作机器人在实际标定时确实会用五参数模型也就是在MDH基础上额外加一个绕Y轴的beta。这通常是为了补偿加工装配误差而不是MDH本身的一部分。我们这篇先用经典四参数MDH把原理讲透五参数可以在标定阶段再做扩展。2. 坐标系建立的四条规则每个关节的“门轴”与“门框”2.1 四条规则的完整表述MDH建系说到底就是反复用同一套动作找Z轴找X轴补齐Y轴。这里给出一套可以直接落地的规则基座的坐标系{0}自己定Z0沿关节1的转轴方向建议指向机械臂正上方或正前方X0可以任意选一个方便的方向但要保证保持右手系。对关节1到关节6首先确认Z_{i-1}轴沿着第i根关节轴的运动方向。旋转关节取旋转轴移动关节取移动方向。这一条最优先不要被“上一根连杆末端在哪”干扰。确定X_i轴分三种情况如果Z_{i-1}和Z_i异面X_i沿公垂线方向从Z_{i-1}指向Z_i如果Z_{i-1}和Z_i相交X_i取同时垂直于二者的方向如果Z_{i-1}和Z_i平行X_i沿共同法线方向此时方向不唯一需要结合机械臂实际布局取一个让后续参数尽量规整的方向。Y_i由右手定则确定X_i叉乘Y_i等于Z_i。没有例外如果算出来不对多半是前面某一步方向选反了。这套规则看起来像在做几何题实际上每个坐标系的Z轴都是“上一根关节轴”X轴是“从上一根轴到当前轴的连接线方向”。可以把它想成门框和门轴Z轴就是每扇门的门轴X轴是连接两根门轴的梁。梁的长短决定a梁与门轴之间的夹角决定alpha沿门轴的高低差决定d开门的角度就是theta。这样记忆负担会小很多。2.2 四个参数如何从几何中读取坐标系画好之后四个参数就是直接读数a_{i-1}沿X_{i-1}方向从Z_{i-1}到Z_i移动的距离。如果两台关节轴是交于一点的a就是0如果是平行的a就是两根轴的实际间距。alpha_{i-1}绕X_{i-1}轴从Z_{i-1}转到Z_i的角度。符号用右手定则判断把大拇指指向X_{i-1}正方向四指卷过去的方向就是正方向。d_i沿Z_i轴从X_{i-1}到X_i的距离。注意这里的Z_i是当前关节的Z轴不是前一关节的。很多人在这一步把标准DH和MDH搞混标准DH的d沿前一关节Z轴MDH的d沿当前关节Z轴。theta_i绕Z_i轴从X_{i-1}转到X_i的角度。对旋转关节这个角就是驱动变量直接写成theta_i q_i theta_offset。theta_offset来源于你把X_i的零点约定放在哪个位置。在读参数时一定要带着“当前坐标系是跟着第i根关节轴转的”这个意识。MDH表里每一行描述的是上一坐标系和当前坐标系之间的相对位姿而不是简单描述两个连杆的长度。只要把这一点想通后面矩阵连乘就只是体力活了。3. 实操填表六轴机械臂MDH参数表从哪来3.1 一张演示用的六轴参数表说得再具体一点。下面这张表是我按常见6R协作机器人结构整理的演示数据用来示范填表逻辑。它不是某个品牌官方标定值大家在做自己的机械臂时需要从自己的三维模型或标定报告里替换数字。ialpha_{i-1}a_{i-1}d_itheta_i1000.0892q12-π/200q2 - π/230-0.4250q340-0.392250.10915q45π/200.09465q56-π/200.0823q6注意几个细节。第一第2行的theta_i写的是q2 - π/2而不是q2。这是因为我在建坐标系时默认机械臂零位时关节2的前臂延长线指向某个方向为了让X轴和Z轴之间的几何关系规整就必须预设90度的初始角偏移。这个偏移不是误差是坐标系初始定义的产物。第二第3、4行的a是负值说明这两个连杆的坐标系X轴方向与机器人正前方相反属于常见约定。第三第5、6行的a都为0表示这是一个近似球腕结构关节4、5、6的轴基本交于一点。读表时你还会发现第1行其实是描述基座到关节1的变换alpha和a都是0只保留了一个d1。如果你的机械臂基座到第一个关节还有一根斜杆那么alpha和a就不会是0了。3.2 从CAD模型量和从实机标定的差异填表最常见的疑问是“这些数从哪里来”。如果你是做毕业设计或开源DIY机械臂直接在SolidWorks里测量即可分别建立相邻关节轴之间的公垂线量出长度差就是a量出Z轴夹角就是alpha量出沿下一关节Z轴的偏移就是d。要注意的是SolidWorks里建的坐标系可能和MDH需要的方向不一致不要直接把零件原点往里套最好是按上一节说的规则重新建坐标系。如果你做的是工业机械臂二次开发或者要做高精度抓取最好从厂商那里拿标定后的DH表或者自己用激光跟踪仪、CMM做参数标定。CAD里的名义参数和实际装配后的参数之间通常有0.1到1毫米量级的偏差对这些偏差影响最大的往往是d和theta偏移量。这也是为什么很多开源项目会在每个关节的URDF里加一个微调用的修正量。理解这一点就能明白“机械臂偏差”在日常开发里的真实含义。商用机械臂还有一个坑就是厂商文档里的DH表不一定会标明是SDH还是MDH有些甚至把rotZ移动到末尾。你在打代码前应当先按厂家给的矩阵形式反推它的顺序或者干脆自己从CAD重新建一张MDH表免得算法里混用两套约定。4. 正运动学矩阵计算从A1连乘到A64.1 齐次变换矩阵的完整形式MDH的连杆变换矩阵就是把四种旋转和平移按顺序乘起来A_i RotX(alpha_{i-1}) * TransX(a_{i-1}) * RotZ(theta_i) * TransZ(d_i)展开后是一个4x4齐次矩阵。为了便于写代码我习惯直接按元素构造| cosθ_i -sinθ_i 0 a_{i-1} | | sinθ_i*cosα_{i-1} cosθ_i*cosα_{i-1} -sinα_{i-1} -d_i*sinα_{i-1} | | sinθ_i*sinα_{i-1} cosθ_i*sinα_{i-1} cosα_{i-1} d_i*cosα_{i-1} | | 0 0 0 1 |注意右上角只有a_{i-1}而左下角和右下角的d_i都乘了sin或cos(alpha)。很多新手把元素位置抄错然后疑惑为什么机械臂末端位置一直偏。最容易判断错的是第3行第2列它应该是cosθ_isinα_{i-1}不是sinθ_isinα_{i-1}。这个错误我见过好几次尤其在零位姿态下表面看没问题但把机械臂转个角度就出问题。写代码时我不建议每次都手工展开整个矩阵。直接用numpy把RotX、TransX、RotZ、TransZ这四个基本算子连乘代码可读性更高也方便后续进行符号推导和灵敏度分析。4.2 矩阵连乘顺序与末端位姿提取正运动学就是一个连乘过程T_0_6 A_1 * A_2 * A_3 * A_4 * A_5 * A_6这里有一个关键认知矩阵左乘表示从右往左做坐标变换A_1先把坐标系1里的点表示到坐标系0A_2再把坐标系2表示的坐标系转换到坐标系1因此这个乘法顺序绝对不能反过来。你在中学里学的“矩阵乘法不满足交换律”就是在这里开始影响命运的。连乘结束之后T_0_6的左上3x3就是末端坐标系相对于基座坐标系的旋转矩阵R。R的3列分别是末端坐标系的X、Y、Z三个轴在基座坐标系下的投影。右上3x1就是末端原点在基座坐标系下的位置向量px T[0,3] py T[1,3] pz T[2,3]如果你想得到RPY欧拉角或四元数需要用旋转矩阵和工具的转换函数继续解算。此时最容易出的问题是各库对欧拉角旋转顺序的定义不一样ROS里默认ZYX顺序SciPy的from_euler默认也是ZYX但有些视觉库是XYZ。日常工作里我们要做手眼标定时pose的旋转部分必须统一成同一种表示否则外参标定矩阵会在旋转顺序上直接打架。5. 最容易翻车的四个细节偏移角、符号约定、参数表风格、工具库差异5.1 theta偏移量最常见的“机械臂偏差”第一个要说的还是theta偏移。在我带过的项目里80%的正运动学计算bug都出在theta_i上。比方说表中写“q2 - π/2”你用电机编码器读出来的q2是0.5弧度那真正进入矩阵的theta应该是0.5-1.5708-1.0708弧度。如果你把0.5直接丢进去末端位置就会整体转了一个固定角度而且不是绕单一轴转因为后续矩阵还会一起连乘看起来就像“机械臂偏差”一样毫无规律。排查方法很简单给机械臂一个已知的单关节运动。比如只转动关节2末端应当在某个圆弧上移动而不是在空间里乱飞。如果发现其他关节不动时末端轨迹不是圆弧多半就是theta_offset或alpha符号错了。这种排查法不需要任何仪器在仿真里就能做。5.2 alpha的符号与坐标轴方向alpha的符号是第二个高发区。绕X轴旋转的角度有正负两个Z轴之间的夹角是锐角但方向可以不同。例如上面演示表里第2行是-π/2第5行是π/2如果你把两者搞反了最后手腕部分的旋转矩阵会变成转置关系。确定符号的最好方法不是靠眼睛看图而是用右手定则比划把右手大拇指指向X轴正方向四指从Z_{i-1}卷向Z_i四指弯曲的方向就是正角度方向。不要依赖CAD视图的观察角度因为你很可能从背面看图看到的符号正好相反。5.3 不同DH风格混用OpenCV、SciPy、ROS各教什么第三个坑是风格混用。OpenCV的标定模块、SciPy的旋转库、ROS的tf、KDL、以及不同版本的MoveIt插件对“旋转矩阵”“欧拉角”“坐标变换”的底层约定不完全一样。尤其是涉及手眼标定和机械臂联合标定时你从相机标定得到的外参是基于标准光学坐标系Z轴指向相机前方而机械臂的基座坐标系常常是Z轴向上两个坐标系不能直接相乘必须先加一个固定变换过渡。我见过的混用例子还包括作者从某一篇论文里复制了DH矩阵公式又用另一个开源库的正运动学函数去验证两者矩阵顺序差一个转置最后花了两天时间在一个负号里挣扎。正确的做法是所有公式一律以“RotX * TransX * RotZ * TransZ”这样的显式乘法为准不要抄“展开式”而不核对顺序。5.4 零位自检让机械臂给你“报数”最后安利一个我常用的自检方法零位测试。把所有关节角设到0注意一下你的q是否真的要代入0把末端工具的坐标也考虑进去然后运行正运动学。这时你会得到一个固定的末端位姿。拿着这个位姿和CAD模型在零位时的位姿比对如果px、py、pz都能对上那你的MDH参数表基本就稳了。如果对不上可以先不急着怀疑a和d优先检查theta_i中的偏移项和alpha的正负号。因为在零位姿态下所有关节角为0d和a的作用是“静态平移”它们错了只会让末端位置整体平移一段固定值而theta偏移或alpha符号错了会导致整个坐标轴方向都发生变化表现出来更明显。6. 用Python和ROS把MDH模型跑起来6.1 Python实现不到20行写出MDH正运动学实际开发中我通常用一个简单的Python函数来完成MDH正运动学。下面这段是演示代码函数名和参数顺序都做了简化但逻辑可以直接搬到你的ROS节点或机器人控制程序里。import numpy as np def mdh_matrix(alpha, a, d, theta): ct np.cos(theta) st np.sin(theta) ca np.cos(alpha) sa np.sin(alpha) return np.array([ [ct, -st, 0, a], [st * ca, ct * ca, -sa, -d * sa], [st * sa, ct * sa, ca, d * ca], [0, 0, 0, 1] ]) def fk_mdh(dh_table, q): T np.eye(4) for row, qi in zip(dh_table, q): alpha, a, d, theta_offset row T T mdh_matrix(alpha, a, d, qi theta_offset) return T # 用上面演示的参数表填进去 dh_table [ (0, 0.0, 0.0892, 0.0), (-np.pi/2, 0.0, 0.0, -np.pi/2), (0, -0.425, 0.0, 0.0), (0, -0.39225, 0.10915, 0.0), (np.pi/2, 0.0, 0.09465, 0.0), (-np.pi/2, 0.0, 0.0823, 0.0), ] q [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] print(fk_mdh(dh_table, q))运行后得到的4x4矩阵就是末端相对于基座的位姿。如果你没有真机就先用这种方式在Python里验证。由于不同机械臂的参数不同我这里不贴固定数值结果你把你的真实DH参数替换进去和CAD比对即可。要注意的是表的顺序必须和你的机械臂关节顺序一致最后一列如果是工具坐标系也可以单独再加一个工具矩阵。6.2 在ROS里用TF/Rviz验证到了ROS环境下MDH模型可以作为一种校验工具。你可以用static_transform_publisher或TF广播把T_0_6发到TF树上然后在Rviz里把base_link和tool0两个坐标系显示出来和机械臂模型的URDF视觉网格对比。如果正运动学算错了末端坐标系就会“飘”在模型外面非常直观。更常见的做法是把这份正运动学代码写成一个ROS service输入六个关节角输出末端位姿。这样你在做MoveIt!规划或自己写轨迹规划算法时随时可以把规划出的关节序列转成笛卡尔位姿用来检查有没有撞到工作空间边界。很多开源机械臂项目都带类似FK的节点你完全可以照抄这个思路。6.3 后续扩展轨迹规划、手眼标定、重力补偿的起点最后说一下MDH能往哪里延伸。正运动学只是机械臂运动学的第一块基石。有了这个4x4矩阵后你可以继续做雅可比矩阵用来分析末端速度与关节角速度的关系可以做逆运动学数值解通过迭代误差来收敛到目标位姿也可以直接在MDH基础上做重力补偿把每一个连杆的重力在基座坐标系下的力矩映射到关节空间。手眼标定同样依赖正运动学相机外参和机械臂坐标系之间的连接必须通过一组准确的T_0_6来建立。如果正运动学里有一个0.1度的偏差手眼外参标定结果就会在上面加一个耦合偏差后面怎么调都调不出来。我的习惯是先把MDH正运动学做成最基础的“心智沙盒”。每次拿到一款新机械臂第一件事不是看控制代码而是根据它的结构手册建MDH表写一个几十行的FK函数再在零位、某个已知位姿、极限位姿下做三次验证。三次都过了才会往上层叠加轨迹规划、抓取、手眼标定这些内容。这个顺序虽然朴素但真的能省掉很多后面排错的时间。