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 个姿态),但关节可能有冗余。比如七轴臂,就有无穷多组解。即使是非冗余的六轴臂,也可能有多个解(比如肘部向上或向下)。
求解逆运动学的方法主要有两类:
- 解析法:针对特定构型推导封闭解。速度快,但通用性差。比如常见的 6R 工业机器人,如果满足 Pieper 准则(三个相邻关节轴交于一点),就可以用解析法。
- 数值法:基于雅可比矩阵迭代求解。通用性强,但可能陷入局部最优或奇异点。
我在项目中遇到过最头疼的问题,就是数值法在奇异点附近发散。比如机械臂完全伸直时,末端沿某个方向的速度无法通过关节速度实现。这时候雅可比矩阵会降秩,迭代直接崩掉。
避坑指南:我曾经在仿真中没处理奇异点,结果逆运动学求解器直接输出 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 表拉出来过一遍。