5DOF 运动学约束

相关源文件

以下文件用作生成此 wiki 页面的上下文:

本文档介绍 IB-Robot 框架中 5 自由度(DOF)机械臂的运动学约束处理系统。SO101 机械臂只有 5 个驱动关节,但末端执行器位姿命令指定 6DOF(3 位置 + 3 姿态)。本文档解释数学问题、MoveItGateway 中实现的约束松弛策略,以及成功逆运动学(IK)求解所需的配置。

有关 MoveItGateway 节点的整体架构和位姿命令接口信息,请参阅 MoveItGateway 节点。有关 MoveIt 启动配置和控制器设置,请参阅 MoveIt 启动配置


5DOF 问题

数学约束

6DOF 位姿规范提供 6 个约束:- 位置:3 个约束(x, y, z)- 姿态:3 个约束(roll, pitch, yaw)

5DOF 机械臂只有 5 个关节变量。标准 IK 求解器尝试同时满足所有 6 个约束将因系统过度约束(6 个方程,5 个未知数)而失败并返回 NO_IK_SOLUTION 错误。

运动学限制

SO101 机械臂的 5 个旋转关节可以控制:- 3 DOF 用于末端执行器位置 - 2 DOF 用于末端执行器姿态(通常是工具轴的方向)- 缺失:1 DOF 用于绕工具轴旋转(roll)

这意味着末端执行器无法实现任意 6DOF 位姿。对于大多数目标姿态,不存在精确解。

来源src/robot_moveit/docs/moveit_gateway.md:22-51


配置:仅位置 IK

运动学配置

主要解决方案是在 KDL 运动学求解器中启用仅位置 IK 模式:

arm:
  kinematics_solver: kdl_kinematics_plugin/KDLKinematicsPlugin
  kinematics_solver_search_resolution: 0.01
  kinematics_solver_timeout: 2.0
  kinematics_solver_attempts: 50
  position_only_ik: True

来源src/robot_moveit/config/lerobot/so101/kinematics.yaml:1-7

position_only_ik: True 时的求解器行为

方面

标准模式

仅位置模式

目标

最小化位置 + 姿态误差

仅最小化位置误差

** 姿态输入**

硬约束, 无法到达时失败

用作解选择的提示

解 空间

需要精确 6DOF 匹配

任何达到位置目标的姿态

成功 率

5DOF 机械臂低 (~10-30%)

高(~80-95%)

求解器 类

`` ChainIkSolverPos_NR``

ChainIkSolverPos

传递给 compute_ik_async() 的姿态四元数仍然影响从满足位置的配置集中选择哪个解,但它不强制严格的姿态匹配。

来源src/robot_moveit/docs/moveit_gateway.md:33-58


姿态预处理策略

即使启用了 position_only_ik: TrueMoveItGateway 节点仍应用姿态预处理以改善 IK 收敛性,并引导求解器朝向运动学可行的姿态。

策略 1:仅 Z 轴约束

方法constrain_to_z_axis_only(quat)

原理:- 保留末端执行器 Z 轴方向(2 DOF:pitch + yaw)- 释放绕 Z 轴的旋转(1 DOF:roll)- 使用最小旋转原理保持接近原始姿态

算法

1. 将四元数转换为旋转矩阵 R
2. 提取并归一化 Z 轴:z = R[:, 2]
3. 将原始 X 轴投影到垂直于 Z 的平面:x' = x - (x·z)z
4. 重新正交化:y' = z × x'
5. 重建旋转矩阵 R' = [x', y', z]
6. 转换回四元数

