3. 逆运动学解析解实现

任务目标

针对标准6R关节型机械臂(后三轴交于一点,满足Pieper准则),实现逆运动学解析解,给定末端位姿矩阵,求解全部8组关节角候选解,并验证正逆解互逆性。

构型与坐标系约定

采用改进DH参数,典型构型如下(单位:mm,角度:°):

| i | alpha(i-1) | a(i-1) | d(i) | theta(i) |
|---|------------|--------|------|----------|
| 1 |    0       |   0    | 150  | theta1   |
| 2 |  -90       |   0    |   0  | theta2   |
| 3 |    0       | 400    |   0  | theta3   |
| 4 |  -90       |   0    | 430  | theta4   |
| 5 |   90       |   0    |   0  | theta5   |
| 6 |  -90       |   0    |  80  | theta6   |

末端位姿矩阵T06为4×4齐次变换矩阵,由正运动学计算得到。

解析解推导要点

利用腕心分离法:

  1. 求theta1:由腕心位置p_w = p_06 - d6 * R_06 * [0,0,1]^T,得theta1 = atan2(p_w.y, p_w.x),存在两个解(±180°偏移)。
  2. 求theta2、theta3:在theta1确定后,将问题转化为平面二连杆求角,利用余弦定理得theta3两个解,再由几何关系求theta2。
  3. 求theta4、theta5、theta6:由R36 = R30^T * R06,根据欧拉角(ZYZ)分解得theta4、theta5、theta6,其中theta4和theta6各有两个解,theta5由atan2(sqrt(r31^2+r32^2), r33)确定。

组合后共8组候选解(部分构型可能退化)。

完整实现代码

import numpy as np
from numpy import sin, cos, arctan2, sqrt, pi

# 改进DH参数
DH = [
    {'alpha': 0,    'a': 0,   'd': 150, 'theta_offset': 0},
    {'alpha': -pi/2,'a': 0,   'd': 0,   'theta_offset': 0},
    {'alpha': 0,    'a': 400, 'd': 0,   'theta_offset': 0},
    {'alpha': -pi/2,'a': 0,   'd': 430, 'theta_offset': 0},
    {'alpha': pi/2, 'a': 0,   'd': 0,   'theta_offset': 0},
    {'alpha': -pi/2,'a': 0,   'd': 80,  'theta_offset': 0},
]

def dh_transform(theta, dh_param):
    """计算单个DH变换矩阵"""
    alpha = dh_param['alpha']
    a = dh_param['a']
    d = dh_param['d']
    ct, st = cos(theta), sin(theta)
    ca, sa = cos(alpha), sin(alpha)
    return np.array([
        [ct, -st*ca,  st*sa, a*ct],
        [st,  ct*ca, -ct*sa, a*st],
        [0,      sa,     ca,    d],
        [0,       0,      0,    1]
    ])

def forward_kinematics(q):
    """正运动学:关节角列表 -> T06"""
    T = np.eye(4)
    for i in range(6):
        T = T @ dh_transform(q[i], DH[i])
    return T

