3、正运动学求解:从关节空间到笛卡尔空间的映射

正运动学,说白了就是「已知关节角度,求末端位姿」。

我刚开始接触机器人时,总觉得这玩意儿很玄乎。后来自己手算了一遍六轴机器人的正解,才发现——其实就是一连串坐标变换的叠加。你给每个关节一个角度,我就能告诉你机器人手爪在哪儿、朝哪个方向。

这一章,我们就拿六轴工业机器人开刀,把正运动学的底裤扒干净。

3.1 什么是正运动学?

先看一个简单问题:

你有一个两连杆机构,肩关节转 30°,肘关节转 45°,末端在哪儿?

这就是正运动学——从关节空间(每个关节的角度值)映射到笛卡尔空间(末端的位置和姿态)。

对于六轴机器人,输入是六个关节角度 θ₁~θ₆,输出是末端坐标系相对于基坐标系的位姿矩阵 T₀₆。

核心公式:

T₀₆ = T₀₁(θ₁) · T₁₂(θ₂) · T₂₃(θ₃) · T₃₄(θ₄) · T₄₅(θ₅) · T₅₆(θ₆)

其中 Tᵢ₋₁ᵢ 是相邻连杆间的齐次变换矩阵。

你想想看,这其实就是把六个小变换串起来,得到一个总变换。每个小变换只依赖一个关节角度,其他参数(连杆长度、偏距、扭角)都是出厂就定死的。

3.2 六轴机器人的连杆参数

要建立正运动学模型,第一步是定义连杆参数。业内标准是 Denavit-Hartenberg 参数法,简称 DH 参数。

每个连杆需要四个参数:

参数 符号 含义 单位
连杆长度 aᵢ 沿 xᵢ 轴,从 zᵢ₋₁ 到 zᵢ 的距离 mm
连杆扭角 αᵢ 绕 xᵢ 轴,从 zᵢ₋₁ 到 zᵢ 的转角 °
关节偏距 dᵢ 沿 zᵢ₋₁ 轴,从 xᵢ₋₁ 到 xᵢ 的距离 mm
关节角度 θᵢ 绕 zᵢ₋₁ 轴,从 xᵢ₋₁ 到 xᵢ 的转角 °

我习惯把 DH 参数表列出来,再写代码。这样不容易乱。下面是一个典型六轴机器人的 DH 参数表:

关节 i aᵢ (mm) αᵢ (°) dᵢ (mm) θᵢ (°)
1 0 0 d₁ θ₁
2 a₂ -90 0 θ₂
3 a₃ 0 0 θ₃
4 0 -90 d₄ θ₄
5 0 90 0 θ₅
6 0 -90 d₆ θ₆

我的经验:DH 参数表一定要和机器人实际结构一一对应。我在项目中遇到过两次「正解算出来末端位置飞了」,最后发现是 α₂ 的符号搞反了。建议你拿到机器人图纸后,先手动对一对每个坐标轴的方向。

3.3 齐次变换矩阵的构建

有了 DH 参数,每个相邻连杆的变换矩阵就可以直接套公式:

Tᵢ₋₁ᵢ = Rot(z, θᵢ) · Trans(z, dᵢ) · Trans(x, aᵢ) · Rot(x, αᵢ)

展开成 4×4 矩阵:

Tᵢ₋₁ᵢ = [
  [cosθᵢ,  -sinθᵢ·cosαᵢ,   sinθᵢ·sinαᵢ,   aᵢ·cosθᵢ],
  [sinθᵢ,   cosθᵢ·cosαᵢ,  -cosθᵢ·sinαᵢ,   aᵢ·sinθᵢ],
  [0,        sinαᵢ,         cosαᵢ,          dᵢ      ],
  [0,        0,             0,              1       ]
]

嗯,这里要注意:矩阵里的 sin 和 cos 都是针对 θᵢ 和 αᵢ 的。别把顺序搞混了。

3.4 正运动学计算流程

整个计算流程其实就三步:

  1. 查表:从 DH 参数表取出 aᵢ, αᵢ, dᵢ, θᵢ
  2. 算矩阵:对每个关节 i,计算 Tᵢ₋₁ᵢ
  3. 连乘:T₀₆ = T₀₁ · T₁₂ · T₂₃ · T₃₄ · T₄₅ · T₅₆

我一般用 Python 写个函数,输入 θ₁~θ₆,输出 T₀₆。下面是我常用的代码骨架:

import numpy as np