来源src/robot_moveit/scripts/moveit_gateway.py:118-175

        graph TD
    InputQuat["Input Quaternion<br/>(x, y, z, w)"]
    ToMatrix["quaternion_to_rotation_matrix()"]
    ExtractZ["Extract Z-axis<br/>z = R[:, 2]"]
    NormalizeZ["Normalize Z-axis"]
    ProjectX["Project X-axis to<br/>perpendicular plane<br/>x' = x - (x·z)z"]
    CrossY["Compute Y-axis<br/>y' = z × x'"]
    RebuildMatrix["Rebuild rotation matrix<br/>R' = [x', y', z]"]
    ToQuat["rotation_matrix_to_quaternion()"]
    OutputQuat["Constrained Quaternion<br/>(x', y', z', w')"]

    InputQuat --> ToMatrix
    ToMatrix --> ExtractZ
    ExtractZ --> NormalizeZ
    NormalizeZ --> ProjectX
    ProjectX --> CrossY
    CrossY --> RebuildMatrix
    RebuildMatrix --> ToQuat
    ToQuat --> OutputQuat
    

图:Z 轴约束算法

来源src/robot_moveit/scripts/moveit_gateway.py:118-175


策略 2:肩部 XZ 平面投影

方法project_orientation_to_shoulder_xz_plane(quat)

原理:- 将姿态变换到肩部坐标系 - 约束旋转轴位于肩部 XZ 平面(Y 分量 = 0)- 变换回基座坐标系

此方法具有几何动机:SO101 的运动链由于其串联结构,自然与肩部 XZ 平面对齐。

算法

1. 查找 TF:base → shoulder(旋转 Q_b2s)
2. 变换姿态:q_shoulder = Q_b2s * q_base
3. 转换为旋转矩阵:R_shoulder
4. 将 Y 分量置零:x_axis[1] = 0, z_axis[1] = 0
5. 重新归一化和正交化
6. 转换回:q_shoulder_constrained
7. 变换到基座:q_base_constrained = Q_s2b * q_shoulder_constrained

来源src/robot_moveit/scripts/moveit_gateway.py:177-271

        graph TB
    InputQuat["Input Quaternion<br/>in base frame"]
    LookupTF["tf_buffer.lookup_transform()<br/>base → shoulder"]
    Transform1["quaternion_multiply()<br/>q_shoulder = Q_b2s * q_base"]
    ToMatrix["quaternion_to_rotation_matrix()"]
    ZeroY["Zero Y components<br/>x[1]=0, z[1]=0"]
    Renormalize["Renormalize x, z axes"]
    CrossY["y = z × x"]
    RebuildMatrix["Rebuild matrix R'"]
    ToQuat["rotation_matrix_to_quaternion()"]
    Transform2["quaternion_multiply()<br/>q_base' = Q_s2b * q_shoulder'"]
    OutputQuat["Constrained Quaternion<br/>in base frame"]

    InputQuat --> LookupTF
    LookupTF --> Transform1
    Transform1 --> ToMatrix
    ToMatrix --> ZeroY
    ZeroY --> Renormalize
    Renormalize --> CrossY
    CrossY --> RebuildMatrix
    RebuildMatrix --> ToQuat
    ToQuat --> Transform2
    Transform2 --> OutputQuat
    

图:肩部 XZ 平面投影算法

来源src/robot_moveit/scripts/moveit_gateway.py:177-271


分层容差策略

当姿态约束通过 Constraints 消息传递给 IK 求解器时,求解器验证最终解满足容差边界。MoveItGateway 实现渐进式松弛策略。

容差级别

策略名称

X/Y 容差 (rad)

Z 容差 (rad)

角度(约)

用例

  • 严格*

0.1

0.05

±5.7° (XY), ±2.8° (Z)

高精度 任务

  • 中等*

0.3

0.1

±17° (XY), ±5.7° (Z)

通用 操作

宽 松

0.5

0.15

±28° (XY), ±8.6° (Z)

粗略 定位

仅 Z 轴

1.0

0.2

±57° (XY), ±11.5° (Z)

方向 重要, roll 自由

无 约束

None

None

N/A

仅位置

注意:X 和 Y 容差较大(松弛 roll)是因为 5DOF 机械臂缺乏控制绕 Z 轴旋转的自由度。Z 容差较小以保持工具方向。

