栅格地图、障碍物地图与膨胀地图(障碍物地图(三)写一张障碍物地图)

花了不少时间看完了障碍物地图的大致思路,这里简单根据前面的思路来写一个简易版的障碍物地图。

1.订阅一张地图

首先,我们需要一张静态地图作为原始数据,这个我们可以订阅当前的map来获取:

void map_test1::MapCallback(const nav_msgs::OccupancyGrid::ConstPtr& map)
{
    map_origin = map->info.origin;
    ROS_INFO("map_origin.x:%f,y:%f",map_origin.position.x,map_origin.position.y);
    map_resolution = map->info.resolution;
    ROS_INFO("map_resolution:%f",map_resolution);
    map_width = map->info.width;
    map_height = map->info.height;
    ROS_INFO("map_width:%d",map_width);
    ROS_INFO("map_height:%d",map_height);
    raw_data.clear();
    for(int i=0;i<map->data.size();i++)
    {
        raw_data.push_back(map->data[i]);
    }
    first_receive = true;
    ROS_INFO("get raw map");
    map_sub.shutdown();
}

这里代码的处理比较简单,只是简单的存储了当前地图的基本信息数据,以给到后续代码使用。

2.订阅激光数据

第一部分我们获取到的只是最基本的原始地图数据,然后我们需要将障碍物添加到地图中去,也就是当前的激光点云,在原代码中,对于来自传感器的点云是存储了一定时间内的,这里我们按照原代码的思路也需要存储一定时间下的点云:

void map_test1::ScanCallback(const sensor_msgs::LaserScan::ConstPtr& scan_in)
{   
    sensor_msgs::PointCloud mapcloud; 
    if(scan_in->header.frame_id.find("laser")==string::npos)
	    return;
	if(!tf_listener.waitForTransform(
            scan_in->header.frame_id,
            "/map",
            scan_in->header.stamp + ros::Duration().fromSec(scan_in->ranges.size()*scan_in->time_increment),
           ros::Duration(1))){
        //ROS_INFO("timestamp error");
        return;
    }    
    try
    {
        projector_.transformLaserScanToPointCloud("/map",*scan_in,mapcloud,tf_listener);
    }
    catch(const std::exception& e)
    {
        ROS_ERROR("%s", e.what());
    }	
	mapcloud.header.frame_id = scan_in->header.frame_id;
	tf::StampedTransform transform;
    try{
      tf_listener.lookupTransform("/map", "/base_link",
                               ros::Time(0), transform);
    }
    catch (tf::TransformException &ex) {
        ROS_ERROR("%s",ex.what());
        ros::Duration(1.0).sleep();
        return;
    }
    Obstacle obstacle_cloud;
    obstacle_cloud.origin_.x = transform.getOrigin().getX();
    obstacle_cloud.origin_.y = transform.getOrigin().getY();
    obstacle_cloud.obstacle_range_ = 25.0;
    obstacle_cloud.cloud_.points = mapcloud.points;
	//processCloud(mapcloud);
    double now_time = ros::Time::now().toSec();
    laser_points.insert(std::make_pair(now_time, obstacle_cloud));
}

简单解析一下上述代码,首先第一部分是调用的ROS下的开源包transformLaserScanToPointCloud将激光点云在传感器坐标系转换到map坐标系下,然后同步存储该时刻下的机器人位姿(参考Observation类函数的形式),最后统一存储到obstacle_cloud下。注意这里我没有使用类函数的形式去保存,因为代码写的比较简单,直接使用了结构体进行的数据存储,然后再使用unordered_map的格式存储的一系列数据。通过这种方式实现了一个类似于Observation类以及ObservationBuffer的处理方式。

3.删除时间过久的数据

对于那些超过一定时间的点云,我们这里也是同样需要对其进行删除的,删除的方式也很简单,直接从unordered_map中删除掉这一条数据就可以了:

