ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Panda机械臂MoveIt2避障规划实战:笛卡尔轨迹绕障方案解析

Panda机械臂MoveIt2避障规划实战:笛卡尔轨迹绕障方案解析 先别急着抄代码先把思路捋顺。Panda机械臂配合MoveIt2做笛卡尔轨迹规划听起来是个很标准的demo真上了机器人你会发现处处是坑。最典型的一个场景末端要从A点沿着直线走到B点但路径中间刚好有一根柱子、一个料箱或者一颗螺丝你直接用compute_cartesian_path硬插值轨迹是直线了机械臂也大概率“哐”一声撞上去。那怎么办这篇文章就把这块讲透从环境配置到避障规划代码再到我实际调试踩过的各种坑一次性说清楚。适合刚接触ROS2和MoveIt2、想跑通Panda机械臂避障规划的人也适合已经在做机器人上下料、焊接、喷涂这类“必须保持末端姿态和路径”的工程场景拿来做方案参考。1. 环境准备与整体设计思路1.1 为什么选Humble加MoveIt2加Panda这套组合ROS2的发行版现在有好几个Foxy、Humble、Iron、Jazzy我自己的经验是如果你用的是Ubuntu 22.04直接上Humble最省事。原因很简单MoveIt2在Humble上已经有非常完整的二进制包panda_moveit_config这种官方配置包也能直接装不需要从源码编译一整天。对于想快速验证算法和执行逻辑的人来说时间成本是第一位的没必要在环境上死磕。Panda机械臂本身是Franka Emika家的七轴机械臂MoveIt2官方配置里已经把运动学、碰撞模型、规划组都做好了开箱即用。七轴比六轴多一个冗余自由度在做避障规划时有更大的搜索空间路径更容易绕出来这是它适合做避障demo的重要原因。如果你手头只有六轴模型思路完全一样但规划成功率会明显低一些。MoveIt2这个版本相比ROS1时代的MoveIt最大的变化是彻底组件化了。规划器、感知、场景监听、轨迹执行全部分离成独立节点数据通信走ROS2的topic/action。这就意味着你可以只启动move_group然后自己写一个小节点去发规划请求。这篇文章的示例就是这样干的。1.2 安装步骤与验证我的安装过程很简单没有走源码编译sudo apt update sudo apt install ros-humble-moveit ros-humble-panda-moveit-config这里有一点要注意ros-humble-panda-moveit-config这个包不一定在你自己配置的软件源里默认包含如果安装时报“Unable to locate package”先执行sudo apt search panda | grep moveit确认包名是不是ros-humble-panda-moveit-config不同发行版的名字可能略有差异。另外如果你以后想上Gazebo仿真还需要sudo apt install ros-humble-panda-gazebo但本文示例不需要Gazebo直接在MoveIt2自带的RViz规划环境里跑就够了。装完以后验证一下source /opt/ros/humble/setup.bash ros2 pkg list | grep panda能列出一堆panda_moveit_config、panda_description这类包说明安装成功。然后启动demoros2 launch panda_moveit_config demo.launch.py这一步会同时拉起move_group、RViz2、机器人状态发布器还有MotionPlanning插件。启动完你会在RViz里看到一只立起来的Panda左侧面板可以拖拽目标位姿MotionPlanning面板里有Plan和Execute按钮。先手动拖一个姿态按Plan如果绿色轨迹能正常显示并且不报错环境就是通的。1.3 搭建“避障场景”的最小参考系做避障规划之前脑子里必须有一张清晰的地图机器人基座在哪、目标在哪、障碍物在哪。Panda在MoveIt里的默认基座坐标系是panda_link0末端执行器默认follow的坐标系是panda_hand也就是法兰末端。所有的位置坐标不管是目标点还是障碍物都建议统一在这个坐标系下设置不然坐标系一混后面查错能查到怀疑人生。另外默认demo里没有桌子、没有地面只有机械臂本体。这个环境对纯运动规划demo是够用的但你要模拟真实工况最好自己往规划场景里加一张桌子和一个障碍物。这就是下一步的事。2. 核心概念笛卡尔规划、避障与MoveIt的规划链路2.1 笛卡尔空间规划和关节空间规划到底差在哪笛卡尔空间规划指的是机械臂末端在三维空间沿直线或者圆弧走比如你要求末端从坐标(0.5, 0.0, 0.6)直线走到(0.7, 0.1, 0.5)过程中末端姿态保持不变。这种规划的结果是末端轨迹但机械臂实际上靠关节转动来实现所以MoveIt要做的是把这条直线离散成很多小点然后在每个点上做逆运动学解算解出对应的关节角度再做插值。关节空间规划就不管末端轨迹了它只给一个目标位姿然后规划器在关节空间里找一条从当前状态到目标状态的无碰撞路径。这条路径末端大概率不是直线甚至可能绕一个大圈但关节运动平滑、实现简单。两者的关系可以用一个特别生活化的例子理解你从客厅走到厨房笛卡尔空间规划要求你每一步都踩在地板砖的某条直线上姿势还得保持“面朝前方”关节空间规划只要求你最后站在厨房门口至于中间从沙发边上绕过去还是从桌子边绕过去无所谓。真实工业场景里焊接、打磨、点胶都要求末端走直线所以不能只给一个目标点必须做笛卡尔规划。但如果中间有障碍物简单的直线插值就会撞上去这就引出了避障规划。2.2 MoveIt2避障的核心PlanningScene与CollisionObjectMoveIt2避障不是靠某个神秘算法单独完成的它有一套完整的场景管理机制核心就是PlanningScene。这个场景里保存了机器人自身模型、外部障碍物、允许碰撞矩阵等信息。每当你添加一个障碍物MoveIt就会把它注册进当前场景每次规划器搜索路径时都会不断查询“当前这个关节状态会不会碰到底座旁边的柱子”等类似问题。添加障碍物在代码里用的是CollisionObject消息。一个CollisionObject可以做四件事ADD添加到场景、REMOVE从场景移除、MOVE移动位置、APPEND附加。常见障碍物形状有三种BOX盒子、CYLINDER圆柱、SPHERE球体。这三种基本能覆盖绝大多数结构化障碍物。如果是相机点云生成的物体则用MESH或者OCTOMAP。对于初学者我建议先用盒子练手因为盒子的尺寸和位置都非常直观不容易出错。MoveIt内部做碰撞检测的库是FCL全称Flexible Collision Library。它会把机械臂每个link简化成凸包或者网格模型然后和场景里的障碍物求交。默认情况下MoveIt2还会用allowed collision matrix来做“白名单”跳过某些不必要检查的碰撞对比如本来就连在一起的link之间。这个机制后面会提到它也是避障失效的一个常见原因。2.3 为什么compute_cartesian_path不能直接用于避障回到开头那个问题我用compute_cartesian_path能不能在障碍物面前自动绕开答案是不能。这个函数做的事情非常“死板”你给它一串waypoints它按直线在每个waypoint之间插入中间点然后对每个点做逆运动学求解。它不做绕障搜索也不考虑障碍物。更重要的是如果中间某个点的逆解无解它会在那个点停止返回一个小于1的fraction。如果每个点都解出来了fraction就是1.0但轨迹完全可能穿过障碍物。也就是说fraction1.0只代表“运动学上每一点都有解”不代表“力学上不会撞”。这是初学者最常误解的地方也是实际调试时最吓人的情况。我见过有人看到笛卡尔轨迹生成成功了就直接给机械臂执行结果末端差点撞进夹具里。那怎么解决两种思路如果障碍物不大且你允许末端稍微偏离直线可以改用采样规划器比如RRTConnect并加一条路径约束Path Constraints让末端只能在一个“管道”内运动。这样规划器既不会偏离目标太多又能在管道范围内绕开障碍。如果必须严格走直线那就得把避障问题变成“直线路径分解多段笛卡尔规划”或者用带避障的笛卡尔规划器/轨迹后处理。这篇文章的示例代码用的是第一种思路这也是MoveIt2官方推荐的方式之一代码量小效果直观。3. 实战从直插到绕障的Panda轨迹规划代码3.1 场景假设与目标位姿设计先定义一个明确的场景。目标点设在机械臂前方远处坐标大概是(0.65, 0.0, 0.55)就是在基座坐标系的X轴正方向、大约65厘米远、55厘米高的地方。机械臂初始状态是默认home姿态末端在机械臂上方。在目标点和机械臂之间我放一根竖直的柱子作为障碍物柱子的位置设在(0.4, 0.0, 0.5)尺寸是0.12m x 0.12m x 1.0m。柱子不高不矮刚好挡在直线路径的中间。这样设计的好处是如果直接做笛卡尔直线插值末端一定会撞上柱子如果做带避障的采样规划末端就必须从柱子左边或右边绕过去。这里注意坐标系的Z轴零点在机械臂基座安装面如果你加了一张桌子障碍物高度必须放在桌面上方。demo环境没有桌子时直接按基座坐标给就行。3.2 完整依赖与代码骨架先用ROS2创建一个功能包这里用Python接口方便快速验证ros2 pkg create panda_cartesian_avoidance_demo --build-type ament_python --dependencies rclpy moveit_msgs geometry_msgs shape_msgs代码里用到三个核心接口MoveGroupCommander负责设置规划组、目标位姿、路径约束并触发规划。PlanningSceneInterface负责向场景里添加/移除障碍物。RobotCommander用来获取当前机器人状态和末端位置。核心代码我放在下面。我这里把步骤拆成几段方便看逻辑。第一步初始化并创建连接#!/usr/bin/env python3 import time import rclpy from moveit_commander import ( RobotCommander, PlanningSceneInterface, MoveGroupCommander, ) from moveit_msgs.msg import ( CollisionObject, PositionConstraint, Constraints, ) from shape_msgs.msg import SolidPrimitive from geometry_msgs.msg import Pose, PoseStamped PLANNING_GROUP panda_arm EE_LINK panda_hand def add_box_obstacle(scene, name, dims, position): obj CollisionObject() obj.id name obj.header.frame_id panda_link0 obj.operation CollisionObject.ADD box SolidPrimitive() box.type SolidPrimitive.BOX box.dimensions [dims[0], dims[1], dims[2]] obj.primitives.append(box) pose Pose() pose.position.x position[0] pose.position.y position[1] pose.position.z position[2] pose.orientation.w 1.0 obj.primitive_poses.append(pose) scene.apply_collision_object(obj) def main(): rclpy.init() node rclpy.create_node(panda_cartesian_avoidance_demo) robot RobotCommander(node) scene PlanningSceneInterface(node) group MoveGroupCommander(node, PLANNING_GROUP) group.set_planner_id(RRTConnect) group.set_num_planning_attempts(10) group.set_planning_time(15.0) group.set_max_velocity_scaling_factor(0.05) group.set_max_acceleration_scaling_factor(0.05) group.set_start_state_to_current_state()这里的set_planner_id(RRTConnect)很关键。OMPL里规划器很多但避障场景下RRTConnect特别管用因为它从起点和目标点同时向中间扩展两棵树搜索速度快对高维空间尤其友好。速度缩放因子设成0.05是因为Panda在仿真里默认速度太猛执行起来不好观察调小一点会更稳。第二步设置目标位姿并添加障碍物# 设置目标位姿让末端沿着X方向伸出并越过障碍 target PoseStamped() target.header.frame_id panda_link0 target.pose.position.x 0.65 target.pose.position.y 0.0 target.pose.position.z 0.55 target.pose.orientation.x 0.0 target.pose.orientation.y 0.0 target.pose.orientation.z 0.0 target.pose.orientation.w 1.0 group.set_pose_target(target) # 添加障碍路径正中间竖一根柱子迫使轨迹绕行 add_box_obstacle( scene, pillar, [0.12, 0.12, 1.0], [0.4, 0.0, 0.5], ) # 给场景广播留一点时间 time.sleep(2.0)这里必须time.sleep一下因为场景消息是异步发布的你不等它后面的规划器很可能还没把柱子加进规划场景里直接导致避障失效。这个坑特别隐蔽尤其在你反复测试时场景状态可能不一致最好在添加障碍物后打印一下规划场景里已存在的物体数量。第三步先做一段“直插”笛卡尔规划验证会撞障碍# ---------- 方法A直接笛卡尔插值不做避障搜索 ---------- waypoints [] current_pose group.get_current_pose(EE_LINK).pose waypoint current_pose waypoint.position.x 0.65 waypoint.position.y 0.0 waypoint.position.z 0.55 waypoints.append(waypoint) fraction, trajectory group.compute_cartesian_path( waypoints, eef_step0.01, jump_threshold0.0 ) print(Method A: cartesian path fraction {}.format(fraction))fraction1.0不代表没碰撞。这里只做演示看到分数是1.0也别直接执行否则机器人会直直撞向柱子。第四步设置路径约束并做避障规划# ---------- 方法BRRT-Connect 路径约束近似直线 避障 ---------- group.clear_pose_targets() constraints Constraints() pos_constraint PositionConstraint() pos_constraint.header.frame_id panda_link0 pos_constraint.link_name EE_LINK constraint_box SolidPrimitive() constraint_box.type SolidPrimitive.BOX constraint_box.dimensions [0.7, 0.36, 0.36] pos_constraint.constraint_region.primitives.append(constraint_box) center Pose() center.position.x 0.4 center.position.y 0.0 center.position.z 0.55 center.orientation.w 1.0 pos_constraint.constraint_region.primitive_poses.append(center) constraints.position_constraints.append(pos_constraint) group.set_path_constraints(constraints) result group.plan() if result[0]: print(Method B: planning success, time {:.2f}s.format(result[2])) group.execute(result[1], waitTrue) else: print(Method B: planning failed, code {}.format(result[3])) # 规划失败时优先尝试放宽约束区域或者把障碍物稍微移远一点 group.clear_path_constraints()这里的核心是PositionConstraint。它给末端画了一个“合法的活动范围”一个长方体长0.7米宽和高各0.36米中心固定在(0.4, 0.0, 0.55)。也就是说机械臂规划出来的轨迹末端只能在这个长方体里面走。长方体基本覆盖了从起点到目标点的直线走廊但它够宽柱子旁边有空间绕过去。采样规划器RRTConnect在这个走廊里搜索既能保证末端轨迹“大致是直线”又不会撞上柱子。如果规划失败问题通常出在约束区域太窄。可以先把constraint_box.dimensions里的宽和高加大到0.5甚至0.6重新规划。代价是轨迹偏离直线的程度变大但至少能跑通。真实工程中你需要在“轨迹直线度”和“规划成功率”之间做权衡没有参数能一次到位。3.3 执行完怎么验证轨迹没撞执行成功后要看轨迹是否真的绕开了障碍物最直接的办法是在RViz里观察。demo.launch.py启动的RViz里已经加载了MotionPlanning插件规划出来的轨迹会以绿色或彩色线条显示碰撞物体显示为半透明色块。如果轨迹线穿过柱子说明避障没生效先查碰撞物体加没加进场景再查约束区域坐标对不对。RViz之外还可以打印轨迹上的关键点检查末端位置到障碍物中心的距离。简化做法是遍历轨迹的关节角度把每个关节角度转成末端位姿再计算末端和柱子之间的距离。这一步比较机械但非常能说明问题。比如我实测下来轨迹绕过柱子时末端离柱子最近的点大约是12厘米明显大于柱子的半宽度说明物理上是安全的。对MoveIt2版本不同导致的plan()返回值差异我补充一下。Humble早期版本的Python接口里plan()返回的可能是一个二元组(success, trajectory_msg)后期版本可能是四元组(success, trajectory_msg, planning_time, error_code)。你写代码时最好先跑一下打印出type(result)和len(result)再按实际结构取值。我经常看到有人抄了ROS1时代的代码直接plan move_group.plan()然后plan[0]判断成功到了ROS2却发现那里存的是轨迹消息白白排查很久。4. 常见问题与排查技巧实录4.1 Q1规划一直失败错误码是99999错误码99999在MoveIt里就是常见的FAILURE。原因很多但最经常遇到的是目标位姿自身不可达或者路径约束太紧。我调试时的顺序是这样的先不加障碍物和路径约束只设目标位姿看能不能规划成功。如果这个都失败说明目标位姿超过工作空间或者末端姿态定义有问题。加了障碍物后失败把障碍物尺寸缩小或位置往旁边挪排除“障碍物和机械臂初始姿态就碰撞”的情况。加了路径约束后失败优先把约束区域放宽。还有一个很容易忽略的点如果障碍物和机械臂的某个link在初始状态就发生了碰撞规划器会认为起始状态不合法直接失败。比如你把柱子放在紧贴机械臂底座的位置机器人初始姿态就已经和柱子重叠了怎么规划都没戏。遇到这种情况先把柱子挪远或者把机械臂移到安全姿态再开始规划。4.2 Q2笛卡尔路径规划返回的fraction不是1.0fraction小于1.0说明直线路径上有一段运动学无解或者路径被截断了。常见原因有两种。一种是目标点离机械臂太远直线路径的中间某个点超出了可达范围。机械臂的末端总不能凭空从空间里“瞬移”过去必须有一段关节角度配置能到达每个中间点。解决办法是调整目标点位置和姿态让路径尽量落在机械臂工作空间的中心区域。另一种是你设置了avoid_collisions参数并且在路径中途撞到了障碍物。MoveIt在检测到碰撞后会截断路径只返回碰撞前的部分。这种情况下fraction也小于1.0而它恰恰说明避障“生效了”。所以看到fraction不是1.0先搞清楚是哪种原因别急着改代码。4.3 Q3轨迹明明穿过障碍物避障好像没生效这种是最吓人的。第一反应是检查PlanningSceneInterface添加障碍物是否成功。最直接的办法是在RViz里看有没有出现那个半透明的盒子。如果没有大概率是障碍物消息发出去了但move_group没收到或者你用的是独立节点而move_group启动时没配置好场景监控。第二个检查点是AllowedCollisionMatrix也就是允许碰撞矩阵。如果你之前手动设置过ACM把机械臂某个link和障碍物之间的碰撞检查跳过了规划器就会认为“碰一下也没事”轨迹自然穿过障碍物。排查方法是把相关碰撞对的允许碰撞状态重新设回去或者重启move_group再测。第三个检查点是坐标系。CollisionObject的header.frame_id必须和规划场景的主坐标系一致通常是panda_link0。如果你写成panda_hand或者worldMoveIt会尝试做坐标变换一旦变换不出来就静默丢弃这个物体。4.4 Q4路径约束设置了但规划结果还是偏离很远路径约束只是一个“软约束”不它是硬约束。MoveIt在采样时必须满足约束才接受该采样点所以理论上不会偏离。但如果你设置的约束盒子非常大比如长宽都是1米那末端可以在很大的空间里随意运动看起来就像没约束。记住约束盒子的尺寸决定了末端允许偏离直线的程度不是机械臂的“期望路径”而是给它画的活动范围。把盒子的宽高从0.36缩小到0.2轨迹就会明显更贴近直线但规划成功率也会下降。这个参数需要根据实际应用反复调。4.5 Q5执行轨迹时机器人乱抖或者超速MoveIt默认生成的轨迹在仿真里会显得很“冲”特别是Panda这种轻量化机械臂。解决方法是设置速度缩放和加速度缩放group.set_max_velocity_scaling_factor(0.05) group.set_max_acceleration_scaling_factor(0.05)0.05意味着把规划出来的轨迹速度降到原来的5%执行时会明显平缓很多。真机上这个数值一定要根据实际控制器能力来不要盲目设很大不然电机和减速机都受不了。5. 进一步扩展从静态避障到动态环境5.1 用Octomap加入视觉感知上面示例里障碍物是手动指定的。真实场景中障碍物通常来自相机点云。MoveIt2支持把点云投影成Octomap八叉树地图然后作为碰撞物体加入规划场景。实现方式是用moveit_ros_perception里的OccupancyMapUpdater配置一个topic接收点云设置好voxel_leaf_size和max_range就能让规划器实时避障。一旦接入Octomap你会发现自己写的那个盒子障碍物代码不用改太多只是CollisionObject的类型从BOX变成了OCTOMAP。但Octomap也有坑点云噪声大、体素太大导致障碍物膨胀、动态物体更新频率不够导致规划撞上移动中人。工程上一般会把Octomap的体素设成2到5厘米并且限制点云输入范围避免机器人把地板、墙壁也当成障碍物。5.2 动态避障思路如果你的机械臂要在人旁边工作或者周围物体在移动单纯“规划一次就执行”是不够的。MoveIt2里常见做法是“反复重新规划轨迹执行看门狗”也就是一边执行当前轨迹一边监控场景变化发现新障碍物靠近就立刻触发重新规划。这个方向对实时性要求很高MoveIt2本身擅长运动规划但不算实时系统工业现场通常会把高实时性的安全避障放到PLC或者独立安全控制器里做。用MoveIt做的是“预规划避障”适合障碍物位置已知、变化不频繁的场景比如上下料、码垛、焊接。5.3 我的一些经验和建议文章最后分享几点这段时间调试Panda的心得希望能帮你少走弯路。坐标系一致性比任何算法参数都重要。我见过太多人把障碍物加到了panda_hand坐标系下结果目标点、障碍物、机械臂本体的坐标系各有各的想法规划出来的轨迹千奇百怪。建议所有物体都统一到panda_link0或者统一到一个你自定义的world坐标系然后做好坐标变换再谈规划。规划失败时不要盲目改参数。先做最小化验证把路径约束删掉、把障碍物挪走、把目标点调近一个个排除变量。很多人一失败就疯狂调规划时间、换规划器其实问题往往出在最简单的坐标系或者可达性上。执行前务必加碰撞检测确认。哪怕MoveIt规划出来的轨迹理论上无碰撞现实中也可能因为模型误差、安装误差导致碰撞。如果你的机械臂有末端力传感器执行时最好把碰撞检测阈值打开一旦发现力矩异常立刻停止。没有力传感器的话至少在RViz里把轨迹看一遍确认离障碍物有足够的安全间隙。MoveIt2的Python接口在不同版本差异很大。网上很多教程代码来自ROS1或早期版本直接复制大概率跑不通。遇到接口报错先打印函数签名再用dir()看对象有哪些方法比硬搜报错信息效率高得多。做这类项目别指望一次成功。我每次改完代码都会先把规划结果在RViz里反复看几遍确认没问题再让机械臂动。尤其是第一次跑避障程序建议把速度调到最低一只手放在急停按钮旁边另一只手盯着RViz轨迹。安全永远是第一位规划算法再漂亮也比不上一台完好无损的机器人。
返回列表