Rerun GridMap 详解:在 2D/3D 空间中可视化机器人栅格地图与代价地图
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
本篇技术指南以 Rerun 的GridMaparchetype(类型参考)为核心,系统讲解如何在 Rerun 中记录并可视化占用栅格地图(occupancy grid map)与导航代价地图(navigation costmap)。读完本文,你将掌握GridMap的全部字段语义、Python / Rust / C++ 三种 SDK 的调用方式、位姿与色彩映射的配置技巧,以及它背后的渲染与 ROS 生态集成原理,可直接用于 SLAM、导航与多模态机器人数据可视化。
GridMap 是什么
GridMap是一个把二维栅格地图编码为图像缓冲区的 archetype:栅格数据以原始字节形式存放,配上一个ImageFormat描述其宽高与像素格式,再叠加一个"场景单位下的单格尺寸(cell size)"和可选位姿,就构成了一块可被渲染器铺设在 2D/3D 场景中的平面贴图。
在 crates/build/re_type_definitions/rerun/archetypes/grid_map.def.rs 中,官方类型定义开宗明义地写道:
A 2D grid map stored as raster data in an image buffer, with a cell size in scene units and pose. This archetype is intended for robotics applications like occupancy maps or navigation costmaps.
也就是说,它专门面向机器人应用场景——占用栅格地图(如 ROS 的nav_msgs/OccupancyGrid)和导航代价地图是它的典型用例。其文档分类为 "Spatial 3D",状态为 stable(#[rerun(state = "stable")])。
需要说明的是:该文档由代码生成器自动产出(文件头部标注 "DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/docs/website.rs"),其权威定义源正是上述.def.rs类型定义文件,SDK 绑定(如 crates/store/re_sdk_types/src/archetypes/grid_map.rs)也由同一套 builder 生成,因此本文中的字段语义均与实现一致。
字段一览
GridMap共 9 个字段:2 个必需、7 个可选(无 recommended),定义见 grid_map.def.rs,生成的 Rust 绑定中NUM_COMPONENTS = 9与此对应(re_sdk_types/src/archetypes/grid_map.rs)。
必需字段
| 字段 | 组件类型 | 说明 |
|---|---|---|
data | ImageBuffer | 栅格原始数据(字节缓冲) |
format | ImageFormat | 栅格图像的格式(宽高、颜色模型、通道类型) |
data与format是渲染的前提:渲染器会先读取这两个字段构造图像信息(visualizers/grid_map.rs 中iter_required取不到任何一个就直接返回)。data在生成代码中带有#[rerun(no_ui_edit)],即不提供 UI 编辑入口,由用户程序直接写入。
可选字段
| 字段 | 组件类型 | 默认行为 |
|---|---|---|
cell_size | CellSize | 单个栅格在场景单位下的尺寸(如 米/像素),默认 0.01 场景单位/像素;构造GridMap时是必传参数 |
translation | Translation3D | 栅格图左下角在空间中的平移;不设则左下角落在父坐标系原点 |
rotation_axis_angle | RotationAxisAngle | 通过"轴 + 角"指定左下角旋转 |
quaternion | RotationQuat | 通过四元数指定左下角旋转 |
opacity | Opacity | 纹理不透明度,默认 1.0(完全不透明) |
draw_order | DrawOrder | 多张栅格图重叠时的绘制顺序,值越大越靠上 |
colormap | Colormap | 单通道栅格图使用的色彩映射;不设置时按ImageFormat原样显示 |
关于cell_size有一个值得注意的细节:虽然它是可选字段(#[rerun(optional)]),但同时也被标记为#[rerun(required_for_constructor)]——即三种 SDK 的构造函数都必须显式传入它。Rust 端GridMap::new(data, format, cell_size)的三个参数与之对应(re_sdk_types/src/archetypes/grid_map.rs)。
位姿语义与旋转优先级
translation与旋转字段共同定义"图像左下角相对于父坐标系原点"的位姿(注意不是图像中心)。定义文件中有两条关键约束(grid_map.def.rs):
rotation_axis_angle与quaternion二选一,指定其一即可表达旋转;- 若两者同时设置,
rotation_axis_angle会被忽略,以四元数为准。
支持展示的视图
GridMap可以在以下视图中显示(类型参考):
Spatial3DView(首选:栅格地图本质是 2D 平面,但最常见的用法是作为 3D 场景中的平面图层)Spatial2DViewDataframeView(以表格形式查看组件数据)
在渲染器实现中,GridMapVisualizer::affinity()显式返回SpatialView3D::identifier(),注释说明"Grid maps are 2D, but most commonly used as planar layers within a 3D context"(visualizers/grid_map.rs)。渲染时它把栅格图构造为一块PickableTexturedRect并标注SpaceKind::ThreeD("The bounding box is flat, but this is distinctively a 3D object in a 3D space!"),同时会向场景提交一个 3D 包围盒,方便拾取与框选(visualizers/grid_map.rs)。
实战示例一:简单占用栅格地图
文档中的第一个示例 "Simple occupancy grid map" 对应仓库中的代码片段:
- Python:docs/snippets/all/archetypes/grid_map_simple.py
- Rust:docs/snippets/all/archetypes/grid_map_simple.rs
- C++:docs/snippets/all/archetypes/grid_map_simple.cpp
Python 版本
import numpy as np import rerun as rr width, height = 64, 64 cell_size = 0.1 # 按照 ROS nav_msgs/OccupancyGrid 的栅格值约定: # -1(255)未知、0 空闲、100 占用 grid = np.full((height, width), -1, dtype=np.int8) grid[8:56, 8:56] = 0 grid[20:44, 20:44] = 100 rr.init("rerun_example_grid_map", spawn=True) rr.log( "world/map", rr.GridMap( data=grid.tobytes(), format=rr.components.ImageFormat( width=width, height=height, color_model="L", # 单通道灰度 channel_datatype="U8", # 每像素 8 位无符号 ), cell_size=cell_size, translation=[ -(width * cell_size) / 2.0, -(height * cell_size) / 2.0, 0.0, ], colormap=rr.components.Colormap.RvizMap, ), )Rust 版本
use rerun::{ archetypes::GridMap, components::{Colormap, ImageFormat}, ColorModel, ChannelDatatype, RecordingStreamBuilder, }; fn main() -> Result<(), Box<dyn std::error::Error>> { let rec = RecordingStreamBuilder::new("rerun_example_grid_map").spawn()?; let width: usize = 64; let height: usize = 64; let cell_size: f32 = 0.1; // 同样遵循 ROS OccupancyGrid 约定:255 未知、0 空闲、100 占用 let mut grid = vec![255u8; width * height]; for y in 8..56 { for x in 8..56 { grid[y * width + x] = 0; } } for y in 20..44 { for x in 20..44 { grid[y * width + x] = 100; } } rec.log( "world/map", &GridMap::new( grid, ImageFormat::from_color_model( [width as u32, height as u32], ColorModel::L, ChannelDatatype::U8, ), cell_size, ) .with_translation([ -(width as f32) * cell_size / 2.0, -(height as f32) * cell_size / 2.0, 0.0, ]) .with_colormap(Colormap::RvizMap), )?; Ok(()) }两个版本都做了三件关键事:
- 按
nav_msgs/OccupancyGrid约定编码栅格值:-1(对应 255)表示未知、0表示空闲、100表示占用;由于使用L(单通道)+U8格式,字节值会被直接当作栅格值。 - 用
cell_size把像素换算成场景尺寸:64 × 0.1 = 6.4场景单位宽。 - 用
translation把地图中心平移到原点:因为位姿参考点是左下角,所以传[-(width*cell)/2, -(height*cell)/2, 0],让 64×64 的地图以原点为中心铺开。 - 指定
RvizMap色彩映射:让单通道栅格值以 ROS RViz 的地图配色显示(未知灰色、空闲黑色、占用色阶)。
C++ 版本要点
C++ 实现与 Rust 结构一致:使用rerun::archetypes::GridMap::new(grid, format, cell_size)构造,再链式调用with_translation、with_colormap等 builder 方法,完整代码见 grid_map_simple.cpp。
实战示例二:在指定位姿记录栅格地图
第二个示例 "Log a grid map at a specific pose" 展示了两层位姿的组合使用(目前仅有 Python 版本,grid_map_pose.py):
import math from pathlib import Path from PIL import Image as PILImage import rerun as rr import rerun.blueprint as rrb rr.init("rerun_example_grid_map_pose", spawn=True) # 1) 记录地图原点的变换:使用 ROS TF 风格的父子坐标系命名 rr.log( "/tf", rr.Transform3D( translation=[1.0, 2.0, 0.0], rotation_axis_angle=rr.components.RotationAxisAngle( [0, 0, 1], -math.pi / 3 ), parent_frame="world", child_frame="map", ), static=True, ) # 2) 用一张图片模拟地图内容 image = PILImage.open(Path(__file__).parent / "ferris.png").convert("RGBA") # 3) 在地图原点记录栅格地图,并指定其相对 map 坐标系的位姿 rr.log( "demo_map", rr.CoordinateFrame("map"), rr.GridMap( data=image.tobytes(), format=rr.components.ImageFormat( width=image.size[0], height=image.size[1], color_model="RGBA", channel_datatype="U8", ), opacity=0.5, # 半透明叠加 cell_size=0.01, # 每像素 0.01 场景单位 translation=[1.1, -1.6, 0.0], rotation_axis_angle=rr.components.RotationAxisAngle( [0, 0, 1], math.pi / 4.0 ), ), ) # 4) 显示带帧名的坐标系轴 rr.send_blueprint( rrb.Spatial3DView( origin="/", overrides={ "/tf": [rr.TransformAxes3D(axis_length=0.5, show_frame=True)], }, ) )这个示例揭示了GridMap位姿的两级结构:
- 外部位姿(实体树变换):通过
Transform3D定义map帧相对world帧的变换,这里采用了 ROS TF 风格的parent_frame/child_frame命名; - 内部位姿(GridMap 自身):
translation+rotation_axis_angle描述栅格图左下角相对map帧原点的位姿——示例中即地图内容在map坐标系内先旋转 45°、再平移到[1.1, -1.6, 0]。
渲染时,这两级变换会被合成:world_from_entity * entity_from_grid,其中entity_from_grid由翻译/旋转字段构造,world_from_entity来自实体树上的变换(visualizers/grid_map.rs)。若不给translation,左下角就落在map原点,只有外部位姿生效。
色彩映射与渲染细节(源码级)
GridMap的渲染由 crates/views/re_view_spatial/src/visualizers/grid_map.rs 中的GridMapVisualizer完成,其中有几个对实际使用影响很大的实现事实:
1. Colormap 仅对单通道(L)图像生效
color_mode_for_grid_map首先检查color_model != ColorModel::L,若不是单通道图像,colormap 会被忽略并输出一条 Info 级提示(grid_map.rs#L329-L344)。也就是说:多通道(如 RGBA)栅格图无法套用 colormap,只能按原始颜色显示。
2. 栅格专用 colormap 要求 L/U8 数据
RvizMap、RvizCostmap、Costmap三种栅格专用映射要求通道类型为U8。若数据是其他类型(如 F32 的代价地图),会降级为直接显示原始图像,并产生 Warning(grid_map.rs#L346-L362)。这三类映射本质是u8 取值的离散映射表,因此value_range被固定为[0.0, 255.0],而不是像普通 colormap 那样依据图像数据范围做启发式归一化(grid_map.rs#L370-L381)。
3. 栅格专用 colormap 使用最近邻采样
由于栅格映射编码的是离散的占用/代价类别,若用线性滤波会把相邻栅格值混在一起、出现边缘渗色。因此当 colormap 为RvizMap | RvizCostmap | Costmap时,纹理缩小滤波被设为Nearest,其余情况才用Linear(grid_map.rs#L302-L310)。
4. cell_size 必须为正
渲染前会校验cell_size:必须有限且大于 0,否则报错 "cell_size must be positive" 并放弃绘制该栅格图(grid_map.rs#L241-L249)。
渲染正确性的测试佐证
仓库在 crates/views/re_view_spatial/tests/grid_map.rs 中为GridMap提供了三组快照测试,可视为官方对渲染正确性的验证:
test_grid_map_texel_accuracy:用 5×5 棋盘格图像验证纹素精确渲染,同时用with_translation([5, 5, 0])+with_rotation_axis_angle(绕 Z 轴旋转 45°)验证"平移 + 旋转"位姿组合;test_grid_map_rviz_map_colormap:渲染覆盖 0–255 全部取值的RvizMap色带;test_grid_map_rviz_costmap_colormap/test_grid_map_costmap_colormap:分别渲染RvizCostmap与Costmap的完整色带。
与 ROS 生态的对接
除了手工构造栅格数据,仓库还内置了从 ROS 消息直接转换的 lens。在 crates/data_flow/re_lenses/src/semantic/ros2msg/occupancy_grid.rs 中,nav_msgs/msg/OccupancyGrid消息会被映射为GridMap:消息的data与info.resolution/info.width/info.height分别对应GridMap的data、format、cell_size,info.origin则映射到translation与quaternion,同时还会带上CoordinateFrame与colormap等配套组件。对应测试快照见 ros_occupancy_grid.snap,而 Foxglove 的VoxelGrid消息(3D 体素栅格)则由 voxel_grid.rs 转换为GridMap或VoxelGridMap。这意味着通过 Rerun 的 MCAP 导入链路,可以直接把现成的 ROS 栅格地图数据流式可视化出来。
实用要点速查
- 坐标系原点:
GridMap的translation/ 旋转参考点是图像左下角,不是中心;居中对齐时需要按[-(w*cell)/2, -(h*cell)/2, 0]偏移。 - 旋转二选一:
rotation_axis_angle与quaternion同时设置时,四元数优先。 - 数据类型:栅格值 0–255 的占用图用
L/U8;浮点代价地图可先用RvizCostmap/Costmap(要求 U8),否则 colormap 不生效。 - 纹理质量:栅格专用 colormap 下纹理为最近邻采样,栅格边界清晰无混色。
- 层次叠加:多张重叠的栅格图可用
draw_order(值大者在上)控制层级;半透明叠加用opacity。 - 默认值:
cell_size默认 0.01 场景单位/像素,opacity默认 1.0。 - 可运行示例:完整可复制的 Python / Rust / C++ 片段分别见 grid_map_simple.py、grid_map_simple.rs、grid_map_simple.cpp 与 grid_map_pose.py。
延伸阅读
- 官方类型定义(字段权威来源):crates/build/re_type_definitions/rerun/archetypes/grid_map.def.rs
- Rust SDK 生成绑定:crates/store/re_sdk_types/src/archetypes/grid_map.rs
- 渲染器实现:crates/views/re_view_spatial/src/visualizers/grid_map.rs
- 快照测试:crates/views/re_view_spatial/tests/grid_map.rs
- 组件参考:
ImageBuffer、ImageFormat、CellSize、Colormap
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考