4、机器人运动学基础:正运动学与逆运动学、DH参数法、在仿真中计算末端位姿

各位做具身智能的同行,咱们今天聊点硬核的——运动学。说实话,我见过不少做算法的同学,一提到运动学就头大,觉得那是机械专业的事。但你想,你让机器人去抓杯子,你总得知道胳膊往哪伸、关节转多少度吧?这就是运动学干的事。

我个人习惯把运动学分成两半:正运动学和逆运动学。正运动学是「已知关节角度,求末端位置」;逆运动学反过来,「已知末端位置,求关节角度」。听起来简单,但实际坑不少。咱们一个一个说。

4.1 正运动学:从关节到末端

正运动学说白了就是:你给机器人每个关节一个角度,它告诉你手在哪、朝哪。这个映射是唯一的,没有歧义。

我记得刚入行时,导师让我算一个六轴机械臂的正运动学。我吭哧吭哧用几何法推了半天,结果算出来末端位置差了好几厘米。后来才发现,我忘了考虑关节之间的偏置。嗯,这里要注意——关节坐标系之间的变换,必须用齐次变换矩阵。

一个典型的齐次变换矩阵长这样:

T = | R   p |
    | 0   1 |

其中 R 是 3x3 旋转矩阵,p 是 3x1 平移向量。把每个关节的变换矩阵连乘起来,就得到末端位姿:

T_0_n = T_0_1 * T_1_2 * ... * T_{n-1}_n

这个连乘顺序不能错。我在项目中遇到过有人把顺序搞反了,结果机器人直接往桌子底下钻。记住:左乘是相对于固定坐标系,右乘是相对于当前坐标系。正运动学里,我们通常用左乘。

核心要点:正运动学是唯一映射,计算简单,但矩阵连乘的精度和顺序是坑。仿真中建议用双精度浮点,避免累积误差。

4.2 DH参数法:标准化的建模工具

你可能会问:每个关节的变换矩阵怎么来的?总不能每次都手推吧?这就是 DH 参数法出场的时候了。

DH 参数法(Denavit-Hartenberg)是一种标准化的连杆坐标系建模方法。它用四个参数描述相邻两个关节之间的变换:

参数 符号 含义
连杆长度 ai 沿 xi 轴,从 zi-1 到 zi 的距离
连杆扭角 αi 绕 xi 轴,从 zi-1 到 zi 的转角
连杆偏距 di 沿 zi-1 轴,从 xi-1 到 xi 的距离
关节转角 θi 绕 zi-1 轴,从 xi-1 到 xi 的转角

有了这四个参数,相邻关节的变换矩阵就可以写成标准形式:

T_i = Rot(z, θ_i) * Trans(z, d_i) * Trans(x, a_i) * Rot(x, α_i)

展开后就是:

T_i = | cosθ_i  -sinθ_i*cosα_i   sinθ_i*sinα_i   a_i*cosθ_i |
      | sinθ_i   cosθ_i*cosα_i  -cosθ_i*sinα_i   a_i*sinθ_i |
      | 0        sinα_i           cosα_i           d_i        |
      | 0        0                0                1          |

我曾经在调试一个协作机器人时,发现末端位姿总是偏一点。查了半天,原来是 DH 参数表里一个 a 值写错了。所以我的建议是:建完 DH 表后,先手动验证几个特殊位姿,比如所有关节归零时,末端应该在什么位置。

小技巧:DH 参数有两种约定——标准 DH 和改进 DH。标准 DH 的变换顺序是 Rot(z)→Trans(z)→Trans(x)→Rot(x);改进 DH 是 Rot(x)→Trans(x)→Rot(z)→Trans(z)。仿真中我习惯用标准 DH,因为大多数开源库(如 PyBullet、MuJoCo)的 URDF 模型默认用标准 DH。

4.3 逆运动学:从末端到关节

逆运动学就麻烦多了。给定末端位姿,求关节角度——这个映射不是唯一的,甚至可能无解。

为什么?你想想看,一个六轴机械臂,末端在空间中有 6 个自由度(3 个位置 + 3 个姿态),但关节可能有冗余。比如七轴臂,就有无穷多组解。即使是非冗余的六轴臂,也可能有多个解(比如肘部向上或向下)。

