构建语义地图时,最开始用的是 octomap_server,后面换成了 semantic_slam: octomap_generator,不过还是整理下之前的学习笔记。

一、增量构建八叉树地图步骤

为了能够让 octomap_server 建图包实现增量式的地图构建,需要以下 2 个步骤:

1.1 配置 launch 启动参数

这 3 个参数是建图必备:

  • 地图分辨率 resolution:用来初始化地图对象
  • 全局坐标系 frame_id:构建的全局地图的坐标系
  • 输入点云话题 /cloud_in:作为建图的数据输入,建图包是把一帧一帧的点云叠加到全局坐标系实现建图
<launch>
<node pkg="octomap_server" type="octomap_server_node" name="octomap_server">
<!-- resolution in meters per pixel -->
<param name="resolution" value="0.10" /> <!-- 增量式构建地图时,需要提供输入的点云帧和静态全局帧之间的 TF 变换 -->
<param name="frame_id" type="string" value="map" /> <!-- 要订阅的点云主题名称 /fusion_cloud -->
<remap from="/cloud_in" to="/fusion_cloud" />
</node>
</launch>

以下是所有可以配置的参数:

  • frame_id (string, default: /map)

    • Static global frame in which the map will be published. A transform from sensor data to this frame needs to be available when dynamically building maps.
  • resolution (float, default: 0.05)
    • Resolution in meter for the map when starting with an empty map. Otherwise the loaded file's resolution is used
  • height_map (bool, default: true)
    • Whether visualization should encode height with different colors
  • color/[r/g/b/a] (float)
    • Color for visualizing occupied cells when ~heigh_map=False, in range [0:1]
  • sensor_model/max_range (float, default: -1 (unlimited))
    • 动态构建地图时用于插入点云数据的最大范围(以米为单位),将范围限制在有用的范围内(例如5m)可以防止虚假的错误点。
  • sensor_model/[hit|miss] (float, default: 0.7 / 0.4)
    • 动态构建地图时传感器模型的命中率和未命中率
  • sensor_model/[min|max] (float, default: 0.12 / 0.97)
    • 动态构建地图时的最小和最大 clamp 概率
  • latch (bool, default: True for a static map, false if no initial map is given)
    • 不管主题是锁定发布还是每次更改仅发布一次,为了在构建地图(频繁更新)时获得最佳性能,请将其设置为 false,如果设置为 true,在每个地图上更改都会创建所有主题和可视化。
  • base_frame_id(string, default: base_footprint)
    • The robot's base frame in which ground plane detection is performed (if enabled)
  • filter_ground(bool, default: false)
    • 动态构建地图时是否应从扫描数据中检测并忽略地平面,这会将清除地面所有内容,但不会将地面作为障碍物插入到地图中。如果启用此功能,则可以使用 ground_filter 对其进行进一步配置
  • ground_filter/distance (float, default: 0.04)
    • 将点(在 z 方向上)分割为接地平面的距离阈值,小于该阈值被认为是平面
  • ground_filter/angle (float, default: 0.15)
    • 被检测平面相对于水平面的角度阈值,将其检测为地面
  • ground_filter/plane_distance (float, default: 0.07)
    • 对于要检测为平面的平面,从 z = 0 到距离阈值(来自PCL的平面方程的第4个系数)
  • pointcloud_[min|max]_z (float, default: -/+ infinity)
    • 要在回调中插入的点的最小和最大高度,在运行任何插入或接地平面滤波之前,将丢弃此间隔之外的任何点。您可以以此为基础根据高度进行粗略过滤,但是如果启用了 ground_filter,则此间隔需要包括接地平面。
  • occupancy_[min|max]_z (float, default: -/+ infinity)
    • 最终 map 中要考虑的最小和最大占用单元格高度,发送可视化效果和碰撞 map 时,这会忽略区间之外的所有已占用体素,但不会影响实际的 octomap 表示。
  • filter_speckles(bool)
    • 是否滤除斑

1.2 要求 TF 变换

有了全局坐标系和每一帧的点云,但是建图包怎么知道把每一帧点云插入到哪个位置呢?

因为随着机器人的不断移动,会不断产生新的点云帧,每个点云帧在全局坐标系中插入的时候都有一个确定的位置,否则构建的地图就不对了,因此需要给建图包提供一个当前帧点云到全局坐标系的位姿,这样建图包才能根据这个位姿把当前获得的点云插入到正确的位置上。