void map_test1::DeletePoint()
{
    
    if (laser_points.empty()) {
        return;
    }

    double now_time = ros::Time::now().toSec();
    auto ite = laser_points.begin();

    while (ite != laser_points.end()) {
        if (now_time - ite->first > dely_time) {
            // 删除当前元素,并预取下一个元素的迭代器
            laser_points.erase(ite++);
        } else {
            ++ite; // 正常移动到下一个元素
        }
    }
}

4.将激光点云添加到地图中

这里就用到了上一章中看过的东西了:

void map_test1::AddScanData()
{
    //拷贝原始数据
    obstacle_map_data = raw_data;
    //遍历激光点云
    unordered_map<double, Obstacle>::iterator ite;
    //ROS_INFO("MAP.SIZE:%zu",laser_points.size());
    for (ite = laser_points.begin(); ite != laser_points.end(); ite++) {
        //ROS_INFO("point.size:%ld",ite->second.cloud_.points.size());
        for(int i=0;i<ite->second.cloud_.points.size();i++)
        {
            //舍弃地图外的点
            if(!InMap(ite->second.cloud_.points[i].x,ite->second.cloud_.points[i].y))
            {
                continue;
            }
            //舍弃距离过远的点
            if(sqrt((ite->second.cloud_.points[i].x-ite->second.origin_.x)*(ite->second.cloud_.points[i].x-ite->second.origin_.x)+
            (ite->second.cloud_.points[i].y-ite->second.origin_.y)*(ite->second.cloud_.points[i].y-ite->second.origin_.y))>ite->second.obstacle_range_)
            {
                continue;
            }
            ModifyMap(ite->second.cloud_.points[i].x,ite->second.cloud_.points[i].y,obstacle_map_data);
        }
    }
}

首先,我们需要遍历之前存储的每一帧激光点云中的点,判断这个点是否在地图上:

bool map_test1::InMap(double x,double y)
{
    if(x>(map_origin.position.x+map_resolution*map_width) || x<map_origin.position.x)
    {
        //ROS_INFO("point not in map,point position:%f,%f",x,y);
        //ROS_INFO("map max_x:%f,min_x:%f",map_origin.position.x+map_resolution*map_width,map_origin.position.x);
        return false;
    }
    else if(y>(map_origin.position.y+map_resolution*map_height) || y<map_origin.position.y)
    {
        //ROS_INFO("point not in map,point position:%f,%f",x,y);
        //ROS_INFO("map max_y:%f,min_y:%f",map_origin.position.y+map_resolution*map_height,map_origin.position.y);
        return false;
    }
    return true;
}

对于那些不在地图中的数据我们就没有必要再进行后续的处理了。然后我们根据之前设置的obstacle_range_参数判断这个点到当时机器人本体所在位置的距离是否足够近,由此排除掉一些非常远的点。

//舍弃距离过远的点
            if(sqrt((ite->second.cloud_.points[i].x-ite->second.origin_.x)*(ite->second.cloud_.points[i].x-ite->second.origin_.x)+
            (ite->second.cloud_.points[i].y-ite->second.origin_.y)*(ite->second.cloud_.points[i].y-ite->second.origin_.y))>ite->second.obstacle_range_)
            {
                continue;
            }

然后,我们就可以对剩下的点调用ModifyMap函数修改对应点所在的地图栅格值:

void map_test1::ModifyMap(double x,double y,std::vector<int> &map)
{
    int mx,my;
    if(!worldToMap(x,y,mx,my))
    {
        return;
    }
    map[my * map_width + mx] = 100;
}

注意这里调用了worldToMap是根据源代码来写的,输入地图点的坐标返回的是栅格地图的XY索引。最后转化成一维坐标并修改对应的值:

bool map_test1::worldToMap(double wx, double wy, int& mx, int& my)
{
    if (wx < map_origin.position.x || wy < map_origin.position.y)
    return false;

    mx = (int)((wx - map_origin.position.x) / map_resolution);
    my = (int)((wy - map_origin.position.y) / map_resolution);

    if (mx < map_width && my < map_height)
        return true;

    return false;
}