求解逆运动学的方法主要有两类:

  1. 解析法:针对特定构型推导封闭解。速度快,但通用性差。比如常见的 6R 工业机器人,如果满足 Pieper 准则(三个相邻关节轴交于一点),就可以用解析法。
  2. 数值法:基于雅可比矩阵迭代求解。通用性强,但可能陷入局部最优或奇异点。

我在项目中遇到过最头疼的问题,就是数值法在奇异点附近发散。比如机械臂完全伸直时,末端沿某个方向的速度无法通过关节速度实现。这时候雅可比矩阵会降秩,迭代直接崩掉。

避坑指南:我曾经在仿真中没处理奇异点,结果逆运动学求解器直接输出 NaN,机器人关节飞到了极限位置。后来我加了阻尼最小二乘法(DLS),才稳住。具体做法是在雅可比矩阵的伪逆里加一个阻尼项:J# = JT(JJT + λ2I)-1。λ 取 0.01 到 0.1 之间,效果不错。

4.4 在仿真中计算末端位姿

理论说完了,咱们来点实际的。在仿真环境里怎么算末端位姿?

以 PyBullet 为例,假设你有一个 URDF 模型加载好了,获取末端位姿只需要几行代码:

import pybullet as p
import pybullet_data

# 连接仿真
p.connect(p.GUI)
p.setAdditionalSearchPath(pybullet_data.getDataPath())

# 加载机器人
robot_id = p.loadURDF("kuka_iiwa/model.urdf", [0, 0, 0])

# 设置关节角度
joint_positions = [0.1, -0.5, 0.3, 0.2, 0.1, -0.4, 0.0]
for i in range(len(joint_positions)):
    p.resetJointState(robot_id, i, joint_positions[i])

# 计算正运动学
# 获取末端连杆的索引(通常是最后一个关节的子连杆)
end_effector_index = 6  # 取决于URDF定义
link_state = p.getLinkState(robot_id, end_effector_index)

# 提取位置和姿态
position = link_state[0]  # 世界坐标系下的位置 (x, y, z)
orientation = link_state[1]  # 四元数 (x, y, z, w)

print(f"末端位置: {position}")
print(f"末端姿态(四元数): {orientation}")

这里要注意,getLinkState 返回的是世界坐标系下的位姿。如果你需要相对于某个坐标系,可以用 p.multiplyTransforms 做变换。

对于逆运动学,PyBullet 也提供了现成的接口:

# 逆运动学求解
target_position = [0.5, 0.3, 0.2]
target_orientation = p.getQuaternionFromEuler([0, 0, 0])

joint_angles = p.calculateInverseKinematics(
    robot_id,
    end_effector_index,
    target_position,
    target_orientation
)

print(f"逆运动学解: {joint_angles}")

这个接口底层用的是数值法,内部已经处理了奇异点。但如果你自己实现 IK,一定要加阻尼项。

实战建议:在仿真中调试运动学时,我习惯先可视化每个关节的坐标系。PyBullet 里可以用 p.addUserDebugLine 画出坐标轴,这样一眼就能看出变换对不对。另外,一定要验证正运动学和逆运动学的一致性——先正算再反算,看末端位姿能不能回到原点。

4.5 知识体系总览

为了让你对整个运动学知识体系有个直观印象,我画了张图:

机器人运动学知识体系 机器人运动学知识体系 运动学 正运动学 齐次变换矩阵 矩阵连乘 逆运动学 解析法 数值法 DH参数法(建模工具) 标准DH约定 改进DH约定 正运动学:唯一映射,计算简单 | 逆运动学:多解/无解,需处理奇异点 | DH参数:标准化建模,减少手推错误

这张图把运动学的核心脉络理清了。正运动学和逆运动学是两条腿,DH 参数法是建模的骨架。你在仿真中做联调时,这三块缺一不可。

最后说一句:运动学是机器人控制的基石。你后面做力矩控制、阻抗控制,都离不开它。别嫌基础,基础不牢,后面全是坑。我当年就是吃了这个亏,现在每次搭新机器人,第一件事就是把 DH 表拉出来过一遍。