ompl_planning.yaml 里的 planner_id 并调规划时间/尝试次数上一页我们只「用了」规划器,这一页要回答一个更根本的问题:规划器到底在解什么?运动规划的形式化定义是:给定机器人当前状态、目标状态、障碍物集合,找一条从起点到终点、全程无碰撞、满足运动学约束的轨迹(按时间排列的位形序列)。
机器人一条手臂若有 n 个关节,它的完整状态可以用一个 n 维向量 q = (q₁, q₂, …, qₙ) 表示。所有可能 q 组成的空间叫构型空间(Configuration Space,简称 C-space)。规划器不在三维物理空间里搜索,而是在 C-space 里搜索:
| 概念 | 含义 | 在本页语境中的角色 |
|---|---|---|
C-space(构型空间) | 所有关节角度向量的集合,维数 = 自由度(DoF) | 规划的搜索空间;一条 7-DoF 手臂的 C-space 是 7 维 |
C-obstacle(障碍空间) | C-space 中会导致碰撞的 q 的集合 | 规划要绕开的「禁区」 |
C-free(自由空间) | C-space 减去障碍空间后的可行区域 | 合法轨迹必须始终落在 C-free 内 |
| 位形(q) | 一个具体的关节角组合 | 轨迹 = 一串按时间排布的 q |
| 维度 | 采样规划(Sampling-based) | 解析/优化规划 |
|---|---|---|
| 代表 | OMPL 全家(RRT/RRTConnect/PRM…)、MoveIt2 默认路径 | CHOMP、STOMP、TrajOpt、Time-Optimal 等 |
| 思想 | 在 C-space 里随机撒点 + 连线,构造一张「路网」再搜索 | 把轨迹参数化,用梯度/优化器最小化代价(平滑、避障) |
| 完备性 | 概率完备:时间越长越可能找到解(若解存在) | 通常只保证局部最优,依赖好初值 |
| 优点 | 高维、复杂障碍下鲁棒;不依赖初值 | 轨迹平滑、可显式加约束与代价 |
| 缺点 | 轨迹「乱」、不平滑,需后处理(简化/平滑/时间参数化) | 易陷局部最优,高维时慢 |
MoveIt2 默认是采样规划(OMPL),并在规划后追加「轨迹简化 + 时间参数化」步骤,把采样出的锯齿轨迹变平滑、再配上速度时间戳——这就是 request_adapters 干的活(见第 2 节 yaml)。
OMPL(Open Motion Planning Library)是 MoveIt2 默认的规划后端,一个与机器人无关的采样规划算法库。MoveIt2 通过 ompl_interface 插件把 OMPL 挂进来,自带 RRT / RRTConnect / RRTstar / PRM / PRMstar / BiTRRT / EST / KPIECE / LazyPRM / STRIDE / SPARS 等 20 余种算法(不同版本略有增减)。
| 算法 | 直觉 | 特点与适用场景 |
|---|---|---|
| RRT | 从起点长一棵随机「树」,朝随机方向探索 | 单树探索;简单直观但慢,轨迹质量一般 |
| RRTConnect | 起点、终点两棵树同时长,快速「对接」 | 收敛快、鲁棒,是 MoveIt2 默认;日常首选 |
| RRTstar | RRT + 不断「重连」优化路径长度 | 渐近最优,路径更短更平滑;但更慢,需更长规划时间 |
| PRM | 先铺满一张「路网」(roadmap),再多次查询 | 适合同一环境多次规划;建图开销大,动态障碍下要重建 |
| BiTRRT | 双向 RRT + 控制与障碍的距离(clearance) | 擅长狭窄通道,尽量贴着「中间」走,避免擦边 |
| EST | 在「能扩展」的区域附近集中采样 | 老牌算法;某些约束场景下比 RRT 稳,速度中等 |
| LazyPRM | 先连线、后「偷懒」延迟碰撞检查 | 边检查很贵时快;但可能频繁碰壁重试 |
MoveIt2 的规划器在 ompl_planning.yaml 里声明。关键结构:planner_configs 定义「算法库」,每个规划组的 planner_configs 列出「本组可用哪些」,default_planner_config 指定默认项。
planning_plugin: ompl_interface/OMPLPlanner
request_adapters: >-
default_planner_request_adapters/AddTimeParameterization
default_planner_request_adapters/FixWorkspaceBounds
default_planner_request_adapters/FixStartStateBounds
default_planner_request_adapters/FixStartStateCollision
default_planner_request_adapters/FixStartStatePathConstraints
start_state_max_bounds_error: 0.1
planner_configs:
RRTConnectkConfigDefault:
type: geometric::RRTConnect # 算法类型,决定用哪个 OMPL 规划器
range: 0.0 # 单步扩展上限;0=按状态空间自动取默认
RRTstarkConfigDefault:
type: geometric::RRTstar
range: 0.0
BiTRRTkConfigDefault:
type: geometric::BiTRRT
range: 0.0
PRMkConfigDefault:
type: geometric::PRM
max_nearest_neighbors: 10
arm:
default_planner_config: RRTConnectkConfigDefault
planner_configs:
- RRTConnectkConfigDefault
- RRTstarkConfigDefault
- BiTRRTkConfigDefault
- PRMkConfigDefault
planning_time: 5.0 # 单次规划时间上限(秒)
planning_attempts: 10 # 最多尝试几次
max_solutions: 10 # 最多保留几条候选解再选优
运行时可临时改规划器(不重启):RViz MotionPlanning 面板里直接选,或代码里 move_group.setPlannerId("RRTstarkConfigDefault")。
| 参数 | 含义 | 典型默认 | 调参建议 |
|---|---|---|---|
planning_time | 单次规划的时间预算 | 5.0 s | 复杂环境/高维臂可加到 10 s;实时性要求高则降到 1~2 s |
planning_attempts | 尝试次数(每次内各自跑 planning_time) | 10 | 失败率高时增大;但真机别让它无限重试 |
max_solutions | 保留候选解数量,从中选「最短/最平滑」 | 10 | 调大更容易拿到平滑路径,代价是更慢 |
range | 树单步扩展的最大步长 | 0(自动) | 越大探索越「粗」,越小越「细」;一般保持自动 |
goal_position_tolerance | 末端位置容差(米) | 0.0001 | 抓取前可适当放宽到毫米级,提高成功率 |
goal_orientation_tolerance | 末端姿态容差(弧度) | 0.001 | 拧螺丝等姿态敏感任务要收紧 |
「规划成功」≠「轨迹好用」。评估一条轨迹至少要盯三件事:
AddTimeParameterization 与轨迹简化缓解;很多任务要求末端全程保持某种姿态或轨迹形状——端一杯水不能倾斜、插销要沿直线插入、焊接要贴面滑行。这就要用到 MoveIt2 的路径约束(Path Constraints)。
| 约束类型 | 含义 | 人形机器人典型用途 |
|---|---|---|
| 方向约束(OrientationConstraint) | 末端坐标系的某一轴要指向某方向(或与参考方向夹角 ≤ 容差) | 端水平托盘、保持掌心向上 |
| 位置约束(PositionConstraint) | 末端(或其上某点)要落在某区域内(球/盒) | 沿直线插入、贴面焊接 |
| 关节约束(JointConstraint) | 某关节角度需落在给定区间 | 限制肘部方向、避免反关节 |
注意区分两个层级:目标约束(goal constraints)只约束「终点」,路径约束(path constraints)约束「全程每一点」。方向约束既可做目标也可做路径,含义完全不同——这是新手常踩的坑。
当你需要末端沿一条直线(或给定路径点序列)运动时,关节空间规划会「乱绕」,要用笛卡尔路径规划:在笛卡尔空间按步长插值出密集途经点,对每个点解 IK,再串成轨迹。核心接口(C++ MoveGroupInterface):
// waypoints: 一串末端位姿(几何直线就取起点与终点间等距插值)
std::vector<geometry_msgs::msg::Pose> waypoints;
// ... 填充 waypoints ...
moveit_msgs::msg::RobotTrajectory trajectory;
// eef_step:末端每步移动距离(m);jump_threshold:允许关节最大跳变(0=禁用)
double fraction = move_group.computeCartesianPath(
waypoints, eef_step, jump_threshold, trajectory);
// fraction ∈ [0,1]:= 1 表示走完全程;小于 1 表示中途卡住(奇异/碰撞/不可达)
if (fraction >= 0.999) {
move_group.execute(trajectory);
} else {
RCLCPP_WARN(node->get_logger(),
"笛卡尔路径仅完成 %.1f%%,剩余段未解出", fraction * 100);
}
Python 侧(MoveIt2 官方绑定为 MoveItPy,前身是 ROS1 的 moveit_commander)做「规划 + 执行」的最小示例:
import rclpy
from rclpy.node import Node
from moveit_py import MoveItPy
rclpy.init()
node = Node("moveit_py_demo")
# 初始化 MoveItPy(会加载 moveit_config 包)
moveit_py = MoveItPy(node_name="moveit_py")
arm = moveit_py.get_planning_component("arm")
# 起点=当前状态,目标=命名位姿 "home"
arm.set_start_state_to_current_state()
arm.set_goal_state(configuration_name="home")
# 规划
plan_result = arm.plan()
if plan_result:
# plan_result.trajectory 是 RobotTrajectory,可交给控制器执行
moveit_py.execute(plan_result.trajectory, controllers=[])
else:
node.get_logger().warn("规划失败,请检查目标/约束/碰撞")
fraction 停在小于 1 的位置。常见对策:缩短 eef_step、换初始位形、或改用关节空间目标绕开奇异点。
规划器「避障」靠的是碰撞检测,而 MoveIt2 的碰撞检测分两层:自碰撞(机器人自己打自己)与环境碰撞(机器人与周围物体)。
机器人的某些连杆天生就是贴着的(如相邻连杆、夹爪的两指),如果处处检测会误报大量「碰撞」。ACM(Allowed Collision Matrix,允许碰撞矩阵)声明「哪些连杆对之间不检查碰撞」,从而避免误报、加速检测。它由 Setup Assistant 采样生成,存于 SRDF 的 <disable_collisions> 段,并以 default 表示「默认允许、其它情况才查」。
<!-- SRDF 片段:声明 link1 与 link2 之间永不检查碰撞 -->
<disable_collisions link1="link1" link2="link2" reason="Adjacent"/>
<!-- 或:默认允许,只有列出的「危险对」才检查 -->
<disable_collisions link1="base_link" link2="gripper_link" reason="Default"/>
PlanningScene 加入,用 collision_object 或 attached_collision_object(抓在手上的物体)表示。真实场景里障碍物是动态、未知的,靠深度相机/激光雷达的点云来「看」。MoveIt2 的感知避障链路:
Octomap 的优势是内存友好 + 多分辨率:空旷区用粗大格子,障碍边缘用细密格子,适合大范围场景。配置要点是 sensors_3d.yaml(声明点云话题、更新频率、过滤范围)与 octomap 参数(体素分辨率,典型 0.05 m)。
PlanningSceneMonitor 持续订阅最新场景,而不是用一份「快照」。
「抓起一个东西」不是一个规划动作,而是一连串有依赖关系的子任务:先规划到抓取点、再闭夹、再抬升、再搬运、再放置。若把整件事塞进一次 MoveGroup 规划,成功率极低且难调试。MoveIt Task Constructor(MTC) 就是为此设计的:把复杂操作拆成有向图上的多个 Stage(阶段),每个 Stage 可独立求解、可回溯,前一个 Stage 的成功结果作为后一个的起点。
| 阶段 | 常用 Stage | 做什么 |
|---|---|---|
| ① 接近 | MoveTo(预抓位姿) | 把手臂移到物体上方的「预抓取点」 |
| ② 生成抓取 | GenerateGraspPose | 围绕物体生成多个候选抓取姿态(角度/位置采样) |
| ③ 求解 IK | ComputeIK | 对每个抓取姿态解出关节解 |
| ④ 伸手 | MoveRelative | 沿接近方向小步移动,把夹爪送到物体处 |
| ⑤ 抓取 | ModifyPlanningScene | 夹爪闭合 + 把物体 attach 到末端(附着) |
| ⑥ 抬升 | MoveRelative | 沿垂直方向抬起物体 |
| ⑦ 放置 | Place / MoveTo + AllowCollision | 移到放置点、松爪、解除附着 |
MTC 官方仓库自带 demo/scripts/pickplace.py 与 C++ 版 Pick and Place 示例,可作为起点改写。配置好后的启动方式:
# 官方 Pick and Place 示例(以 panda 为例,不同版本包名略有差异)
ros2 launch moveit_task_constructor_demo pick_place_demo.launch.py
# 或跑 Python 脚本版
python3 pickplace.py
GenerateGraspPose 就是把抓取库「铺」到物体上。attached_collision_object(物体随末端一起运动,才能正确检测「抬着箱子时会不会撞」)。仿真里 MoveIt2 规划的轨迹「凭空」执行;真机上轨迹必须经 ros2_control 下发给关节模组,并由硬件接口读回编码器反馈。
MoveIt2 规划轨迹(MoveGroup / MoveItPy)
│ 发布 moveit_msgs/msg/JointTrajectory
▼
joint_trajectory_controller(ros2_control)
│ 每个控制周期(如 100 Hz)插值下发位置/速度
▼
hardware_interface(真机驱动:CAN/EtherCAT 或仿真插件)
│ 写入电机命令 / 读回编码器
▼
关节模组(伺服电机 + 减速器)
真机配置示例(ros2_controllers.yaml):
controller_manager:
ros__parameters:
update_rate: 100 # 控制循环 Hz
arm_controller:
ros__parameters:
type: joint_trajectory_controller/JointTrajectoryController
joints:
- shoulder_pan_joint
- shoulder_lift_joint
- elbow_joint
- wrist_1_joint
command_interfaces:
- position # 位置控制接口(也可加 velocity/effort)
state_interfaces:
- position
- velocity
state_publish_rate: 50.0
action_monitor_rate: 20.0
allow_partial_joints_goal: false
constraints:
stopped_velocity_tolerance: 0.01
goal_time: 0.0
规划适合「点到点」;而遥操作、力引导、人机协作需要机器人实时跟随外部输入(手柄、手部追踪、力矩)。moveit_servo 提供高频率(约百 Hz 级)的末端速度伺服:输入「末端期望速度/增量」,直接解出关节速度下发,不做完整规划。它常与视觉遥操作、临场感控制配合,是实现「人手带着机械臂走」的关键模块。
ros2 control list_controllers 与三处关节名;人形机器人的手臂规划与桌面机械臂最大的不同:双臂、双足浮动基座、全身平衡。这里只谈手臂层面的落地现状,整机平衡与步态见本站其它章节。
双臂协同通常有两种做法:
实际工程里常用「主子臂」模式:主臂走笛卡尔/关节规划,从臂用相对约束跟随,兼顾成功率与同步性。
宇树 H1 是双臂人形机器人,官方 unitree_ros 仓库提供了 h1_description(URDF 与 MJCF 两种描述),以及 H1-2 型号描述;同时社区有基于 H1 的 ROS2 SDK 与 MoveIt 配置实践。在 H1 上跑 MoveIt2 的典型路径是:用官方 URDF 经 Setup Assistant 生成 moveit_config,建 left_arm / right_arm 规划组,再接入宇树的关节控制接口。注意:H1 双臂的 MoveIt 配置多由社区维护,官方公开资料以描述文件与 SDK 为主,具体型号的规划组命名与控制器需以对应仓库为准(约)。
智元机器人(AgiBot)在 2024 年 10 月开源了 灵犀 X1 的软硬件资料(官方称「0 元购」软硬件全套图纸与代码),含推理代码仓库 AgibotTech/agibot_x1_infer 等,以及自研开源通信中间件 AimRT。这套资料为「整机 + 双臂操作」的学习与二次开发提供了从模型到算法的完整参考。手臂层面的规划仍可复用 MoveIt2 生态(约,具体以各仓库 README 为准)。
继续深入的方向与官方入口(均为核实过的官方文档,详见文末「参考来源」):
moveit/moveit_task_constructor 与 Pick and Place with MTC 教程;planning_time/planning_attempts 调参;③ 目标约束只管终点、路径约束管全程,fraction<1 表示笛卡尔路径中途 IK 无解(奇异/不可达);④ 避障靠 ACM(豁免检测)+ 自/环境碰撞 + Octomap 感知;⑤ 复杂抓取用 MoveIt Task Constructor 拆 Stage 图,真机经 ros2_control(joint_trajectory_controller)下发,实时跟随用 moveit_servo。
ompl_planning.yaml 里的 planner_id / planning_time / planning_attempts 调参。约束规划与 computeCartesianPath 解决「直线/定姿态」类任务,避障靠 ACM + 自/环境碰撞 + Octomap 感知。复杂抓取用 MoveIt Task Constructor 拆成 Stage 图;真机落地靠 ros2_control(joint_trajectory_controller)+ moveit_servo 伺服;人形机器人则叠加双臂协同与整机平衡,宇树 H1 与智元灵犀 X1 的开源资料是重要实践参考。
computeCartesianPath 比关节空间规划更合适?fraction 停在 0.4 时最可能是哪里出了问题?attach 到末端与不 attach,对后续「抬升搬运」的碰撞检测结果有什么本质区别?GenerateGraspPose 的采样角度从 90° 改成 180°,观察成功率与候选解数量的变化。
资料整理更新至 2026-08;版本与参数以各官方文档当前版本为准,本文中未能逐一核实之处均标注「约」。