栅格地图

news/2024/9/19 23:39:00/文章来源:https://www.cnblogs.com/Gaowaly/p/18316392

在ROS中,地图是非常基本的元素,特别对于2D激光SLAM而言,栅格地图可以说是必不可少的元素。机器人在需要前往目标点时,需要在栅格地图中找到一条合适的路径从当前点到达目标点,这部分内容在move_base中有了详细的接口,可以直接调用并返回路径。但是作为一名工程师,不仅要知其然更要知其所以然,正好最近重新看了下这部分的内容,在此简单对地图这块的处理简单作个笔记,以备后续翻阅。
熟悉move_base的开发者会知道,在ROS中,地图大致可以分为三张:栅格地图、障碍物地图与膨胀地图。当然也可以按照局部地图与全局地图划分,但是那个不是本篇的重点,暂时先不考虑那种划分方式。栅格地图与后续两张地图可以说是存在密不可分的关系。首先,栅格地图是作为障碍物地图的基础而存在的,没有栅格地图,亦没有障碍物地图。同时,栅格地图也是膨胀地图的必须元素,深入了解过三张地图之间相互关系的开发者会知道,在move_base中,膨胀地图这个插件(inflaction_layer)本身是不维护地图的实体的,它对于地图的膨胀是在栅格地图(静态地图)的图层进行操作的。所以有必要先了解清楚栅格地图的基础内容,这也可以为我们后续学习另外两种地图打下比较好的基础。

1、地图的消息格式

在ROS中已经定义了官方的地图格式:

nav_msgs/OccupancyGrid

我们可以通过下述指令展开看看这个消息的具体包含:

rosmsg show nav_msgs/OccupancyGrid

得到的结果如下

std_msgs/Header headeruint32 seqtime stampstring frame_id
nav_msgs/MapMetaData infotime map_load_timefloat32 resolutionuint32 widthuint32 heightgeometry_msgs/Pose origingeometry_msgs/Point positionfloat64 xfloat64 yfloat64 zgeometry_msgs/Quaternion orientationfloat64 xfloat64 yfloat64 zfloat64 w
int8[] data

以上,就是一张地图消息格式的基本组成。我们从上往下看,首先是header部分,这里跟很多消息类型中的header是一样的,包含了一张地图的时间戳以及一个frame_id,对于地图这个东西而言,似乎时间戳并不是很重要。而frame_id则是一般会固定使用“map"。
其次,则是一个nav_msgs/MapMetaData实体info,其中的内容包含了较多信息。首先map_load_time,似乎也没什么用,所有地图设置的都是0。而resolution,则是地图中非常重要的一个参数,它直接关系到了地图的清晰度。一般而言,我们建图时使用的栅格大小就是这里的resolution,使用0.05代表一个栅格是5cm,而使用0.025则代表一个栅格是2.5cm。接下来的参数则是width与height,这两个参数与resolution共同构成了一张地图的实际长宽,例如:

resolution: 0.025
width: 2048
height: 2208

代表了地图的宽度为(0.025 * 2048=51.2)米,高度为(0.025 * 2208=55.2)米。最后面是一个geometry_msgs/Pose的实体,这个参数代表的则是这张地图上的一个坐标,开始时,我曾经一度以为这个坐标是指地图中心点的坐标轴,但是实际上并不是,它代表的是地图最左下角的点在地图上的坐标,这个问题我们可以通过一张实际的地图观察一下,首先我们拿到一张实际的地图的info信息:

map_load_time: 0.000000000
resolution: 0.025
width: 2048
height: 2208
origin: position: x: -20y: -35.2z: 0orientation: x: 0y: 0z: 0w: 1

可以看到它的position为(-20,-35.2),然后我们在RVIZ中加载这张地图,并切换到地图坐标系下,可以看到地图大致显示如下:

这是一张51.2 * 55.2的地图,左上角处的栅格中心为(0,0)原点,大致就可以看出info参数中的position实际对应的就是地图的左下角部分。关于这个问题我们也可以后面通过一些简单的操作验证一下,先将数据结构看完。
剩下最后一个数据结构就是int8[] data这个字段,这个位置实际上存储的就是真正的地图实际数据,每一个栅格具体的值就是通过这里得到的,它的大小为info.width*info.height,同时,它的值只包含了三种数据:-1代表未知,也就是上图中灰色部分;0代表空闲,也就是图上的白色部分;100(注意是100不是1)代表障碍物,图上黑色部分。取一段实际地图数据,例如:

-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 0 0 0 0 -1 -1 -1 -1 0 0 0 0 0 -1 -1 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 -1 -1 0 0 0 0 -1 0 0 0 0 0 0 -1 -1 -1 0 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 100 100 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100 100 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1

可以看到实际的数据就是如上所说的形式。

2、简单发布一张地图

为了验证上述一些问题,我们使用一个简单的节点对地图进行了简单的测试,代码过于简单,细节不赘述,先po源码上来:

#include "ros/ros.h"
#include "nav_msgs/OccupancyGrid.h"
#include <string>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
class map_test2
{
private:/* data */ros::Publisher map_pub;nav_msgs::OccupancyGrid new_map;
public:map_test2(/* args */);void InitMap();void PubMap();~map_test2();
};map_test2::map_test2(/* args */)
{ros::NodeHandle n;map_pub = n.advertise<nav_msgs::OccupancyGrid>("map_test",2);
}void map_test2::InitMap()
{new_map.header.seq = 1;new_map.header.stamp = ros::Time::now();new_map.header.frame_id = "map";new_map.info.height = 100;new_map.info.width = 100;new_map.info.map_load_time = ros::Time::now();new_map.info.resolution = 0.025;new_map.info.origin.position.x = 1.0;new_map.info.origin.position.y = 1.0;new_map.info.origin.orientation.w = 1.0;for(int i=0;i<new_map.info.height;i++){for(int j=0;j<new_map.info.width;j++){if((i%10)==0||(i%10)==1){new_map.data.push_back(100);}elsenew_map.data.push_back(0);}}
}
void map_test2::PubMap()
{map_pub.publish(new_map);
}
map_test2::~map_test2()
{
}int main(int argc, char **argv)
{ros::init(argc, argv, "map_test2");ROS_INFO("map_test2_node start");map_test2 map_test2;map_test2.InitMap();while (ros::ok()){map_test2.PubMap();ros::Duration(0.05).sleep();ros::spinOnce();}return 0;
}

上述代码实现了一个简单的地图发布功能,频率为20Hz,编译并运行上述代码,我们可以在RVIZ中看到一张实际名为map_test的地图:

 

注意到我们在上面给定地图原点的时候,给出的坐标系是

new_map.info.origin.position.x = 1.0;
new_map.info.origin.position.y = 1.0;

因此,其对应的栅格的左下角第一个点的位置就是在(1,1)这个位置。同时宽度与高度都是100,分辨率为0.025,因此,我们得到了一张宽与高都为2.5m的地图。最后,我们对于每10个栅格中行数末尾为0与1结尾的行设置了100(障碍物)的值,其他都设置了0(空闲),因此我们也得到了上述的带横向黑色细线条的具体地图实体。

由此,我们大致可以知道第一部分的分析基本上是没有问题的,对于地图部分的基础内容理解基本无误。

3、move_base中对于栅格地图的简单处理

为了后续其他研究需要,顺便简单记录一点move_base节点中对于栅格地图的处理部分。由于要考虑到在后续计算膨胀地图时使用的是0-255的表示方式,因此,在move_base中,对地图进行了简单的转换:

  // initialize the costmap with static datafor (unsigned int i = 0; i < size_y; ++i){for (unsigned int j = 0; j < size_x; ++j){unsigned char value = new_map->data[index];costmap_[index] = interpretValue(value);++index;}}

 

上述是一个遍历,根据地图的宽与高进行遍历,并调用interpretValue函数返回具体参数存储到costmap_中,而interpretValue实现如下:

unsigned char StaticLayer::interpretValue(unsigned char value)
{// check if the static value is above the unknown or lethal thresholdsif (track_unknown_space_ && value == unknown_cost_value_)return NO_INFORMATION;else if (!track_unknown_space_ && value == unknown_cost_value_)return FREE_SPACE;else if (value >= lethal_threshold_)return LETHAL_OBSTACLE;else if (trinary_costmap_)return FREE_SPACE;double scale = (double) value / lethal_threshold_;return scale * LETHAL_OBSTACLE;
}

这个函数咋一看挺复杂,但是实际上很简单。首先我们先要明确其中的一些参数:track_unknown_space_这个参数字面意思跟踪未知空间,简单理解就是对未知空间的处理。我们可以看到第一个if与下面的else if是互斥的,也就是只能同时满足一个,而这个值默认为true,也就是走if语句。而unknown_cost_value_这个值为-1,对于-1,我们知道它在地图中代表的是什么?未知空间嘛。在这种情况下返回的数据是什么?NO_INFORMATION。这个值的实际值是255。如果这个时候你再看一下FREE_SPACE=0那么这个track_unknown_space_参数就很好理解了:就是对于原本地图中为未知的那部分区域,我是将它作为障碍物处理还是作为空闲处理嘛。然后下面第二个else if,将输入值跟lethal_threshold_比较,而这个值在算法中默认为100,大于等于100代表什么?障碍物。所以障碍物的点返回值为LETHAL_OBSTACLE(254)。接下来是第三个else if,这里没有作比较,只是判断了一个参数trinary_costmap_,如果trinary_costmap_为true,则直接返回FREE_SPACE(0)。而这个参数默认其实也是为true的,所以对于前面没有处理的数据在这里都会返回0。而实际上对于一张地图而言,我们判断完了它为未知情况的点(-1),判断完了它为障碍物的点(100),它本身也就只剩下为空闲(0)的点了。所以这里对其他点直接返回0本身是没有什么问题的。至于后面的:

double scale = (double) value / lethal_threshold_;
return scale * LETHAL_OBSTACLE;

想来应该是为了适配其他特殊情况而使用的,例如cartographer中的地图似乎采用的就不是三值化的地图,它存在更多的细节数据,则有可能会执行到后续语句。对于我们这里而言,目前只要了解前面这一部分差不多就可以了。

参考:
【ROS-Navigation】Costmap2D代价地图源码解读-静态层StaticLayer

栅格地图、障碍物地图与膨胀地图(栅格地图)_障碍物栅格膨胀-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/758813.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VA02/VA03增加alv

项目背景:搞备库单(新建一种类型的销售订单),备库单不出货(通过排程明细类别控制,也需要新增),正式销售订单创建时,备库单分配数量给正式SO,SO的排程明细类别也是新增的(不跑MRP), 创建后,如果备库单已有库存,则从备库单直接自动调拨库存到正式SO上,还有其它的一…

Folding Strip

第一次在考场中做出来的E题,rank也是来到了20,纪念一下,一定要相信自己呀 构造方法见官方题解,证明见下: 对于原始串\(s\),如果第一个(从左往右数)相同交界不折,选择折后面的交界,那么根据决策包容性,我们可以折一下第一个相同交界,答案不会更差,如下 如果不折第一…

【matplotlib】生成各种图表

一、场景工作中,可能需要使用脚本分析各种数据,并生成图表二、工具matplotlibhttps://matplotlib.org/stable/install/index.html三、安装python -m pip install -U pip python -m pip install -U matplotlib 四、生成图表 1、折线图import matplotlib.pyplot as plt# 准备数…

浅谈cookie和session

最近写了好多次登录注册的业务接口,那不免会听到session、cookie等概念。那么他们是什么呢?之间的关系?有啥作用呢?我这次终于好好捋清楚他们的关系了,这次做一次学习总结。关于用户信息存储相关的问题 背景 先讨论Session和Cookie,我们先了解其诞生的背景,毕竟需求推动…

施工行业必备:2024年值得选择的项目管理工具

国内外主流的10款施工项目进度管理软件对比:PingCode、Worktile、Contractor Foreman、建设工程项目管理平台(JSGC)、智慧工地综合管理系统、工程项目信息管理系统(GCXX)、Buildertrend、Procore、Autodesk Construction Cloud、Fieldwire、ClickUp、monday.com。在施工行…

spingboot 发布 https

1 进入jdk安装目录,我的是在C:\Program Files\Java\jdk1.8.0_311\bin 菜单栏 输入 cmd回车 2. 使用JDK自带的keytool工具生成ssl证书(这里注意JDK版本,版本向上兼容,高版本生成的密钥,不能用于低版本) keytool -genkey -alias gateway -storetype PKCS12 -keyalg RSA -ke…

thinkPHP/fastAdmin框架使用memcached缓存

1.安装memcached扩展: 2.重启PHP,在thinkPHP或者fastAdmin启用扩展(开放相应端口:例如11211) 在fastAdmin中,则需修改config.php里面的cache配置: 3.实际运行测试: 第一个信息正常显示说明运行成功,第二个显示false,说明缓存删除成功每天进步一点点

线性代数过滤的脏水

线性代数 向量 \(\to\),信息学中用形如 \[\begin{bmatrix}a_1\\a_2\\\vdots\\a_p\end{bmatrix} \]的矩阵来表示指向 \(\left(a_1,a_2,...,a_p\right)\) 的 \(p\) 维向量。 向量求和 直接就 \(\vec{a}+\vec{b}=\vec{c}\) 时满足 :投影 借用8分钟带你彻底弄懂《线性代数》-CSDN博…

谷歌为什么要用AIDL替代HIDL(HAL)?

谷歌为什么要用AIDL替代HIDL(HAL)?背景:官方:https://source.android.com/devices/architecture/aidl/aidl-hals.Google 在Android 11引入了AIDL for HALs,旨在代替HIDL原先的作用。在之后的Android版本推荐使用AIDL 实现Hal层的访问。这样做的原因,应该有以下几点:AIDL比…

Halcon的Blob分析

一、Blob分析流程及常用算子 1.基本步骤 Blob分析的基本步骤,这是一种理想状态,也是最基本的套路,获取图像->分割图像(区分前景像素和背景像素)->特征提取(比如面积、重心、旋转角度等)。 halcon代码实现如下: read_image(Image,‘particle’) threshold(Image, …

U454574

好奇怪的题,玩空洞的时候想到的,但原来草稿纸丢掉了qwq,懒,什么时候来补吧 看到这个很容易想到dp,设dp[i]为到第i个点的最小时间。然后用dp[i]更新后面。考虑判定如何从一个点走到另一个点。我们发现,平移原来的跳跃路径是没有关系的。通过数学方法和感新理解可以发现前面…