到此,一个简单的障碍物地图就差不多实现了,运行这个代码并给定一些地图以及激光数据,我们大概就能得到类似于这样子的情况:
原始地图:
在这里插入图片描述加入障碍物点云:
在这里插入图片描述
上述图片中是保留了2s内的激光点云的,所以在运动的时候会看到边界会显得更加粗一点,部分动态障碍物还存在托尾。
放一段动图:

简单障碍物地图实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件库V1.5版本iApp源码V3

软件库V1.5版本iApp源码V3 配置教程在【mian.iyu】的【载入事件】 更新内容&#xff1a; 1、分类对接蓝奏&#xff08;免费&#xff0c;付费&#xff0c;会员&#xff0c;广告&#xff09;&#xff0c;支持蓝奏文件描述设置为简介&#xff08;改动&#xff1a;首页.iyu&#…

Kubernetes二进制(单master)部署

文章目录 Kubernetes二进制&#xff08;单master&#xff09;部署一、常见的K8S部署方式1. Minikube2. Kubeadmin3. 二进制安装部署4. 小结 二、K8S单&#xff08;Master&#xff09;节点二进制部署1. 环境准备1.1 服务器配置1.2 关闭防火墙1.3 修改主机名1.4 关闭swap1.5 在/e…

Linux常用指令集合

ls显示目录文件 选项&#xff1a; -a 所有文件&#xff08;all所有&#xff09; -l 详细信息&#xff08;Information信息&#xff09;&#xff08;自动包含-1&#xff09; 所以常用 ll -1 一行只输出一个文件。 -R 列出所有子目录下的文件。…

运维别卷系列 - 云原生监控平台 之 02.prometheus exporter 实践

文章目录 [toc]exporter 简介常用的 exporternode-exporter 实践创建 svc创建 daemonsetprometheus 配置服务发现 exporter 简介 随着 Prometheus 的流行&#xff0c;很多系统都已经自带了用于 Prometheus 监控的接口&#xff0c;例如 etcd、Kubernetes、CoreDNS 等&#xff0c…

基于Springboot的校园疫情防控信息管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控信息管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

保研机试之【文件描述符】

A选项&#xff1a; 一个文件描述符对应着系统级文件表中的一项 B选项 C选项 D选项 E选项 F选项 综上&#xff0c;我认为这道题选择B、C、E、F~

内网工具之LDP的使用

LDP 是微软自带的一款活动目录信息查询工具&#xff0c;在域控的 cmd 窗口执行 ldp 命令即可打开 LDP 工具。普通域成员主机默认是没有 LDP 工具的&#xff0c;可以自行上传ldp.exe 工具上去查询活动目录信息。不在域内的机器&#xff0c;也可以通过上传 ldp.exe 工具上去执行。…

接口测试基础

1、接口测试 接口&#xff1a;系统之间数据交互的通道。 硬件接口软件接口 接口测试&#xff1a;基于不同的输入参数&#xff0c;校验接口响应数据与预期数据是否一致。 接口地址 接口参数 2. 为什么要学接口测试&#xff1f; 提前介入测试、尽早发现问题 3、接口测试学什…

网上有哪些赚钱的方法能一天赚二三十?盘点7个靠谱的搞钱副业和赚钱软件

想在家里躺着就能把钱赚&#xff1f;这不再是遥不可及的梦想&#xff01;随着互联网的飞速发展&#xff0c;网上赚钱的方式层出不穷&#xff0c;总有一款适合你。 今天&#xff0c;就让我们一起揭开这些神秘面纱&#xff0c;看看哪些网上赚钱秘诀能让你轻松实现月入过万&#x…

OpenAI GPT-4

本文翻译整理自&#xff1a;https://openai.com/index/gpt-4-research/ (March 14, 2023) 文章目录 一、关于 GPT-4二、能力视觉输入Visual inputs: chart reasoningSample 2 of 7 操纵性Steerability: Socratic tutorSample 1 of 3 三、局限性四、风险与缓解措施五、训练流程…

