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

news2024/9/20 20:46:07

前面看完了膨胀地图相关的内容,这里根据前面看过的内容手搓一张膨胀地图试一下。

1、数据预处理

第一步,先进行数据预处理,为了后续计算方便,首先在这里预先计算两张二维数组表,后续遍历时会用到这张表:

void map_test3::computeCaches()
{//Case 1:如果栅格的膨胀半径是0,那就直接返回。
    
    if (robot_radius == 0)
        return;
    cached_distances_.clear(); // 分配内存
    for (unsigned int i = 0; i <= 50; ++i) {
        for (unsigned int j = 0; j <= 50; ++j) {
            cached_distances_[i][j] = hypot(static_cast<double>(i), static_cast<double>(j));
            ROS_INFO("i:%d,j:%d,value:%f",i,j,cached_distances_[i][j]);
        }
    }

    ROS_INFO("computeCost");
    cached_costs_.clear();
    // 计算代价
    for (unsigned int i = 0; i <= 50; ++i) {
        for (unsigned int j = 0; j <= 50; ++j) {
            cached_costs_[i][j] = computeCost(hypot(static_cast<double>(i), static_cast<double>(j)));
            //ROS_INFO("i:%d,j:%d,value:%f",i,j,cached_costs_[i][j]);
        }
    }
}

这个函数内涉及到了两张表,第一张存储的是三角形的斜边长,它的意义是当前栅格在离它最近的障碍物点之间的栅格距离。而第二章表存储的是代价值,它与第一张表是相对应的,代表的是这个距离的cost值的变化。这里面还用到了一个computeCost函数,它与源码中的基本上是差不多的,只是源码中的变量weight这里暂时使用了固定值代替,但是不影响大体运行思路。

2、地图订阅与膨胀

在两张表处理完成后,接下来需要一张现有的地图,所以这里要进行一张地图的订阅与数据处理。地图的订阅比较简单,就是将数据存储到raw_data内,但是需要注意一个问题,那就是我们是不能直接对这张地图进行膨胀的,因为如果障碍物紧贴地图边缘的话,根据机器人大小向外膨胀时会超出地图边界,这样的话数据计算上会变得比较麻烦,因此,这里获取到原始数据后首先对其按照机器人大小进行膨胀,这样子后续就不用担心处理边界的问题:

void map_test3::updateBounds()
{
    int length = int(robot_radius/map_resolution)+1;
    ROS_INFO("length:%d",length);
    for(int i=0;i<map_height+2*length;i++)
    {
        for(int j=0;j<map_width+2*length;j++)
        {
            if(i<length)  //地图上边界膨胀
            {
                expend_data.push_back(-1);
            }
            else if(length <= i && i<map_height+length)
            {
                if(j<length)
                {
                    expend_data.push_back(-1);
                }
                else if(length<=j && j<map_width+length)
                {
                    expend_data.push_back(raw_data[map_width*(i-length)+j-length]);
                }
                else
                {
                    expend_data.push_back(-1);
                }
            }
            else
            {
                expend_data.push_back(-1);
            }
        }
    }
}

updateBounds函数的中心思想还是很简单的,它只需要将原始数据按顺序保留,在地图外部扩张出一部分栅格即可,对于这些扩张出来的栅格,先可以将其设置为未知(-1)。

3、障碍物点云的提取与膨胀

这一部分的思想与上一篇中源码的思想基本上是一致的,首先提取出地图中所有的障碍物点,然后对其进行遍历,更改其占用值,当然,对于这些点而言,它的占用值是不变的,然后,下一步更新其周围四个点的数据:

