4. 运动学与动力学基础:正/逆运动学、雅可比矩阵、动力学建模(拉格朗日法)

各位同学,欢迎来到第四章。这一章,我们要啃一块硬骨头——运动学与动力学。

说实话,很多做机器人控制的朋友,一听到「运动学」三个字就开始头疼。我当年刚入行时也一样,觉得这东西太数学了,离实际代码很远。直到有一次,我调试一个六轴机械臂的抓取动作,发现末端怎么都到不了目标位置……折腾了两天,最后发现是逆运动学解算时选错了分支。嗯,从那以后,我再也不敢轻视运动学了。

这一章,我会尽量用「工程师的直觉」来讲。咱们不搞纯数学推导,而是把每个概念和实际代码、调试经验结合起来。你想想看,搞懂了这些,你就能让机器人「指哪打哪」了。

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

正运动学,说白了就是:已知每个关节的角度,求末端执行器的位置和姿态

举个例子。你有一个两连杆的机械臂,肩关节转了30度,肘关节转了45度。那么手在哪儿?这就是正运动学要回答的问题。

我个人习惯用 DH参数法(Denavit-Hartenberg)来建模。这个方法的核心是:给每个关节建立一个坐标系,然后用四个参数来描述相邻坐标系之间的变换。

DH参数四要素:
  • θ(关节角):绕Z轴的旋转角
  • d(连杆偏距):沿Z轴的平移距离
  • a(连杆长度):沿X轴的平移距离
  • α(连杆扭角):绕X轴的旋转角

有了这四个参数,我们就可以写出相邻关节的齐次变换矩阵。把所有矩阵乘起来,就得到了末端相对于基座的位姿。

代码实现其实很直观。我常用的一个Python示例是这样的:

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

# 示例:两连杆机械臂
theta1, theta2 = np.radians([30, 45])
d1, d2 = 0, 0
a1, a2 = 1.0, 0.8
alpha1, alpha2 = 0, 0

T01 = dh_transform(theta1, d1, a1, alpha1)
T12 = dh_transform(theta2, d2, a2, alpha2)
T02 = T01 @ T12  # 末端位姿

print("末端位置:", T02[:3, 3])

你看,代码就这么几行。但实际项目中,关节数一多(比如六轴),矩阵乘法就容易出错。我建议你写一个循环,别手写一串矩阵乘法——我曾经吃过这个亏,debug到怀疑人生。

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

逆运动学是正运动学的反向问题:已知末端位姿,求每个关节的角度

这比正运动学难多了。为什么?因为正运动学有唯一解,而逆运动学可能有多个解、一个解,甚至无解。

你想想看,一个六轴机械臂,末端在同一个位置,手臂可以「弯着」够到,也可以「伸着」够到。这就是多解问题。

避坑指南: 我曾经在产线上遇到过一个问题——机器人每次运动到同一个目标点,但关节角度却不一样。后来发现是逆运动学求解器每次随机选了一个解,导致运动轨迹不重复。解决方案是:加入「关节角度最小变化」的约束,让求解器优先选择离当前关节角最近的解。

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

  • 解析法:通过几何关系直接推导出公式。速度快,但只适用于特定构型的机器人(比如有相邻关节轴线相交的情况)。
  • 数值法:用迭代逼近的方式求解。通用性强,但计算量大,且可能不收敛。

我个人在实际项目中,更常用数值法,尤其是 雅可比矩阵伪逆法。它的思路是:

  1. 给定一个初始关节角猜测
  2. 计算当前末端位姿与目标位姿的误差
  3. 用雅可比矩阵的伪逆,把误差映射回关节空间,得到关节角的修正量
  4. 迭代直到误差足够小

代码实现大致如下:

def inverse_kinematics_numerical(target_pose, initial_q, max_iter=100, tol=1e-6):
    q = initial_q.copy()
    for i in range(max_iter):
        # 正运动学:计算当前末端位姿
        T_current = forward_kinematics(q)
        # 计算位姿误差(位置+姿态)
        error = compute_pose_error(target_pose, T_current)
        if np.linalg.norm(error) < tol:
            break
        # 计算雅可比矩阵
        J = compute_jacobian(q)
        # 伪逆求解关节角增量
        dq = np.linalg.pinv(J) @ error
        q += dq
    return q

这里有个关键点:雅可比矩阵的伪逆。当机器人处于奇异位形时,雅可比矩阵会降秩,伪逆就不稳定了。我建议你在代码里加一个阻尼因子(Damped Least Squares),能有效避免这个问题。

4.3 雅可比矩阵:速度与力的映射

雅可比矩阵,是连接关节空间和操作空间的桥梁。

它有两个核心作用:

  • 速度映射:关节速度 → 末端速度
  • 力映射:末端力 → 关节力矩

说白了,雅可比矩阵就是「关节运动」和「末端运动」之间的线性变换。你转动关节,末端怎么动?雅可比矩阵告诉你。