来源src/robot_moveit/scripts/moveit_gateway.py:397-404src/robot_moveit/docs/moveit_gateway.md:73-82

姿态约束消息

create_orientation_constraint() 方法构建 OrientationConstraint 消息:

constraint = OrientationConstraint()
constraint.header.frame_id = frame_id           # "base"
constraint.link_name = link_name                # "gripper"
constraint.orientation = target_quat            # (x, y, z, w)
constraint.absolute_x_axis_tolerance = tol_x
constraint.absolute_y_axis_tolerance = tol_y
constraint.absolute_z_axis_tolerance = tol_z
constraint.weight = 1.0

来源src/robot_moveit/scripts/moveit_gateway.py:273-306


多策略回退工作流

cmd_pose_callback() 实现嵌套回退循环:

For each orientation preprocessing strategy:
    For each tolerance level:
        Try IK solve
        If success: execute and return
        Else: continue to next tolerance
    Next strategy
Report failure if all combinations fail

回退序列

        graph TD
    Start["Receive /cmd_pose"]
    S1["Strategy 1:<br/>constrain_to_z_axis_only()"]
    S2["Strategy 2:<br/>project_orientation_to_shoulder_xz_plane()"]
    S3["Strategy 3:<br/>Current EE orientation<br/>(position-only move)"]
    S4["Strategy 4:<br/>Default orientation<br/>(0, 0, 0, 1)"]

    T1["Tolerance Loop:<br/>Strict → Medium → Relaxed<br/>→ Z-only → None"]
    T2["Tolerance Loop:<br/>Strict → Medium → Relaxed<br/>→ Z-only → None"]
    T3["Tolerance Loop:<br/>Strict → Medium → Relaxed<br/>→ Z-only → None"]
    T4["Tolerance Loop:<br/>Strict → Medium → Relaxed<br/>→ Z-only → None"]

    Solve1["solve_and_move()"]
    Solve2["solve_and_move()"]
    Solve3["solve_and_move()"]
    Solve4["solve_and_move()"]

    Success["IK Success<br/>Execute Motion"]
    Failure["All strategies failed<br/>Log error"]

    Start --> S1
    S1 --> T1
    T1 --> Solve1
    Solve1 -->|Success| Success
    Solve1 -->|Fail all tolerances| S2

    S2 --> T2
    T2 --> Solve2
    Solve2 -->|Success| Success
    Solve2 -->|Fail all tolerances| S3

    S3 --> T3
    T3 --> Solve3
    Solve3 -->|Success| Success
    Solve3 -->|Fail all tolerances| S4

    S4 --> T4
    T4 --> Solve4
    Solve4 -->|Success| Success
    Solve4 -->|Fail all tolerances| Failure
    

图:多策略回退决策树

来源src/robot_moveit/scripts/moveit_gateway.py:316-430

代码结构

cmd_pose_callback 中的回退逻辑:

strategies = [
    ("Gripper Z-axis constraint", self.constrain_to_z_axis_only(orig_quat)),
    ("Shoulder XZ plane projection", self.project_orientation_to_shoulder_xz_plane(orig_quat)),
]

# Fallback: current orientation (lookup via TF)
current_quat = tf_buffer.lookup_transform(base_link, ee_link, ...)
strategies.append(("Current orientation (position only)", current_quat))

# Final fallback: identity quaternion
strategies.append(("Default orientation (no rotation)", (0.0, 0.0, 0.0, 1.0)))

tolerance_strategies = [
    ("Strict tolerance", (0.1, 0.1, 0.05)),
    ("Medium tolerance", (0.3, 0.3, 0.1)),
    ("Relaxed tolerance", (0.5, 0.5, 0.15)),
    ("Z-axis only", (1.0, 1.0, 0.2)),
    ("No constraints", None),
]

for strategy_name, quat in strategies:
    for tol_name, tolerances in tolerance_strategies:
        if self.solve_and_move(adjusted_pose, orientation_tolerance=tolerances):
            return  # Success