void map_test3::updateCosts()
{
    int length = int(robot_radius/map_resolution)+1;
    int seen_size_ = (map_height+2*length) * (map_width+2*length);
    if (seen_ == NULL) {// seen_的阵列为空,重新赋值
        ROS_WARN("InflationLayer::updateCosts(): seen_ array is NULL");
        int seen_size_ = (map_height+2*length) * (map_width+2*length);
        seen_ = new bool[seen_size_]();
    }
    else if (seen_size_ != (map_height+2*length) * (map_width+2*length))
    {
        ROS_WARN("InflationLayer::updateCosts(): seen_ array size is wrong");// seen_的阵列为错误,删除后重新赋值
        delete[] seen_;
        seen_size_ = (map_height+2*length) * (map_width+2*length);
        seen_ = new bool[seen_size_]();//这里分配了一个size 和master map 尺寸一样的bool 数组
    }
    else
    {
        if (seen_)
            delete[] seen_;
        seen_size_ = (map_height+2*length) * (map_width+2*length);
        seen_ = new bool[seen_size_]();
    }
    inflation_cells_.clear();
    inflation_cells.clear();
    map_seen.clear();
    for (int i = 0; i < map_height+2*length; i++)
    {
        for (int j = 0; j < map_width+2*length; j++)
        {
            int index = i*(map_width+2*length)+j;    //1)从master_grid地图获取(i,j)对应的索引index
            unsigned char cost = expend_data[index];  //2)从master_grid地图获取索引对应的代价cost
            if (cost == 100)               //3)先判断代价是否等于致命障碍物对应的代价,如果是,压入
            { //注意这里的obs_bin是一个指针,数据其实是存储在inflation_cells_下
                //std::map<double, std::vector<CellData> > inflation_cells_;
                CellData celldata;
                celldata.index_ = index;
                celldata.src_x_ = i;
                celldata.src_y_ = j;
                celldata.x_ = i;
                celldata.y_ = j;
                inflation_cells_[0].push_back(celldata);
                inflation_cells.push_back(celldata);
            }
        }
    }
    //根据膨胀队列,进行膨胀
    //通过上面的操作,现在inflation_queue_里面全部都是obstacle cell,这些cell被打包成CellData类型,包含了这些cell本身的索引,最近障碍物的索引(即它们本身),距离(0)
    std::map<double, std::vector<CellData> >::iterator bin;
    int count = 0;
    int interate = 0;
    while(inflation_cells.size()>0)// && count<12000)
    {
        CellData cell;
        cell = inflation_cells[0];
        //记录该celldata的索引index
        unsigned int index = cell.index_;

        // ignore if already visited  如果已访问过,则忽略
        if (seen_[index])
        {
            //ROS_INFO("index: %d has been seen",index);
            inflation_cells.erase(inflation_cells.begin());
            continue;
        }
        seen_[index] = true;
        map_seen[index] = true;
        //得到该cell的坐标和离它最近的障碍物的坐标
        unsigned int mx = cell.x_;
        unsigned int my = cell.y_;
        unsigned int sx = cell.src_x_;
        unsigned int sy = cell.src_y_;
        //ROS_INFO("mx:%d,my:%d,src_x:%d,src_y:%d",mx,my,sx,sy);
        //4)更新膨胀队列中点的cost 值
        //根据该CELL与障碍的距离来分配cost
        
        unsigned char cost = costLookup(mx, my, sx, sy);
        unsigned char old_cost = expend_data[index];  //从master_grid查找指定索引index的代价
        //如果old_cost无信息或者cost≥内接膨胀障碍物,将cost更新到master_array
        if (old_cost == NO_INFORMATION && (inflate_unknown_ ? (cost > FREE_SPACE) : (cost >= INSCRIBED_INFLATED_OBSTACLE)))
            expend_data[index] = cost;
        else//否则比较新旧大小,将大的cost更新到master_array
            expend_data[index] = std::max(old_cost, cost);
        ROS_INFO("index:%d",index);
        // attempt to put the neighbors of the current cell onto the inflation list
        //上面首先从inflation_cells_中取出最大distance的cell,再将这个cell的前后左右四个cell都塞进inflation_cells_。
        //由于下面关于enqueue的调用,最后两个参数(sx, sy)没有改变,所以保证了这个索引一定是obstacle cell。
        //由于在 enqueue入口会检查 if (!seen_[index]),这保证了这些cell不会被重复的塞进去。
        //由于这是一个priority queue,因此障碍物的周边点,每次都是离障碍物最远的点被弹出,并被检查,这样保证了这种扩张是朝着离障碍物远的方向进行。
        //尝试将当前单元的邻居(前后左右)放到队列中 
        //ROS_INFO("COUNT:%d",count);
        //if (mx > 0 && count<1000)
        if (mx > 0)
            enqueue(index - 1, mx - 1, my, sx, sy);
        if (my > 0)
            enqueue(index - (map_width+2*length), mx, my - 1, sx, sy);       
        if (mx < (map_width+2*length) - 1)
            enqueue(index + 1, mx + 1, my, sx, sy);
        if (my < (map_height+2*length) - 1)
            enqueue(index + (map_width+2*length), mx, my + 1, sx, sy);
        inflation_cells.erase(inflation_cells.begin());    
    }
}

这个函数的中心在于while循环以及循环内的enqueue函数,enqueue函数是非常重要的一个函数,它的主要内容如下:

inline void map_test3::enqueue(int index, unsigned int mx, unsigned int my,
                                    unsigned int src_x, unsigned int src_y)
{
  if (!seen_[index])
  {
    // we compute our distance table one cell further than the inflation radius dictates so we can make the check below
    //找它和最近的障碍物的距离,如果超过了阈值,则直接返回
    //如果不超过阈值,就把它也加入inflation_cells_数组里
    double distance = distanceLookup(mx, my, src_x, src_y);
    // we only want to put the cell in the list if it is within the inflation radius of the obstacle point
    if (distance*map_resolution > robot_radius)
      return;

    // push the cell data onto the inflation list and mark
    CellData celldata;
    celldata.index_ = index;
    celldata.x_ = mx;
    celldata.y_ = my;
    celldata.src_x_ = src_x;
    celldata.src_y_ = src_y;
    inflation_cells_[distance].push_back(celldata);
    inflation_cells.push_back(celldata);
  }
}

在enqueue函数中会判断当前点到最近障碍物的距离。如果处于一个危险范围内(distance*map_resolution < robot_radius)则会将该点加入到遍历的队列,后续会根据点到障碍物点的距离更新其代价值,如果这个点离障碍物足够远则可以认为当前这个点是足够安全的,则不需要更新该点,通过这种方式,可以将所有离障碍物点足够近的点都遍历一遍,并修改这些点的占用值为合适的数据。

地图的重新发布

在经过第三步的遍历后,也就修改完成了所有栅格点的占用数值,然后,我们将其重新发布出来,得到一张处理后的地图,类似于如下状态:
在这里插入图片描述
后续就可以在这张地图上进行进一步的处理,例如结合障碍物地图以及路径规划实现半动态的路径规划:
在这里插入图片描述

小结

通过地图的遍历以及每个点到障碍物的距离与代价值一一对应,可以在一次遍历下完成地图的膨胀,整体来说算法的效率还是比较高的。通过该部分算法再结合激光点云以及其他算法,可以实现很多需要的功能,例如全局路径规划等。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UniAudio 1.5:大型语言模型(LLMs)驱动的音频编解码器

大型语言模型&#xff08;LLMs&#xff09;在文本理解和生成方面展示了卓越的能力&#xff0c;但它们不能直接应用于跨模态任务&#xff0c;除非进行微调。本文提出了一种跨模态上下文学习方法&#xff0c;使未进行进一步训练的LLMs能够在少量示例的情况下&#xff0c;无需任何…

stack=s+stack#TypeError: can only concatenate str (not “list“) to str

PYTHON的神奇操作 stack[1,2] s"ddd" # stacksstack#TypeError: can only concatenate str (not "list") to str stacks print(stack)#[1, 2, d, d, d] stack[1,2] s"ddd" stacksstack # 这里会报错&#xff0c;因为不能直接将字符串和列表相加…

强大的多数据库客户端工具:DataGrip【送源码】

今天给大家带来的工具是&#xff1a;DataGrip 介绍 DataGrip是jetbrains开发的一款关系数据库和 NoSQL 数据库的多数据库客户端工具&#xff0c;可以30天免费试用&#xff0c;后续使用需要购买。 DataGrip还是一款强大的跨平台工具&#xff0c;支持多种操作系统&#xff0c;比…

创建线程

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 由于线程是操作系统直接支持的执行单元&#xff0c;因此&#xff0c;高级语言&#xff08;如Python、Java等&#xff09;通常都内置多线程的支持。Py…

京东h5st4.73

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; lianxi a15018601872 …

只有你相信,客户才会相信

我们外贸人在一起聊天的时候&#xff0c;可能最常说的话就是我们的产品不好做&#xff0c;出单太难了&#xff0c;是不是产品不行啊&#xff1f;但是又不知道什么产品好做&#xff0c;好不容易听到一款产品好做吧&#xff1f;自己又不懂&#xff0c;摸索不出来&#xff0c;因此…

洛谷 P1726:上白泽慧音 ← Tarjan算法

【题目来源】https://www.luogu.com.cn/problem/P1726【题目描述】 在幻想乡&#xff0c;上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞&#xff0c;使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新…

李宏毅2023机器学习作业HW06解析和代码分享

ML2023Spring - HW6 相关信息&#xff1a; 课程主页 课程视频 Sample code HW06 视频 HW06 PDF 个人完整代码分享: GitHub | Gitee | GitCode P.S. HW06 是在 Judgeboi 上提交的&#xff0c;出于学习目的这里会自定义两个度量的函数&#xff0c;不用深究&#xff0c;遵循 Sugge…

JS正则表达式构造函数和正则表达式字面量的区别

背景 笔者在使用正则表达式的过程中&#xff0c;经常看到两种使用方式&#xff0c;比较好奇这两种方式有什么不同。 一种是 正则表达式构造函数&#xff1a;new RegExp(“[xxx]”) 另一种是 正则表达式字面量&#xff1a; /[xxx]/ 于是&#xff0c;就去网上搜了一下…结果看到国…