def inverse_kinematics(T06):
    """逆运动学解析解,返回所有候选解列表(每个解为6个关节角)"""
    solutions = []
    # 提取末端位置和姿态
    p = T06[:3, 3]
    R = T06[:3, :3]
    d6 = DH[5]['d']
    
    # 腕心位置
    pw = p - d6 * R @ np.array([0, 0, 1.0])
    px, py, pz = pw
    
    # ---- theta1 ----
    r = sqrt(px**2 + py**2)
    if r < 1e-6:
        return []  # 奇异:腕心在轴1轴线上
    theta1_candidates = [arctan2(py, px), arctan2(-py, -px)]
    
    for theta1 in theta1_candidates:
        c1, s1 = cos(theta1), sin(theta1)
        
        # ---- theta3 ----
        # 在theta1确定后,计算theta2/theta3平面的几何参数
        # 从基座到腕心在平面内的投影
        x_plane = c1*px + s1*py  # 沿x1方向
        z_plane = pz             # 沿z0方向
        # 考虑DH偏移:a1=0, d1=150, a2=0, d2=0
        # 腕心相对关节2坐标系的位置
        xw = x_plane
        zw = z_plane - DH[0]['d']
        
        # 二连杆参数:L1=a2=400, L2=a3=400(实际a3=0但d4=430偏移)
        # 注意:关节3后d4=430沿z3方向,需在平面中处理
        # 简化:将d4视为连杆长度的一部分(Pieper准则)
        L1 = DH[2]['a']  # 400
        L2 = DH[3]['d']  # 430
        
        D = (xw**2 + zw**2 - L1**2 - L2**2) / (2*L1*L2)
        D = np.clip(D, -1.0, 1.0)
        
        theta3_1 = arctan2(sqrt(1-D**2), D)
        theta3_2 = arctan2(-sqrt(1-D**2), D)
        
        for theta3 in [theta3_1, theta3_2]:
            # ---- theta2 ----
            phi = arctan2(zw, xw)
            psi = arctan2(L2*sin(theta3), L1 + L2*cos(theta3))
            theta2 = phi - psi
            
            # ---- theta4, theta5, theta6 ----
            # 计算R30(前三个关节的旋转矩阵)
            T01 = dh_transform(theta1, DH[0])
            T12 = dh_transform(theta2, DH[1])
            T23 = dh_transform(theta3, DH[2])
            R30 = (T01 @ T12 @ T23)[:3, :3]
            
            # R36 = R30^T * R06
            R36 = R30.T @ R
            
            # ZYZ欧拉角分解
            r31 = R36[2, 0]
            r32 = R36[2, 1]
            r33 = R36[2, 2]
            
            theta5 = arctan2(sqrt(r31**2 + r32**2), r33)
            
            if abs(theta5) < 1e-6:
                # 奇异:theta5=0,theta4和theta6退化
                theta4 = 0.0
                theta6 = arctan2(-R36[0, 1], R36[0, 0])
                solutions.append([theta1, theta2, theta3, theta4, theta5, theta6])
            elif abs(theta5 - pi) < 1e-6:
                theta4 = 0.0
                theta6 = arctan2(R36[0, 1], -R36[0, 0])
                solutions.append([theta1, theta2, theta3, theta4, theta5, theta6])
            else:
                for theta5_sign in [1.0, -1.0]:
                    theta5_cur = theta5_sign * theta5
                    theta4 = arctan2(r32/sin(theta5_cur), r31/sin(theta5_cur))
                    theta6 = arctan2(R36[1, 2]/sin(theta5_cur), -R36[0, 2]/sin(theta5_cur))
                    solutions.append([theta1, theta2, theta3, theta4, theta5_cur, theta6])
    
    # 去重(数值容差)
    unique_solutions = []
    for sol in solutions:
        if not any(np.allclose(sol, existing, atol=1e-6) for existing in unique_solutions):
            unique_solutions.append(sol)
    
    return unique_solutions

def verify_solution(q, T06_target):
    """验证单组解的正逆互逆性"""
    T_calc = forward_kinematics(q)
    return np.allclose(T_calc, T06_target, atol=1e-6)

# ============ 测试与验证 ============
if __name__ == "__main__":
    # 测试1:随机生成关节角 -> 正解 -> 逆解 -> 验证
    np.random.seed(42)
    q_test = np.random.uniform(-pi, pi, 6)
    T_target = forward_kinematics(q_test)
    
    print("目标关节角:", np.round(q_test, 4))
    print("目标位姿矩阵:\n", np.round(T_target, 4))
    
    all_solutions = inverse_kinematics(T_target)
    print(f"\n找到 {len(all_solutions)} 组候选解")
    
    # 验证所有解
    valid_count = 0
    for i, q_sol in enumerate(all_solutions):
        is_valid = verify_solution(q_sol, T_target)
        if is_valid:
            valid_count += 1
        print(f"解{i+1}: {np.round(q_sol, 4)}  正逆互验: {'通过' if is_valid else '失败'}")
    
    print(f"\n验证结果: {valid_count}/{len(all_solutions)} 组解通过正逆互验")
    
    # 测试2:检查是否包含原始关节角(或等效解)
    found_original = any(np.allclose(q_test, sol, atol=1e-4) for sol in all_solutions)
    print(f"原始关节角是否在解集中: {'是' if found_original else '否(可能为等效解)'}")

运行与验证

运行方式:

python ik_solver.py

验证方法:

  1. 程序随机生成一组关节角,通过正运动学得到目标位姿矩阵;
  2. 调用逆运动学求解器,输出全部候选解;
  3. 对每组候选解重新执行正运动学,与目标位姿矩阵比较,误差小于1e-6即判定通过;
  4. 检查原始关节角是否出现在解集中(由于多解性,可能以等效解形式出现)。

通过条件:所有输出的候选解均通过正逆互验,且解的数量在1~8之间(非奇异情况下通常为8组)。

多解情况说明

解析解天然产生多组候选解:theta1有2个候选,theta3有2个候选,theta4/theta6组合有2种(theta5非奇异时),共2×2×2=8组。实际使用时需根据关节限位、避障等约束筛选最优解。本实现已包含数值去重逻辑,避免重复输出相同解。