来源src/robot_moveit/scripts/moveit_gateway.py:372-430


带约束的 IK 求解

solve_and_move() 实现

        graph TB
    Start["solve_and_move(target_pose,<br/>orientation_tolerance)"]
    ValidateState["Validate latest_joint_state<br/>has required joints"]
    CreateConstraints{"orientation_tolerance<br/>is not None?"}
    BuildConstraint["create_orientation_constraint()<br/>Build OrientationConstraint"]
    NoConstraint["constraints = None"]

    ComputeIK["moveit2.compute_ik_async(<br/>position, quat_xyzw,<br/>start_joint_state, constraints)"]
    WaitFuture["Wait for future.done()<br/>(timeout 5.0s)"]
    GetResult["moveit2.get_compute_ik_result(future)"]

    CheckResult{"ik_solution<br/>is not None?"}
    ExtractJoints["Extract joint_positions<br/>from solution"]
    MoveJoint["move_to_joint(joint_positions)"]
    Success["Return True"]
    Fail["Log warning<br/>Return False"]

    Start --> ValidateState
    ValidateState --> CreateConstraints
    CreateConstraints -->|Yes| BuildConstraint
    CreateConstraints -->|No| NoConstraint
    BuildConstraint --> ComputeIK
    NoConstraint --> ComputeIK

    ComputeIK --> WaitFuture
    WaitFuture --> GetResult
    GetResult --> CheckResult
    CheckResult -->|Yes| ExtractJoints
    CheckResult -->|No| Fail
    ExtractJoints --> MoveJoint
    MoveJoint --> Success
    

图:solve_and_move() 工作流

来源src/robot_moveit/scripts/moveit_gateway.py:444-577

关键方法调用

方法

用途

文件位置

compute_ik_async()

异步 IK 计算 (pymoveit2)

moveit_gateway.py:506-534

get_compute_ik_result()

从 future 提取关节解

moveit_gateway.py:545

move_to_configuration()

通过 MoveGroup 执行关节轨迹

moveit_gateway.py:573

create_orientation_constraint()

构建 OrientationConstraint 消息

moveit_gateway.py:273-306

来源src/robot_moveit/scripts/moveit_gateway.py:444-577


配置集成

机器人配置 YAML

robot_config YAML 包含传递给启动系统的 MoveIt 特定参数:

moveit:
  arm_group_name: arm
  base_link: base
  ee_link: gripper
  shoulder_link: shoulder

来源src/robot_config/config/robots/test_single_arm_single_cam.yaml:30-35

启动构建器集成

robot_config 包中的 generate_moveit_nodes() 函数提取这些参数并传递给 MoveIt 启动文件:

arm_group_name = robot_config['moveit']['arm_group_name']
base_link = robot_config['moveit']['base_link']
ee_link = robot_config['moveit']['ee_link']
shoulder_link = robot_config['moveit']['shoulder_link']

moveit_launch = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(str(moveit_launch_file)),
    launch_arguments={
        'arm_group_name': arm_group_name,
        'base_link': base_link,
        'ee_link': ee_link,
        'shoulder_link': shoulder_link,
    }.items()
)

shoulder_link 参数对肩部 XZ 平面投影策略至关重要。

来源src/robot_config/robot_config/launch_builders/moveit.py:47-68


坐标系变换

工作空间分析

cmd_pose_callback() 通过将目标位置变换到肩部坐标系来记录详细的工作空间信息:

1. Lookup transform: base → shoulder (translation T, rotation Q)
2. Compute relative position: p_relative = p_base - T
3. Apply rotation: p_shoulder = Q * p_relative
4. Compute distances from base and shoulder origins

日志输出示例

[INFO] Target Pose: x=-0.046, y=-0.000, z=0.423
[INFO]   Target in shoulder frame: x=-0.034, y=0.012, z=-0.323
[INFO]   Distance from base origin: 0.426 m
[INFO]   Distance from shoulder origin: 0.325 m