def dh_transform(a, alpha, d, theta):
    ct = np.cos(theta)
    st = np.sin(theta)
    ca = np.cos(alpha)
    sa = np.sin(alpha)
    
    T = np.array([
        [ct, -st*ca,  st*sa,  a*ct],
        [st,  ct*ca, -ct*sa,  a*st],
        [0,   sa,     ca,     d   ],
        [0,   0,      0,      1   ]
    ])
    return T

def forward_kinematics(theta, dh_params):
    # dh_params: list of [a, alpha, d] for each joint
    T = np.eye(4)
    for i in range(6):
        a, alpha, d = dh_params[i]
        Ti = dh_transform(a, alpha, d, theta[i])
        T = T @ Ti
    return T

我曾经踩过的坑:用 Python 的 @ 运算符做矩阵乘法时,要确保两个矩阵都是 np.ndarray 类型。有一次我用了 list 嵌套,结果 @ 变成了逐元素乘法,末端位置直接偏了 200mm。排查了整整一个下午。

3.5 正运动学的几何意义

你可能会问:算出来一个 4×4 矩阵,然后呢?

T₀₆ 矩阵其实包含了两个信息:

  • 位置:矩阵右上角的 3×1 列向量,就是末端在基坐标系下的 x, y, z 坐标
  • 姿态:矩阵左上角的 3×3 旋转矩阵,表示末端坐标系相对于基坐标系的朝向

举个例子,如果 T₀₆ 是:

T₀₆ = [
  [0, -1,  0,  500],
  [1,  0,  0,  200],
  [0,  0,  1,  800],
  [0,  0,  0,    1]
]

那就意味着:末端在 (500, 200, 800) 这个位置,而且末端坐标系的 x 轴指向基坐标系的 y 轴方向(因为旋转矩阵第一列是 [0,1,0]ᵀ)。

说白了,正运动学就是帮你把「关节转了多少度」翻译成「手在哪儿、朝哪儿」。这是所有运动学控制的基础。

3.6 正运动学验证方法

代码写完了,怎么知道算得对不对?

我一般用两个方法:

  1. 零位验证:把所有关节角度设为零,看末端位置是否和机械图纸一致
  2. 单关节运动:只转动一个关节,看末端轨迹是否合理(比如转关节 1,末端应该在水平面画圆)

举个例子,零位时(所有 θᵢ = 0),我的机器人末端位置应该是 (a₂ + a₃, 0, d₁ - d₄ - d₆)。如果算出来不对,那就是 DH 参数或者矩阵顺序有问题。

我的习惯:每次改完 DH 参数,先用零位验证跑一遍。这步花不了 30 秒,但能省下后面调试的半天时间。

3.7 正运动学的知识体系

下面这张图是我自己总结的正运动学知识结构,帮你理清思路:

正运动学知识体系 正运动学求解 输入:关节角度 θ₁~θ₆ 核心:DH 参数表 输出:位姿矩阵 T₀₆ 连杆长度 aᵢ 连杆扭角 αᵢ 关节偏距 dᵢ 关节角度 θᵢ 步骤1:构建 Tᵢ₋₁ᵢ 步骤2:连乘 T₀₆ 步骤3:提取位姿 验证:零位验证 + 单关节运动 核心思想:关节空间 → 笛卡尔空间,本质是坐标变换的链式乘法

3.8 常见问题与避坑

最后,分享几个我实际项目中遇到的坑:

  • DH 参数符号搞反:α 的正负号决定了坐标轴旋转方向,错了末端姿态会完全不对
  • 矩阵乘法顺序:一定是 T₀₁ · T₁₂ · T₂₃ · ...,不能反过来
  • 角度单位:Python 的 math.sin 默认用弧度,DH 参数表里如果是角度,记得转弧度
  • 浮点误差:连乘六个矩阵后,旋转矩阵可能不再正交。我一般会做一次 Gram-Schmidt 正交化

我曾经犯过的错:有一次在产线上,机器人抓取位置总是偏 2mm。查了两天,最后发现是 DH 参数里的 d₆ 写成了 0,而实际末端执行器有 50mm 的偏距。从那以后,我每次拿到新机器人,第一件事就是拿尺子量一遍所有连杆尺寸。

正运动学是机器人学的基石。你把它吃透了,后面学逆运动学、轨迹规划、动力学都会轻松很多。这一章的内容,我建议你亲手算一遍——拿笔算一个关节的变换矩阵,再用代码验证。嗯,动手才是最快的理解方式。