4、正运动学求解:从基座到末端的齐次变换

正运动学,说白了就是:给定六个关节角度,算出末端在哪儿、朝哪儿。

我刚入行时觉得这玩意儿就是矩阵乘法,没啥技术含量。直到有一次在产线上调试,机器人末端总是差那么几毫米对不准工件——查了两天才发现,是某个连杆的DH参数符号搞反了。嗯,从那以后我再也不敢小看这一步了。

这一章,我们就来手撕6轴工业机器人的正运动学。核心工具就是上一章建立的DH参数,加上齐次变换矩阵的连乘。

4.1 齐次变换矩阵回顾

每个关节i到关节i+1的变换,可以用一个4x4矩阵表示:

T_i = Rot(z, θ_i) · Trans(z, d_i) · Trans(x, a_i) · Rot(x, α_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      |

我个人习惯把这个矩阵记作 A_i。每个A_i描述了从关节i坐标系到关节i+1坐标系的变换。

小技巧: 我写代码时喜欢把A_i拆成旋转部分R_i和平移部分p_i分开存储,调试时能快速定位是角度算错了还是连杆长度写错了。

4.2 6轴机器人DH参数表

以典型的6轴工业机器人为例(比如常见的六关节串联臂),DH参数表长这样:

关节i θ_i (关节变量) d_i (mm) a_i (mm) α_i (rad)
1 θ₁ d₁ a₁ -π/2
2 θ₂ 0 a₂ 0
3 θ₃ 0 a₃ -π/2
4 θ₄ d₄ 0 π/2
5 θ₅ 0 0 -π/2
6 θ₆ d₆ 0 0

注意:θ₁到θ₆是变量,其他参数(d₁、a₁、a₂、a₃、d₄、d₆)是机器人结构决定的常量。

避坑指南: 我曾经在项目里把α₄的符号搞反了(π/2写成了-π/2),结果末端姿态差了90度。建议每次写完参数表,先用一个已知角度组合(比如全部归零)手动验算一遍。

4.3 正运动学推导:矩阵连乘

从基座到末端,总变换矩阵就是6个A_i的乘积:

T_06 = A_1 · A_2 · A_3 · A_4 · A_5 · A_6

这个T_06是一个4x4矩阵:

| R_06(3x3)   p_06(3x1) |
|    0 0 0        1      |

其中:

  • R_06:末端坐标系相对于基座的旋转矩阵(表示姿态)
  • p_06:末端坐标系原点在基座坐标系中的位置(表示位置)

我建议你推导时不要一次性把6个矩阵全乘开——那会得到一个巨长的表达式,容易出错。我的做法是:

  1. 先算前两个:T_02 = A_1 · A_2
  2. 再乘第三个:T_03 = T_02 · A_3
  3. 依次类推,直到T_06

这样每一步的中间结果都可以打印出来检查。

4.4 位置与姿态的解析计算

最终T_06矩阵中,位置向量p_06就是末端在基座坐标系下的三维坐标(x, y, z)。

姿态则可以从旋转矩阵R_06中提取。常用的表示方式有两种:

表示方式 提取方法 适用场景
欧拉角 (RPY) 从R_06计算绕固定轴的旋转角 人机交互、示教
四元数 从R_06的迹和元素计算 插值、控制

我个人更推荐在内部计算时用四元数,因为避免了万向锁问题。但给用户看的时候,欧拉角更直观。

4.5 代码实现

下面是一个Python实现的核心片段。我习惯用numpy来操作矩阵:

import numpy as np

def dh_transform(theta, d, a, alpha):
    """根据DH参数生成单个齐次变换矩阵"""
    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(joint_angles, dh_params):
    """
    正运动学计算
    joint_angles: [θ1, θ2, θ3, θ4, θ5, θ6]
    dh_params: 每行 [d, a, alpha] 的列表(不含theta)
    """
    T = np.eye(4)
    for i in range(6):
        theta = joint_angles[i]
        d, a, alpha = dh_params[i]
        Ai = dh_transform(theta, d, a, alpha)
        T = T @ Ai
    return T  # 返回4x4齐次变换矩阵
关键点: 代码中的 T = T @ Ai 是左乘——每次将新的关节变换乘到当前累积变换的右侧。这对应了从基座到末端的链式传递。

4.6 验证:一个简单例子

假设所有关节角度都为零:

joint_angles = [0, 0, 0, 0, 0, 0]
T_06 = forward_kinematics(joint_angles, dh_params)
print(T_06)

你会得到一个矩阵,其位置向量p_06应该等于所有连杆长度在基座坐标系下的投影之和。如果结果不对,先检查DH参数表中的d和a值是否与机器人实际结构一致。

我记得有一次,学生拿到的机器人手册里d₁和a₁的定义方式和标准DH不一样,结果算出来末端位置差了半个手臂的长度。所以拿到任何机器人的参数,第一件事就是确认DH约定。

4.7 本章知识体系

下面这张图总结了正运动学的核心流程:

正运动学求解流程 关节角度 θ₁~θ₆ DH参数表 (d, a, α) 计算每个A_i矩阵 T_06 = A₁·A₂·A₃·A₄·A₅·A₆ 末端位置p + 姿态R 输入 6个关节角度值 机器人结构参数 核心计算 6次DH变换矩阵生成 5次矩阵乘法 输出 位置: (x, y, z) 姿态: 旋转矩阵/欧拉角/四元数

正运动学是整个运动控制的基础。你想想看,如果连末端在哪儿都算不准,后面的轨迹规划、逆运动学、力控制全都是空中楼阁。所以这一章的内容,值得你花时间亲手推导一遍、写一遍代码、再验证一遍。