【H5全行业数据大屏展示】—— 数据大屏分享

数据大屏展示是一种将大量数据以可视化形式展示在屏幕上的方式&#xff0c;可以帮助人们更直观地理解和分析数据。在各行各业中&#xff0c;数据大屏展示已经成为一种流行的工具&#xff0c;被广泛应用于数据分析、决策支持和业务监控等方面。在本文中&#xff0c;将分享一些数…

“论数据访问层设计技术及其应用”写作框架,系统架构设计师

论文真题 在信息系统的开发与建设中&#xff0c;分层设计是一种常见的架构设计方法&#xff0c;区分层次的目的是为了实现“高内聚低耦合”的思想。分层设计能有效简化系统复杂性&#xff0c;使设计结构清晰&#xff0c;便于提高复用能力和产品维护能力。一种常见的层次划分模…

文件系统实验(操作系统)

文件系统实验 【预备知识】 1.文件系统的文件类型 为了便于用户利用终端进行输入和输出&#xff0c;UNIX系统做了专门安排。UNIX系统自动为用户打开3个文件&#xff1a;标准输入、标准输出和标准错误输出文件&#xff0c;文件描述符分别为0、1、2&#xff0c;缺省时&#xff0c…

分班查询,一键发布,老师们都在用的分班查询系统

老师们开学季马上又要到了&#xff0c;回想起了每年埋头苦干&#xff0c;对着一堆堆的学生名单&#xff0c;一个个手动分配班级&#xff0c;再一个个通知家长和学生的日子&#xff0c;那种手忙脚乱&#xff0c;生怕出错的紧张感&#xff0c;是不是还历历在目&#xff1f;每次分…

Instagram Reels API接口——高效获取用户主页Reels视频

一、引言 Instagram作为全球知名的社交媒体平台&#xff0c;近年来推出的Reels功能受到了广大用户的热烈欢迎。Reels以短视频的形式&#xff0c;让用户能够轻松创作和分享有趣、有创意的内容。为了帮助开发者、品牌和分析师更好地利用这一功能&#xff0c;我们推出了一款专注于…

从视频创意到传播策略 | 医药产品TVC新媒体传播方案

作为营销策划人&#xff0c;你一定在寻找能够激发创意灵感、拓展策划视野的实战案例。这份最新传播方案由Unithought精心打造&#xff0c;不仅是一份详尽的策划指南&#xff0c;更是一次深入患者心灵的品牌传播实践。 何策网&#xff0c;每日收录全网方案PPT &#xff01; 方…

手把手教你如何修复填补画图时间序列时出现的空白区域,Python向,Plotly库

填补画图时出现的空白区域&#xff0c;Python向&#xff0c;Plotly库 画图的烦恼美丽的plotly库首选安排时间序列的引索Index接下来我们安排plotly来画图继续修正图的格式 画图的烦恼 大家画时间序列的时候肯定遇到过画图没有软件里来的那么舒服&#xff0c;怎么画都会出现空白…

计算机顶级会议和顶级期刊

顶级会议 国际计算机设计会议&#xff08;ICCD&#xff09;&#xff1a;由国际电气与电子工程师协会&#xff08;IEEE&#xff09;主办&#xff0c;是计算机体系结构领域的国际顶级会议之一&#xff0c;已经成功举办四十余届。 NeurIPS&#xff1a;全称神经信息处理系统大会&a…

机械臂 CoppeliaSim Simulink联合仿真

实现机械臂在CoppeliaSim&#xff08;以前称为V-REP&#xff09;和Simulink上的联合仿真涉及多个步骤&#xff0c;包括环境设置、模型导入、通信配置、控制算法设计和测试调试。 前期准备 安装软件配置工作环境创建和配置CoppeliaSim场景 导入机械臂模型配置机械臂参数在Simuli…

goldfish loss:减少训练数据泄漏,提高大语言模型输出的多样性

LLMs&#xff08;大型语言模型&#xff09;能够记忆并重复它们的训练数据&#xff0c;这可能会带来隐私和版权风险。为了减轻记忆现象&#xff0c;论文作者引入了一种名为"goldfish loss"的微妙修改&#xff0c;在训练过程中&#xff0c;随机抽样的一部分标记被排除在…

阿里云ECS(CentOS/Alibaba Cloud Linux)安装最新 Docker 方法

最近&#xff08;6月份&#xff09;我发现 docker 官方无法正常访问&#xff0c;docker pull 命令也执行失败&#xff0c;用 TZ 也一样&#x1f614;。 以下步骤适用于 CentOS 7/8或Alibaba Cloud Linux 系统。 1. 更新系统包 首先&#xff0c;确保您的ECS实例系统软件包是最…