在 ROS 中可以很方便的使用 TF 来表示这个变换,我们只需要在启动建图包的时候,在系统的 TF 树中提供「cloud frame -> world frame」的变换就可以了:

cloud frame -> world frame (static world frame, changeable with parameter frame_id)

注意:

  • cloud frame:就是输入点云话题中 head.frame_id,比如 Robosense 雷达的 frame_id = rslidar
  • world frame:这是全局坐标系的 frame_id,在启动 launch 中需要手动给定,比如我给的是 map

如果你给 world frame id 指定的是输入点云的 frame_id,比如 fusion_cloud.head.frame_id == wolrd_frame_id == rslidar,则只会显示当前帧的八叉树,而不会增量构建地图,这点要注意了,可以自己测试看看。

那么为了增量式建图,还需要在系统的 TF 树中提供「rslidar -> world」的变换,这个变换可以通过其他的 SLAM 等获得,比如我测试时候的一个 TF 树如下:

我找了下源代码 OctomapServer.cpp 中寻找 TF 的部分:

	tf::StampedTransform sensorToWorldTf;
try {
m_tfListener.lookupTransform(m_worldFrameId, cloud->header.frame_id, cloud->header.stamp, sensorToWorldTf);
} catch(tf::TransformException& ex){
ROS_ERROR_STREAM( "Transform error of sensor data: " << ex.what() << ", quitting callback");
return;
}

总体来说这个建图包使用起来还是挺简单的,最重要的就是要写清楚输入点云话题和 TF 变换。

小 Tips:没有 TF 怎么办?

我刚开始建图的时候,我同学录制的 TF 树中并没有「world -> rslidar」的变换,只有「world -> base_link」,所以为了能够测试增量式建图,因为我的点云帧的 frame_id 是 rslidar,因此我就手动发布了一个静态的「base_link -> rslidar」的变换:

rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 base_link rslidar

这样系统中就有「rslidar -> world」的变换了,但是我发的位姿都是 0,所以对建图测试没有影响,为了方便启动,放在 launch 中:

<node pkg = "tf2_ros" type = "static_transform_publisher" name = "dlonng_static_test_broadcaster" args = "0 0 0 0 0 0 base_link rslidar" />

如果你也遇到这个问题,可以试试发个静态 TF 做做测试,关于静态 TF 详细技术可以参考之前的文章:ROS 机器人技术 - 静态 TF 坐标帧

二、ColorOctomap 启用方法

为了显示 RGB 颜色,我分析了下源码,第一步修改头文件,打开注释切换地图类型:OctomapServer.h

// switch color here - easier maintenance, only maintain OctomapServer.
// Two targets are defined in the cmake, octomap_server_color and octomap_server. One has this defined, and the other doesn't
// 打开这个注释
#define COLOR_OCTOMAP_SERVER #ifdef COLOR_OCTOMAP_SERVER
typedef pcl::PointXYZRGB PCLPoint;
typedef pcl::PointCloud<pcl::PointXYZRGB> PCLPointCloud;
typedef octomap::ColorOcTree OcTreeT;
#else
typedef pcl::PointXYZ PCLPoint;
typedef pcl::PointCloud<pcl::PointXYZ> PCLPointCloud;
typedef octomap::OcTree OcTreeT;
#endif

CMakeList.txt 文件中有 COLOR_OCTOMAP_SERVER 的编译选项:

target_compile_definitions(${PROJECT_NAME}_color PUBLIC COLOR_OCTOMAP_SERVER)

OctomapServer.cpp 中有 colored_map 的参数:

m_useHeightMap = true;
m_useColoredMap = false; m_nh_private.param("height_map", m_useHeightMap, m_useHeightMap);
m_nh_private.param("colored_map", m_useColoredMap, m_useColoredMap);

地图默认是按照高度设置颜色,如果要设置为带颜色的地图,就要禁用 HeightMap,并启用 ColoredMap:

if (m_useHeightMap && m_useColoredMap) {
ROS_WARN_STREAM("You enabled both height map and RGB color registration. This is contradictory. Defaulting to height map.");
m_useColoredMap = false;
}

第二步、需要在 octomap_server 的 launch 文件中禁用 height_map,并启用 colored_map

<param name="height_map" value="false" />
<param name="colored_map" value="true" />