这有助于诊断可达性问题,因为 SO101 的典型工作空间从肩关节延伸约 0.3-0.4m。

来源src/robot_moveit/scripts/moveit_gateway.py:317-367


性能特征

典型成功率

配置

IK 成功率

备注

标准 6DOF 约束

~10-20%

大多数位姿 无解

p osition_only_ik: True

~70-80%

适合位置 关键任务

  • Z 轴约束 + 中等容差

~85-90%

平衡姿态 和成功率

  • 完整回退 流水线

~95-98%

几乎总是 找到解

来源src/robot_moveit/docs/moveit_gateway.md:26-51

计算成本

回退流水线最多测试 4 策略 × 5 容差级别 = 每个位姿命令 20 次 IK 尝试。典型时间线:

  • 单次 IK 尝试:50-200ms(KDL 求解器,50 次尝试)

  • 完整回退最坏情况:2-4 秒

  • 典型情况(第一次策略成功):100-300ms

来源src/robot_moveit/config/lerobot/so101/kinematics.yaml:4-5


数学工具

MoveItGateway 类使用 scipy.spatial.transform.Rotationnumpy 实现四元数和旋转矩阵操作:

        classDiagram
    class MoveItGateway {
        +quaternion_multiply(q1, q2)
        +quaternion_conjugate(q)
        +quaternion_to_rotation_matrix(q)
        +rotation_matrix_to_quaternion(R)
        +constrain_to_z_axis_only(quat)
        +project_orientation_to_shoulder_xz_plane(quat)
        +create_orientation_constraint(target_quat, link_name, frame_id, tolerances)
        +cmd_pose_callback(msg)
        +solve_and_move(target_pose, orientation_tolerance)
    }

    class scipy_Rotation {
        <<external>>
        +from_quat(quat)
        +from_matrix(R)
        +as_quat()
        +as_matrix()
        +inv()
    }

    class numpy {
        <<external>>
        +linalg.norm(vec)
        +dot(a, b)
        +cross(a, b)
        +column_stack(arrays)
    }

    MoveItGateway --> scipy_Rotation : uses
    MoveItGateway --> numpy : uses
    

图:数学依赖关系

来源src/robot_moveit/scripts/moveit_gateway.py:81-175

关键方法

所有操作使用 [x, y, z, w] 四元数格式(标量在后约定)。

来源src/robot_moveit/scripts/moveit_gateway.py:81-116


故障排除

常见失败模式

症状

可能原因

解决方案

` NO_IK_SOLUTION` 错误

` position_only_ik: False`

在 kinematics.yaml 中设置 posit ion_only_ik: True

成功率低 (~20%)

姿态约束过紧

验证回退流水线 正在运行

超时错误

k inematics_solver_timeout 过低

增加到 2.0+ 秒

解的姿态与 预期相差甚远

仅位置模式激活

添加中等容差的 姿态约束

所有策略失败

目标超出工作空间

检查肩部坐标系距离 (应 < 0.4m)

来源src/robot_moveit/docs/moveit_gateway.md:339-355

调试日志

启用调试日志以查看策略尝试:

ros2 run robot_moveit moveit_gateway.py --ros-args --log-level debug

示例输出:

[DEBUG] Trying Gripper Z-axis constraint
[DEBUG]   Failed with Strict tolerance, trying next tolerance...
[DEBUG]   Failed with Medium tolerance, trying next tolerance...
[INFO] IK succeeded with Gripper Z-axis constraint + Relaxed tolerance

来源src/robot_moveit/scripts/moveit_gateway.py:416-428


来源src/robot_moveit/scripts/moveit_gateway.py:1-596src/robot_moveit/config/lerobot/so101/kinematics.yaml:1-7src/robot_moveit/docs/moveit_gateway.md:1-389src/robot_config/config/robots/test_single_arm_single_cam.yaml:30-35src/robot_config/robot_config/launch_builders/moveit.py:15-78