它的数学形式是:

v = J(q) · q̇

其中 v 是末端速度(线速度+角速度),q̇ 是关节速度。

计算雅可比矩阵,我习惯用 向量积法。对于旋转关节,雅可比矩阵的每一列可以这样算:

def compute_jacobian(q):
    """计算六轴机械臂的雅可比矩阵(简化版)"""
    n = len(q)
    J = np.zeros((6, n))
    # 计算每个关节的变换矩阵
    T = [np.eye(4)]
    for i in range(n):
        T.append(T[-1] @ dh_transform(q[i], d[i], a[i], alpha[i]))
    
    # 末端位置
    p_end = T[-1][:3, 3]
    
    for i in range(n):
        # 关节i的Z轴方向(在基坐标系下)
        z_i = T[i][:3, 2]
        # 关节i到末端的位置向量
        p_i = T[i][:3, 3]
        r = p_end - p_i
        
        # 线速度部分:z_i × r
        J[:3, i] = np.cross(z_i, r)
        # 角速度部分:z_i
        J[3:, i] = z_i
    
    return J
经验之谈: 调试雅可比矩阵时,可以用「数值微分法」来验证你的解析雅可比是否正确。具体做法是:给每个关节加一个微小扰动,看末端位置的变化量,然后和雅可比矩阵计算的结果对比。如果误差在1e-6量级,基本就对了。

4.4 动力学建模:拉格朗日法

运动学只关心「位置、速度、加速度」的几何关系,不关心「力」。但机器人要动起来,必须有力。这就是动力学要解决的问题。

动力学建模的方法有很多,我个人最常用的是 拉格朗日法。为什么?因为它基于能量,推导过程相对系统化,不容易出错。

拉格朗日法的核心公式很简单:

L = T - V

其中 L 是拉格朗日量,T 是系统动能,V 是系统势能。

然后,动力学方程由欧拉-拉格朗日方程给出:

d/dt (∂L/∂q̇) - ∂L/∂q = τ

其中 τ 是关节驱动力矩。

看着有点抽象?咱们拿一个最简单的单摆来举例。

单摆的动能 T = ½ m l² θ̇²,势能 V = m g l (1 - cosθ)。

代入拉格朗日方程,得到:

m l² θ̈ + m g l sinθ = τ

这就是单摆的动力学方程。你看,推导过程很清晰。

对于多关节机器人,拉格朗日法会得到一个更通用的形式:

M(q) q̈ + C(q, q̇) q̇ + G(q) = τ

其中:

  • M(q):惯性矩阵,描述质量分布
  • C(q, q̇):科里奥利力和离心力矩阵
  • G(q):重力项
实际项目中的坑: 我做过一个移动机械臂的项目,动力学模型里忽略了科里奥利力项。结果在高速运动时,末端轨迹偏差越来越大。后来加上 C 矩阵,问题就解决了。所以,如果你的机器人运动速度较快,千万别省掉科里奥利力。

拉格朗日法的推导虽然系统,但手动推导多关节机器人时,计算量非常大。我建议你用符号计算工具(比如 SymPy)来辅助:

import sympy as sp

# 定义符号变量
theta1, theta2 = sp.symbols('theta1 theta2')
dtheta1, dtheta2 = sp.symbols('dtheta1 dtheta2')
m1, m2, l1, l2, g = sp.symbols('m1 m2 l1 l2 g')

# 计算动能和势能(以两连杆为例)
# 这里省略具体表达式,实际项目中用符号推导
T = 0.5 * m1 * l1**2 * dtheta1**2 + ...
V = m1 * g * l1 * (1 - sp.cos(theta1)) + ...

# 拉格朗日量
L = T - V

# 欧拉-拉格朗日方程
# 对每个关节求导
eq1 = sp.diff(sp.diff(L, dtheta1), 't') - sp.diff(L, theta1)
eq2 = sp.diff(sp.diff(L, dtheta2), 't') - sp.diff(L, theta2)

# 化简得到 M, C, G 矩阵

用符号推导的好处是,你可以直接得到 M、C、G 的解析表达式,然后转成 C++ 或 Python 代码,效率很高。

4.5 本章小结

这一章的内容,信息量确实不小。咱们来捋一捋:

  • 正运动学:从关节到末端,用 DH 参数和齐次变换矩阵搞定
  • 逆运动学:从末端到关节,解析法快但受限,数值法通用但要注意奇异点
  • 雅可比矩阵:关节空间和操作空间之间的速度/力映射,调试时用数值微分验证
  • 动力学建模:拉格朗日法基于能量,推导系统化,建议用符号计算工具辅助

这些知识,是后续做轨迹规划、力控制、阻抗控制的基础。你想想看,没有运动学,你连机器人该往哪儿走都不知道;没有动力学,你连该用多大的力都算不出来。

嗯,这一章就到这儿。下一章咱们会把这些知识串起来,讲轨迹规划——让机器人真正「动起来」。


专注资料整理