【免费下载链接】moveit2
:robot: MoveIt for ROS 2
导读:moveit_ros_perception 是 MoveIt2(ROS 2 版 MoveIt)中连接机器人感知数据的核心软件包,负责把深度相机图像与激光/深度点云实时融入碰撞检测所需的 OctoMap 占据地图。本文以该包的 CHANGELOG.rst 为时间主线,结合当前仓库(版本 2.15.2)的源码实现,系统讲解两大 OctoMap 更新器、GPU 网格自过滤、异步自由空间更新、机器人本体点云掩蔽等核心机制,以及 ROS 2 移植、QoS、构建系统等跨版本工程演进。读完本文,你将掌握这些感知模块的参数含义、底层调用链与排障思路,能够在自己的 MoveIt2 配置中正确选用和调优感知更新链路。
一、包定位:moveit_ros_perception 在 MoveIt2 中的角色
从 package.xml 可见,该包的描述是 “Components of MoveIt connecting to perception”,当前版本为 2.15.2,许可证为 BSD-3-Clause。它依赖 moveit_core、moveit_msgs、moveit_ros_occupancy_map_monitor、moveit_ros_planning 以及 image_transport、sensor_msgs、tf2*、cv_bridge、pluginlib 等 ROS 2 组件,还依赖 glut、libglew-dev、libomp-dev、opengl 等图形与并行库——这从依赖层面就暗示了它既处理传感器消息,也依赖 GPU 渲染与 OpenMP 并行加速。
从 CMakeLists.txt 可以看到它的内部结构:整个包由六个子库组成,分别对应六个子目录:
| 子库 | 库目标名 | 职责 |
|---|---|---|
depth_image_octomap_updater | moveit_depth_image_octomap_updater / _core | 用深度图像(sensor_msgs/Image + CameraInfo)更新 OctoMap |
pointcloud_octomap_updater | moveit_pointcloud_octomap_updater / _core | 用点云(sensor_msgs/PointCloud2)更新 OctoMap |
lazy_free_space_updater | moveit_lazy_free_space_updater | 异步批量计算自由空间(free cells) |
mesh_filter | moveit_mesh_filter | 基于 OpenGL 的网格自过滤(self-filtering) |
point_containment_filter | moveit_point_containment_filter | 判断点云中的点是否落在机器人自身形状内(ShapeMask) |
semantic_world | moveit_semantic_world | 订阅表格识别结果并生成放置(place)位姿 |
其中 mesh_filter 与 depth_image_octomap_updater 仅在 WITH_OPENGL 选项为 ON(默认 ON)时构建。CMake 里还创建了一个 INTERFACE 目标 moveit_ros_perception::moveit_ros_perception,下游包可以一次性链接全部六个库。两个 OctoMap 更新器都通过 pluginlib 导出插件,分别注册为 occupancy_map_monitor/DepthImageOctomapUpdater 与 occupancy_map_monitor/PointCloudOctomapUpdater,基类为 occupancy_map_monitor::OccupancyMapUpdater(见 occupancy_map_updater.hpp)。
二、两大 OctoMap 更新器:感知数据进入规划场景的入口
OccupancyMapMonitor(在 occupancy_map_monitor.cpp 中)通过 pluginlib 按 sensor_type 加载更新器,依次调用 setMonitor → setParams → initialize,最后 addUpdater 把更新器接入地图。两个更新器共享基类定义的生命周期:setParams(读参数)、initialize(建订阅/缓存)、start/stop(启停回调)、excludeShape/forgetShape(把机器人或物体的形状排除在占据判定之外)。
2.1 depth_image_octomap_updater:深度图像 → OctoMap
该更新器在 depth_image_octomap_updater.cpp 中实现,构造函数给出了全部参数的默认值,setParams 从节点参数服务器读取。参数清单与默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
image_topic | "depth" | 深度图像话题(与 CameraInfo 成对订阅) |
queue_size | 5 | 消息缓存队列长度 |
near_clipping_plane_distance | 0.3 | 近裁剪平面距离(米) |
far_clipping_plane_distance | 5.0 | 远裁剪平面距离(米),决定感知范围 |
shadow_threshold | 0.04 | 阴影阈值,用于区分“被模型遮挡”与“背景” |
padding_scale | 0.0 | 模型外扩的缩放系数 |
padding_offset | 0.02 | 模型外扩的固定偏移(米) |
max_update_rate | 0 | 最大更新频率,0 表示不限速 |
skip_vertical_pixels | 4 | 垂直方向跳过的边缘像素 |
skip_horizontal_pixels | 6 | 水平方向跳过的边缘像素 |
filtered_cloud_topic | 空 | 非空时发布过滤后的深度图(16UC1,毫米) |
ns | 空 | 发布话题的命名空间前缀 |
核心处理流程(见 depthImageCallback):
- 限频与统计:若
max_update_rate_ > 0,用last_update_time_做节流;同时统计回调平均间隔average_callback_dt_,供后续 TF 等待时长估算。 - TF 变换:把深度图像帧变换到地图帧(
map_h_sensor)。若 TF 暂不可用,会按queue_size折算次数重试,并统计good_tf_/failed_tf_,当失败数超过成功数时输出告警——这正是 CHANGELOG 中反复出现“TF unavailable”相关修复的落点。 - 网格过滤:利用
CameraInfo的内参(k[0]、k[4]、k[2]、k[5])驱动mesh_filter::StereoCameraModel,把机器人模型渲染成“模型深度”,与传感器深度比对后得到每个像素的标签(背景 / 模型 / 远裁剪)。 - 体素归类:背景像素按针孔模型反投影成 3D 点,
tree_->coordToKey得到占据体素(occupied_cells);落在远裁剪平面或模型上的像素记为model_cells,之后把occupied_cells中与模型重叠的体素剔除(“与模型重叠的 cell 不视为占据”)。 - 地图更新:加写锁后
tree_->updateNode(occupied_cell, true)标记占据,再通过free_space_updater_->pushLazyUpdate(...)把 occupied/model cell 集合与传感器原点交给后台线程做自由空间清理(见第四节)。 - 调试输出:
debug_info_开启时,通过 image_transport 发布model_depth、filtered_depth、filtered_label三路调试图像;filtered_cloud_topic_非空时另发布一版 16UC1 过滤深度图(毫米)。
CHANGELOG 中与该更新器相关的关键修复包括:
- 2.1.1:
Port moveit_ros_perception/depth_image_octomap_updater (#354),并修复两个移植错误——“无法收到深度图像”与“时间源不匹配”。源码中last_update_time_ = rclcpp::Time(0, 0, RCL_ROS_TIME)(见 depth_image_octomap_updater.hpp)的注释正是为了“prevent exception about time sources mismatch”。 - 2.8.0:改用
isnan判断相机内参是否有效(避免px == px这类写法被静态分析器误报),对应源码中 isnan(px) 分支。 - 2.11.0:
Fixed segmentation fault in depth_image_octomap_updater (#2963)——这类崩溃通常与图像尺寸变化导致内部缓存(x_cache_/y_cache_)与filtered_labels_扩容不同步有关,源码中所有缓存都按“小于则 resize”的规则按需扩容。 - 2.15.0:
fix depth_image_octomap_updater for image_transport 7.x (#3756),对应源码中按RCLCPP_VERSION_GTE(30, 0, 0)区分ImageTransport(*node_)与ImageTransport(node_)的写法,并改用image_transport::create_camera_subscription(*node_, ...)(见 depth_image_octomap_updater.cpp 与 start()),以兼容 Humble 到 Rolling 的不同image_transport版本。
2.2 pointcloud_octomap_updater:点云 → OctoMap
该更新器在 pointcloud_octomap_updater.cpp 中实现。与深度图更新器不同,它的参数全部为必选——setParams 会收集缺失参数名并打印 Missing parameters under '...' 错误日志(见 setParams):
| 参数 | 默认值 | 说明 |
|---|---|---|
point_cloud_topic | 无(必填) | 点云话题 |
max_range | 无(必填) | 最大感知距离,超出视为裁剪(clip)点 |
padding_offset | 无(必填) | 机器人形状外扩偏移 |
padding_scale | 无(必填) | 机器人形状外扩缩放 |
point_subsample | 无(必填) | 点采样步长,大于 1 可降低计算量 |
max_update_rate | 无(必填) | 最大更新频率 |
filtered_cloud_topic | 无(必填,可为空串) | 非空时发布过滤后的点云 |
ns | 空(可选) | 命名空间前缀 |
核心处理流程(见 cloudMsgCallback):
- 订阅使用
message_filters::Subscriber+tf2_ros::MessageFilter(目标帧为地图帧),并采用rclcpp::SensorDataQoS()(RCLCPP_VERSION_GTE(28, 3, 0)之后)与独立互斥回调组,避免传感器大消息阻塞主逻辑。 - 对每个点,先用
ShapeMask::maskContainment得到三类掩码:INSIDE(落在机器人模型内,记为model_cells)、CLIP(超出传感器范围,沿射线方向归一化后截断到max_range_,记为clip_cells)、OUTSIDE(正常占据,记为occupied_cells)。对 NaN 点直接跳过。 - 射线求自由空间:对每个 occupied / model / clip 端点用
tree_->computeRayKeys计算射线穿过的体素并放入free_cells;clip 单元格本身也视为自由(“超出量程的方向上没有障碍物”)。 - 写地图:
free_cells标为 false、occupied_cells标为 true、model_cells设为最小 log-odds(getClampingThresMinLog() - getClampingThresMaxLog()),随后triggerUpdateCallback()通知规划场景刷新。 - 若
filtered_cloud_topic_非空,把保留下来的有效点重新打包成PointCloud2发布(仅含 xyz 字段,尺寸动态 resize 到实际保留点数)。
CHANGELOG 中与之相关的修复:
- 2.5.3:
Don't return false when not finding optional parameter (#1418)——ns是可选的,找不到时不应导致整个初始化失败,对应源码中get_parameter_or(name_space + ".ns", ns_, std::string())的写法。 - 2.13.0:
fix: pointcloud_octomap_updater not cleaning objects at max_range (#3294)——修复了量程边界处物体残留的问题,正是通过把 clip 端点体素本身加入free_cells并在射线求交时一并处理实现的(见 clip_cells 处理)。同版本还修复了“OctoMap 与 Filtered_Cloud 在运动执行期间不更新”的问题。 - 2.14.1:
More user-friendly log message for pointcloud_octomap_updater (#3514),把参数缺失时的报错改为逐条列出缺失键名。
三、mesh_filter:用 OpenGL 把“机器人本体”从深度图中滤掉
自过滤(self-filtering)是 MoveIt 感知里最实用也最容易出问题的环节:深度相机必然会扫到机械臂本体,若不滤除,OctoMap 会把机器人自身当成障碍物。mesh_filter 子模块用 GPU 渲染解决这一问题。
在 mesh_filter_base.cpp 中可以看到其经典双 pass 架构:
- Pass 1(渲染):
mesh_renderer_用真实相机内参(GLRenderer内部通过setCameraParameters(fx, fy, cx, cy)构建透视投影)把机器人网格渲染到离屏 FBO,输出模型深度纹理与模型标签(每个网格一个唯一 handle 对应的颜色标签)。 - Pass 2(过滤):
depth_filter_把传感器深度图作为纹理(GL_DEPTH_COMPONENT,支持GL_FLOAT与GL_UNSIGNED_SHORT两种编码),与模型深度在同一片段着色器中按shadow_threshold阈值比较,输出每个像素的标签:背景(BACKGROUND)、模型(对应 mesh handle)、远裁剪(FAR_CLIP及以上)。 - 异步执行:
MeshFilterBase构造函数启动独立filter_thread_,所有渲染/读取操作以FilterJob任务队列的方式提交(见 run()),保证 OpenGL 上下文只在单线程中使用,这正是多次“段错误修复”的工程基础。 - 外扩控制:
padding_coefficients = sensor_parameters->getPaddingCoefficients() * padding_scale_ + (0,0,padding_offset_),对应参数padding_scale/padding_offset的物理含义——模型沿射线方向外扩,防止把紧贴机械臂的物体误滤。
gl_renderer.cpp 中 GLRenderer 创建自己的 OpenGL 上下文与帧缓冲(fbo_id_/rbo_id_/rgb_id_/depth_id_),并通过 freeglut 保证无显示环境(如无头服务器)下也能工作。CHANGELOG 相关条目:
- 0.6.6:为 glut 窗口注册空显示回调,规避 freeglut 3.0 下 “No display callback registered for window” 的启动失败。
- 2.10.0:
Fix segmentation fault in mesh_filter/gl_renderer (#2834),属于渲染上下文生命周期类问题。 - 2.9.0:
Converts float to double (#2343)大 PR 波及此模块——GLRenderer函数定义改为double,同时保持虚函数覆写所需参数类型不变,消除了大量 clang-tidy 告警。 - 1.0.6 / 1.0.4 / 1.0.3:多次提高
mesh_filter_test的接受阈值并修复测试,mesh_filter_test.cpp 至今保留着基于随机网格 + 渲染比对的自过滤验证用例。 - 0.10.2:
make OpenGL parts optional (#698)对应的正是 CMake 中的WITH_OPENGL选项,gl_renderer相关代码仅在开启时编译。
此外,mesh_filter 还提供一个 nodelet 插件 mesh_filter/DepthSelfFiltering(见 moveit_depth_self_filter.xml),描述为 “Nodelet for filtering meshes from depth images, e.g. meshes of the robot or any attached object where a transformation can be provided for”,即把同一套 GPU 过滤逻辑暴露给需要深度自过滤的其他节点复用。
四、lazy_free_space_updater:把“清除自由空间”挪到后台
占据地图不只标记障碍物,还要定期把传感器射线穿过的体素清为“自由”,否则机器人移动后旧障碍会残留。这一计算量大,LazyFreeSpaceUpdater 用两个后台线程异步完成(见 lazy_free_space_updater.cpp):
lazyUpdateThread(聚合线程):把深度/点云回调里pushLazyUpdate进来的 occupied/model cell 集合与传感器原点按批聚合;只要传感器原点位移不超过max_sensor_delta_(默认 1 mm)就并入同一批,达到max_batch_size_或原点漂移后交给处理线程。processThread(处理线程):对每批数据用tree_->computeRayKeys求射线穿过的体素,统计每个自由 cell 被射线命中的次数,再以lg_miss(miss 的 log-odds)加权更新树;模型 cell 置为最小 log-odds。两个方向(occupied 端点、model 端点)的射线求交用#pragma omp sections并行。- 背压策略:处理线程的队列长度固定为 1,若上一批未完成则丢弃新批并输出警告 “Previous batch update did not complete”——这是有意的丢数据设计,避免花过多时间清理地图。
CHANGELOG 相关条目:0.6.5 中 “adding RAII-based locking for OccMapTree”、“moving lazy_free_space_updater into it's own library”,以及 0.6.6 中 “Remove OpenMP parallelization”(后又在 0.10.6 重新 “Enforce OpenMP support for perception”),反映了并行策略在早期版本中的反复权衡。
五、point_containment_filter:用包围球加速的本体点云掩蔽
ShapeMask(见 shape_mask.cpp)负责回答“点云里的这个点是否落在机器人/被排除物体内部”。实现要点:
- 每个被排除形状(来自
excludeShape)构造一个bodies::Body(createEmptyBodyFromShapeType按形状类型创建),记录其体积并按体积排序(std::set<SeeShape, SortBodies>)。 - 处理点云时,先为每个 body 计算包围球,再用
bodies::mergeBoundingSpheres求整个机器人的总包围球:只有落入总包围球内的点才需要逐个 body 做精确的containsPoint判定,这是最主要的加速手段。 - 掩码结果三态:
OUTSIDE(保留)、INSIDE(机器人本体,映射为 model cell)、CLIP(超出传感器量程,映射为 clip cell)。
CHANGELOG 相关条目:2.9.0 的 (moveit_ros) add missing CYLINDER check (#2640) 明确指出“该检查缺失导致后面的 CYLINDER 分支不可达”,这是对 semantic_world 中 generatePlacePoses 形状类型分发逻辑的修复——当前源码中该函数已完整覆盖 MESH、SPHERE、BOX、CYLINDER、CONE 五种形状(见 semantic_world.cpp)。1.0.3 中 “Allow subclassing of point_containment_filter::ShapeMask (#1457)” 则是把该类设计为可被下游扩展。
六、semantic_world:表格识别与放置位姿生成
semantic_world 是感知与操作(manipulation)衔接的桥梁:它订阅 object_recognition_msgs/TableArray,把识别出的桌子加入 PlanningScene 碰撞世界,并生成可供机械臂放置(place)物体的候选位姿。核心能力(见 semantic_world.cpp):
addTablesToCollisionWorld:先发布 diff 场景移除旧桌子,再把每张桌子的凸包顶点构建成网格,经orientPlanarPolygon统一法线朝上后,以CollisionObject形式 ADD 进planning_scene话题。generatePlacePoses:对桌面的convex_hull用 OpenCV 的cv::findContours+cv::pointPolygonTest做多边形内外判定,按resolution步长、num_heights层高度(delta_height间隔)枚举候选点,并依据物体形状(MESH/BOX/SPHERE/CYLINDER/CONE)计算min_distance_from_edge与height_above_table,保证位姿不悬空、不越界。findObjectTable/isInsideTableContour:给定一个物体位姿,反查它位于哪张桌子之上。tableCallback:收到TableArray后通过 planning scene 的帧变换把桌子位姿统一到目标坐标系(transformTableArray),并触发用户注册的回调。
该模块在 CHANGELOG 中的痕迹包括 0.9.6 对编译开关的修复,以及 2.9.0 的 CYLINDER 检查补漏;它的 table_subscriber_ 也随 2.7.2 从 qos_event.hpp 迁移到 event_handler.hpp(见 semantic_world.cpp 的版本条件包含)。
七、工程化演进主线:从 ROS 1 移植到现代 ROS 2 实践
CHANGELOG 的时间线(0.5.x → 2.15.2)完整记录了该包十多年的工程演化,可归纳为几条主线:
1. ROS 2 移植与 API 适配(2.0 ~ 2.2)
- 2.1.1(2021-04)集中移植
depth_image_octomap_updater与pointcloud_octomap_updater,修复“收不到深度图像”与“时间源不匹配”;2.0.0(2020-10)是 ROS 2 版本线的起点。 - 2.2.0 修复 macOS 编译;2.3.0 修复 Galactic/Rolling 的告警并用
__has_include兼容废弃头文件,同时把OccMapTree迁入moveit_core/collision_detection。
2. QoS 与线程模型的演进(2.3 ~ 2.8)
- 2.3.1:
clipped points should only be considered up to the max_range——修复 clip 点处理;同版本还启用了 codespell 拼写检查与 package.xml format 3。 - 2.7.0:传感器消息改用
SensorDataQoS(sensor_msgs::Image与PointCloud2),源码中两个更新器目前分别以rclcpp::SensorDataQoS()创建订阅。 - 2.7.2:
Switch from qos_event.hpp to event_handler.hpp (#2111),为同时兼容 Humble 与 main 分支增加了条件编译。 - 2.8.0:
Replaced numbers with SystemDefaultsQos() (#2271),统一 QoS 常量使用。
3. tf2 与现代 C++ 规范(2.5 ~ 2.15)
- 2.5.x:
boost::bind→std::bind→ lambda(“banish bind()”),new改make_shared。 - 2.7.0:启用
-Wold-style-cast,全面改用 C++ 风格类型转换、<>引用非本地头文件。 - 2.9.0:大规模
float → double重构、统一moveit_core/moveit_ros节点日志、新增 clang-tidy 规则并落地 clang-format-14 的QualifierAlignment。 - 2.13.0:
Update deprecated tf2 imports from .h to .hpp (#3197)——源码中__has_include(<tf2/LinearMath/Vector3.hpp>)、<tf2_ros/buffer.hpp>等条件包含正是这一迁移的兼容层(见 pointcloud_octomap_updater.cpp)。 - 2.15.0:
Remove use of ament_target_dependencies (#3726),改用导出 CMake targets(如moveit_ros_planning::moveit_ros_planning);同时为 GCC 15 补充<cstdint>/<fstream>显式包含。 - 2.15.1:适配
tf2_ros移除双接口构造函数后的CreateTimerROS新 API(源码中按RCLCPP_VERSION_GTE(29, 6, 0)分支选择CreateTimerROS(*node)或CreateTimerROS(base_interface, timers_interface),见 pointcloud_octomap_updater.cpp),并弃用FindBoost.cmake。 - 2.15.2:本轮 clang-tidy 清理。
4. 组织与平台(2.10 ~ 2.15)
- 2.10.0:
Migrate ros-planning org to moveit (#2847),仓库与镜像地址全面迁移到moveit组织;同版本统一日志命名空间(如moveit.ros.depth_image_octomap_updater,见源码中moveit::getLogger("moveit.ros.depth_image_octomap_updater"))。 - 2.12.0:头文件从
.h全面转向.hpp(Enhancement/use hpp for headers (#3113))。 - 2.15.0/2.15.1:持续修复 Ubuntu 26.04 CI 与 macOS 链接问题。
八、如何在自己的 MoveIt2 中使用与验证
该包随 MoveIt2 源码一起构建,标准流程为在 MoveIt2 工作空间下:
colcon build --packages-select moveit_ros_perception
(若不需要 OpenGL 相关子模块,可在构建时关闭 WITH_OPENGL。)两个 OctoMap 更新器作为插件注册在 pointcloud_octomap_updater_plugin_description.xml 与 depth_image_octomap_updater_plugin_description.xml 中,最终由 moveit_ros_occupancy_map_monitor 包按 sensor_type 加载,因此配置感知时通常需要在 occupancy map monitor 的参数中指定 sensor_type: PointCloudOctomapUpdater(或 DepthImageOctomapUpdater)以及上文列出的参数。调试时可开启更新器的 debug_info_(publishDebugInformation),通过 RViz 订阅 model_depth / filtered_depth / filtered_label / 过滤点云话题,直观检查自过滤与量程设置是否合理。
本文所引源码与配置均位于当前仓库的 moveit_ros/perception 目录及其依赖的 moveit_ros/occupancy_map_monitor 目录,读者可结合 CHANGELOG.rst 对照各版本修复在源码中的落点,深入理解 MoveIt2 感知链路的演进脉络。
【免费下载链接】moveit2
:robot: MoveIt for ROS 2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