运维别卷系列 - 云原生监控平台 之 01.prometheus 入门和部署

文章目录 [toc]什么是 PrometheusPrometheus 架构及其一些生态系统组件Prometheus 的工作模式Prometheus 的适用场景Prometheus 的不适用场景Prometheus 词汇表 Prometheus 启动参数Prometheus 配置文件通用占位符定义配置文件示例解释服务发现 Prometheus 部署创建 namespace创…

Vue入门到关门之Vue3学习

一、常用API 注意&#xff1a;本文项目均使用脚手架为 Vite 1、setup函数 &#xff08;1&#xff09;介绍 如果在项目中使用配置项API&#xff0c;那么写起来就和vue2的写法是一样的&#xff1b;但是如果在项目中写的是组合式API&#xff0c;那么组件中所用到的&#xff1a…

深度学习入门到放弃系列 - 阿里云人工智能平台PAI部署开源大模型chatglm3

通过深度学习入门到放弃系列 - 魔搭社区完成开源大模型部署调用 &#xff0c;大概掌握了开源模型的部署调用&#xff0c;但是魔搭社区有一个弊端&#xff0c;关闭实例后数据基本上就丢了&#xff0c;本地的电脑无法满足大模型的配置&#xff0c;就需要去租用一些高性价比的GPU机…

宝兰德成为中国信通院政企信创促进中心成员 共谱信创产业新篇章

近日&#xff0c;中国信通院云计算标准和开源推进委员会2024年第一次全体工作会议圆满结束。会上&#xff0c;宝兰德获得由中国信通院和EDCC政企信息技术应用创新促进中心授予的荣誉证书&#xff0c;成为政企信创促进中心成员单位。 关于政企信创促进中心 中国信通院政企信创促…

uniapp高性能图片裁剪插件,可添加水印

效果图&#xff1a; 插件地址&#xff1a;高性能图片裁剪&#xff0c;裁剪图片后自动添加水印 - DCloud 插件市场 示例&#xff1a; <template> <view><button click"select">选择图片</button><image mode"widthFix" :src&qu…

DOM重点核心(注册事件+DOM事件流)

目录 1.注册事件 注册时间概述 addEventListener() 删除事件 2.DOM事件流 DOM事件流理论 事件对象 事件对象的常见属性和方法 e.targe 和 this的区别 阻止默认行为 阻止冒泡 事件委托 禁止右键菜单和禁止选中文字 获得鼠标的坐标&#xff08;可视区、页面、浏览器…

RIP动态路由协议详解

目录 一&#xff1a;RIP协议的基本信息 二&#xff1a;RIP协议中的更新方式 三&#xff1a;RIP协议中的计时器 定时更新器&#xff08;UPDATE timer&#xff09; 无效定时器&#xff08;invalid Timer&#xff09; 垃圾收集定时器&#xff08;garbage collection timer&a…

怎样计算Excel一列数值中十位数为5的个数?

有一列数字&#xff0c;可能正数也可能是负数&#xff0c;有可能有小数&#xff0c;要怎么计算这列数字中十位数为5的数量有多少个&#xff1f; 一、按示例情况&#xff0c;数字均为整数 公式如下&#xff1a; SUM(--(MID(A1:A6,LEN(A1:A6)-1,1)"5")) 数组公式&a…

【JS面试题】原型原型链

一、面试真题展示&#xff1a; 1. 如何准确判断一个变量是不是数组&#xff1f; ① 使用instanceof进行判断&#xff1a;a instanceof Array ② 使用Array.isArray()进行判断&#xff1a;Array.isArray(a) 2. 手写一个简易的jQuery&#xff0c;考虑插件和扩展性&#xff1f; …

python的文件操作及函数式编程介绍

五、文件操作 1、读取键盘输入 input 获取标准输入&#xff0c;数据类型统一为字符串 #!/usr/bin/python # -*- coding: UTF-8 -*- str input("请输入&#xff1a;") print&#xff08;"你输入的内容是: ", str&#xff09; 这会产生如下的对应着输入的…