2 个核心的八叉树生成函数 insertCloudCallbackinsertScan 中有对颜色的操作:

#ifdef COLOR_OCTOMAP_SERVER
unsigned char* colors = new unsigned char[3];
#endif // NB: Only read and interpret color if it's an occupied node
#ifdef COLOR_OCTOMAP_SERVER
m_octree->averageNodeColor(it->x, it->y, it->z, /*r=*/it->r, /*g=*/it->g, /*b=*/it->b);
#endif

三、保存和显示地图

启动 octomap_server 节点后,可以使用它提供的地图保存服务,保存压缩的二进制存储格式地图:

octomap_saver mapfile.bt

保存一个完整的概率八叉树地图:

octomap_saver -f mapfile.ot

安装八叉树可视化程序 octovis 来查看地图:

sudo apt-get install octovis

安装后重启终端,使用以下命令显示一个八叉树地图:

octovis xxx.ot[bt]

四、源码阅读笔记

在开组会汇报的时候,整理了以下这个建图包的关键流程,只有 2 个关键的函数也不是很复杂,我给代码加了注释,在文末可以下载。

第一步是订阅点云的回调:

第二步是插入单帧点云构建八叉树,这里就不详细介绍过程了,因为涉及到八叉树库 octomap 的更新原理:

放一张我们学院后面的一条小路的建图结果,分辨率是 15cm:

以下是我建图过程的 launch:

<launch>
<node pkg="octomap_server" type="octomap_server_node" name="octomap_server">
<!-- resolution in meters per pixel -->
<param name = "resolution" value = "0.15" /> <!-- name of the fixed frame, needs to be "/map" for SLAM -->
<!-- 静态全局地图的 frame_id,但在增量式构建地图时,需要提供输入的点云帧和静态全局帧之间的 TF 变换 -->
<param name = "frame_id" type = "string" value = "camera_init" /> <!-- set min to speed up! -->
<param name = "sensor_model/max_range" value = "15.0" /> <!-- 机器人坐标系 base_link,滤除地面需要该 frame -->
<!-- <param name = "base_frame_id" type = "string" value = "base_link" /> --> <!-- filter ground plane, distance value should be big! 项目并不需要滤除地面 -->
<!-- <param name = "filter_ground" type = "bool" value = "true" /> -->
<!-- <param name = "ground_filter/distance" type = "double" value = "1.0" /> -->
<!-- 分割地面的 Z 轴阈值 value 值 -->
<!-- <param name = "ground_filter/plane_distance" type = "double" value = "0.3" /> --> <!-- 直通滤波的 Z 轴范围,保留 [-1.0, 10.0] 范围内的点 -->
<!-- <param name = "pointcloud_max_z" type = "double" value = "100.0" /> -->
<!-- <param name = "pointcloud_min_z" type = "double" value = "-1.0" /> --> <!-- <param name = "filter_speckles" type = "bool" value = "true" /> --> <param name = "height_map" value = "false" />
<param name = "colored_map" value = "true" /> <!-- 增加了半径滤波器 -->
<param name = "outrem_radius" type = "double" value = "1.0" />
<param name = "outrem_neighbors" type = "int" value = "10" /> <!-- when building map, set to false to speed up!!! -->
<param name = "latch" value = "false" /> <!-- topic from where pointcloud2 messages are subscribed -->
<!-- 要订阅的点云主题名称 /pointcloud/output -->
<!-- 这句话的意思是把当前节点订阅的主题名称从 cloud_in 变为 /pointcloud/output -->
<remap from = "/cloud_in" to = "/fusion_cloud" /> </node>
</launch>

我做的项目代码在这里:AI - Notes: semantic_map

