4. 逆运动学数值解(迭代法)

任务目标

本案例实现六轴机械臂的逆运动学数值求解:给定末端目标位姿,使用雅可比矩阵的阻尼最小二乘迭代法(DLS)求解关节角,处理奇异位形下的数值稳定性,并对比迭代解与正运动学重投影的精度。

核心实现

import numpy as np

# 标准DH参数: [a, alpha, d, theta_offset]
DH = np.array([
    [0,     np.pi/2, 0.1625, 0],
    [-0.425, 0,      0,      0],
    [-0.3922, 0,      0,      0],
    [0,     np.pi/2, 0.1333, 0],
    [0,    -np.pi/2, 0.0997, 0],
    [0,     0,       0.0996, 0]
])

def fk(theta):
    """正运动学: 返回末端齐次变换矩阵 (4x4)"""
    T = np.eye(4)
    for i in range(6):
        a, alpha, d, offset = DH[i]
        th = theta[i] + offset
        ct, st = np.cos(th), np.sin(th)
        ca, sa = np.cos(alpha), np.sin(alpha)
        A = np.array([
            [ct, -st*ca,  st*sa, a*ct],
            [st,  ct*ca, -ct*sa, a*st],
            [0,   sa,     ca,     d   ],
            [0,   0,      0,      1   ]
        ])
        T = T @ A
    return T

def jacobian(theta):
    """数值雅可比 (6x6): 对每个关节用中心差分"""
    J = np.zeros((6, 6))
    delta = 1e-6
    T0 = fk(theta)
    p0 = T0[:3, 3]
    R0 = T0[:3, :3]
    for i in range(6):
        th_p = theta.copy(); th_p[i] += delta
        th_m = theta.copy(); th_m[i] -= delta
        Tp = fk(th_p); Tm = fk(th_m)
        # 位置部分
        J[:3, i] = (Tp[:3, 3] - Tm[:3, 3]) / (2 * delta)
        # 姿态部分 (旋转向量差)
        Rp = Tp[:3, :3] @ R0.T
        Rm = Tm[:3, :3] @ R0.T
        wp = np.array([Rp[2,1]-Rp[1,2], Rp[0,2]-Rp[2,0], Rp[1,0]-Rp[0,1]]) / 2
        wm = np.array([Rm[2,1]-Rm[1,2], Rm[0,2]-Rm[2,0], Rm[1,0]-Rm[0,1]]) / 2
        J[3:, i] = (wp - wm) / (2 * delta)
    return J

def ik_dls(T_target, theta_init, lambda_=0.01, tol=1e-6, max_iter=100):
    """阻尼最小二乘逆运动学求解"""
    theta = theta_init.copy()
    for _ in range(max_iter):
        T_cur = fk(theta)
        # 位置误差
        e_pos = T_target[:3, 3] - T_cur[:3, 3]
        # 姿态误差 (旋转向量)
        R_err = T_target[:3, :3] @ T_cur[:3, :3].T
        e_rot = 0.5 * np.array([
            R_err[2,1] - R_err[1,2],
            R_err[0,2] - R_err[2,0],
            R_err[1,0] - R_err[0,1]
        ])
        e = np.concatenate([e_pos, e_rot])
        if np.linalg.norm(e) < tol:
            break
        J = jacobian(theta)
        # DLS: (J^T J + lambda^2 I)^{-1} J^T
        JtJ = J.T @ J
        damping = lambda_**2 * np.eye(6)
        dtheta = np.linalg.solve(JtJ + damping, J.T @ e)
        theta += dtheta
    return theta

# ========== 验证 ==========
if __name__ == "__main__":
    # 目标位姿: 由一组已知关节角生成
    q_truth = np.array([0.5, -0.8, 0.3, 1.2, -0.6, 0.4])
    T_target = fk(q_truth)

    # 多组初始值测试
    np.random.seed(42)
    print("=== 逆运动学数值解验证 ===")
    for trial in range(3):
        q_init = np.random.uniform(-1.5, 1.5, 6)
        q_sol = ik_dls(T_target, q_init)
        T_sol = fk(q_sol)
        # 精度指标
        pos_err = np.linalg.norm(T_sol[:3,3] - T_target[:3,3])
        R_err = T_sol[:3,:3] @ T_target[:3,:3].T
        rot_err = np.linalg.norm(0.5*np.array([
            R_err[2,1]-R_err[1,2], R_err[0,2]-R_err[2,0], R_err[1,0]-R_err[0,1]
        ]))
        print(f"试验{trial+1}: 初始角={np.round(q_init,2)}")
        print(f"  解算角={np.round(q_sol,4)}")
        print(f"  位置误差={pos_err:.2e} m, 姿态误差={rot_err:.2e} rad")
        print(f"  关节角误差={np.linalg.norm(q_sol-q_truth):.2e} rad")

运行与验证

环境依赖:仅需 numpy(pip install numpy)。

运行方式:直接执行脚本:python ik_dls.py。

验证方法:

  • 程序使用随机初始值进行3次独立求解,每次均从同一目标位姿反解关节角。
  • 通过条件:位置误差 < 1e-5 m 且姿态误差 < 1e-5 rad,表明迭代收敛到正确解。
  • 关节角误差反映解的唯一性——由于数值迭代可能收敛到不同运动学位形,该值允许较大(>0.1 rad),但正运动学重投影误差必须满足上述精度。

奇异位形处理说明

当机械臂接近奇异位形时,雅可比矩阵接近奇异,J^T J 的条件数急剧增大。本实现采用阻尼最小二乘(DLS),通过在 J^T J 上叠加 λ²I 保证矩阵可逆,避免数值爆炸。阻尼系数 λ=0.01 在精度与稳定性间取得平衡——若增大λ则迭代更稳健但收敛精度下降,减小λ则反之。如需测试奇异位形,可将目标位姿设为机械臂完全伸展状态(如所有关节角为0),此时DLS仍能返回一个可行解而非发散。

DLS迭代法求解流程 输入目标位姿 T_d 初始化 θ₀ 正运动学 T(θ) 计算误差 e ||e||<tol? 是 输出 θ* 否 计算雅可比 J Δθ = (JᵀJ+λ²I)⁻¹Jᵀe θ ← θ + Δθ

常见问题:若迭代不收敛,首先检查目标位姿是否在机械臂工作空间内;其次适当增大阻尼系数λ至0.1可改善发散问题,但会牺牲精度;若初始值远离解,可尝试多组随机初始值并选取误差最小者。