ROS 八叉树地图构建 - 使用 octomap_server 建图过程总结!的更多相关文章

  1. ROS 八叉树地图构建 - 给 octomap_server 增加半径滤波器!

    为了在每帧点云中滤除噪声点,选择了半径滤波器,也用高斯滤波器测试过,但是没有半径效果好,这里记录下在 octomap_server 中增加半径滤波器的步骤,并在 launch 中配置滤波器参数. 一. ...

  2. ROS 八叉树地图构建 - 安装 octomap 和 octomap_server 建图包!

    项目要用到八叉树库 Octomap 来构建地图,这里记录下安装.可视化,并启用带颜色的 Octomap 的过程. 一.Apt 安装 Octomap 库 如果你不需要修改源码,可以直接安装编译好的 oc ...

  3. SLAM+语音机器人DIY系列:(六)SLAM建图与自主避障导航——2.google-cartographer机器人SLAM建图

    摘要 通过前面的基础学习,本章进入最为激动的机器人自主导航的学习.在前面的学习铺垫后,终于迎来了最大乐趣的时刻,就是赋予我们的miiboo机器人能自由行走的生命.本章将围绕机器人SLAM建图.导航避障 ...

  4. cartographer环境建立以及建图测试(详细级)

  5. 通过gmapping和伪造的odom,完成Kinect建图

    传感器信息: 环境深度信息:sensor_msgs/laserScan -----> RGBD三维点云数据:通过ros功能包depthimage to laserscan完成深度相机数据转换成激 ...

  6. [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增

    题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...

  7. 【bzoj3073】[Pa2011]Journeys 线段树优化建图+堆优化Dijkstra

    题目描述 Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路.N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a, ...

  8. 【bzoj4383】[POI2015]Pustynia 线段树优化建图+差分约束系统+拓扑排序

    题目描述 给定一个长度为n的正整数序列a,每个数都在1到10^9范围内,告诉你其中s个数,并给出m条信息,每条信息包含三个数l,r,k以及接下来k个正整数,表示a[l],a[l+1],...,a[r- ...

  9. [BZOJ4289] [PA2012] Tax 解题报告 (最短路+差分建图)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4289 4289: PA2012 Tax Time Limit: 10 Sec  Memo ...

随机推荐

  1. unity-热更-InjectFix(一)

    1 C#热更新预备知识 1.1 mono.cecil注入 使用Mono.Cecil实现IL代码注入 注入之后修改dll,新增mdb文件: 注意,待了解参数注释打开会报错: 1.2 InjectFix ...

  2. 循序渐进VUE+Element 前端应用开发(17)--- 菜单资源管理

    在权限管理系统中,菜单也属于权限控制的一个资源,应该直接应用于角色,和权限功能点一样,属于角色控制的一环.不同角色用户,登录系统后,出现的系统菜单是不同的.在VUE+Element 前端中,我们菜单结 ...

  3. linux 安装 mysql8

    1. 下载地址: https://dev.mysql.com/downloads/file/?id=484922 2. 安装 mysql80-community-release-el7-3.noarc ...

  4. DEX文件解析--5、dex方法原型解析

    一.前言    前几篇文章链接:     DEX文件解析---1.dex文件头解析     DEX文件解析---2.Dex文件checksum(校验和)解析     DEX文件解析--3.dex文件字 ...

  5. 一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~

    前言 公司有一个 Web 管理系统,使用 Tomcat 进行部署.由于是后台管理系统,所有的网页都需要登录授权之后才能进行相应的操作. 起初这个系统的用的人也不多,为了节省资源,这个系统仅仅只是单机部 ...

  6. SpringBoot整合Swagger3生成接口文档

    前后端分离的项目,接口文档的存在十分重要.与手动编写接口文档不同,swagger是一个自动生成接口文档的工具,在需求不断变更的环境下,手动编写文档的效率实在太低.与swagger2相比新版的swagg ...

  7. create-react-app中的babel配置探索

    版本 babel-loader version:"8.1.0" create-react-app:"3.4.1" 三个配置 第一部分: { test: /\.( ...

  8. 题解 CF997E 【Good Subsegments】

    先将问题进行转化,发现满足\((max-min)-(r-l)=0\)的区间即为好区间. 对于本题这样的统计子区间的问题,先将询问离线,按右端点排序一个一个解决,固定右端点,然后通过数据结构来处理出区间 ...

  9. node的async模块

    废话不多说,直接开始 这个模块有几种方法.分别用于的不通的情况自己喜欢怎么用就怎么用 第一个方法,series  这个方法用于串行切无关联.什么意思那就是,里面的方法是一个一个执行的,每一个方法相互不 ...

  10. 前端学习(十三):CSS盒子模型

    进击のpython ***** 前端学习--CSS盒子模型 在前面的时候也说过,包括分析网页结构的时候,提到了,网页就其实就是一个一个盒子叠起来的 那现在就是有装饰的盒子,难度就变得深刻 所以说为了能 ...