【基础计算】ESDF栅格距离图计算并行加速版
前言与参考
这一部分仅为路径规划源码及论文GPIR的一个小部分,但是有代码实现,第一次看的时候有些懵,所以特此记录;主要是设置好了栅格地图后,添加了障碍物后,对其的欧式距离计算和梯度计算等。原代码中为了加速,不同以往的直接每个点逐个计算遍历,而是分了两个步骤同时使用多线程并行计算进行加速
参考文献:
- 2002论文:a general algorithm for computing distance transforms in linear time
- 完整GPIR 路径规划源码 github链接
- 仅sdf_test 的debug及测试代码 gitee链接
- openmp并行计算:介绍与简单测试链接
通常在计算图/栅格距离时,复杂度为 \(O(m\times n)\) 也就是pixel of image OR dim of map,其中在计算欧式距离时因为还有一些额外的计算操作所以在计算大图时会有些耗时,此处根据参考1论文中的方法提出一种可以使用多线程并行计算的方式,使得理论上复杂度为 \(O(m\times n/p)\) 其中p为线程数量,可以通过结果表得知,在较为大的pixel or dim下,加速较为明显

现在的主要应用可能是在加速路径规划时的栅格地图及距离图时效果较为明显,效果预览(灰黑为grid图,彩色为离障碍物距离图,最右边为距离梯度示意图)

以下我们进行简洁伪代码及实际代码对应,及整个过程的详细讲解
简洁过程
论文中总结
- In a first phase each column \(C_x\) is separately scanned. (defined by points \((x , y)\) with x fixed) 其中距离由最近的点决定
- In a second phase each row \(R_y\) is separately scanned. (defined by points \((x , y)\) with y fixed). 对于\(R_y\)上的每个点,最小的EDT值为\((x-x')^2+G(x',y)^2\) 其中 \((x',y)\)沿row \(R_y\)

可以看到伪代码对应的第一和第二阶段,和实际代码中的 完全对应
void SignedDistanceField2D::EuclideanDistanceTransform(
    std::array<int, 2> dim,
    std::function<bool(const int x, const int y)> is_occupied,
    DistanceMap* output_map) {
  int inf = dim[0] + dim[1] + 10;
  std::vector<std::vector<int>> g(dim[0], std::vector<int>(dim[1], 0));
  omp_set_num_threads(1);
  {
#pragma omp parallel for
    // column scan phase 1====
    for (int x = 0; x < dim[0]; ++x) {
      g[x][0] = is_occupied(x, 0) ? 0 : inf;
      for (int y = 1; y < dim[1]; ++y) {
        g[x][y] = is_occupied(x, y) ? 0 : 1 + g[x][y - 1];
      }
      for (int y = dim[1] - 2; y >= 0; --y) {
        if (g[x][y + 1] < g[x][y]) g[x][y] = 1 + g[x][y + 1];
      }
    }
  }
  // row scan phase 2====
  omp_set_num_threads(1);
  {
#pragma omp parallel for
    for (int y = 0; y < dim[1]; ++y) {
      int q = 0, w;
      std::vector<int> s(dim[0], 0);
      std::vector<int> t(dim[0], 0);
      auto f = [&g, &y](int x, int i) -> double {
        return (x - i) * (x - i) + g[i][y] * g[i][y];
      };
      for (int u = 1; u < dim[0]; ++u) {
        while (q >= 0 && f(t[q], s[q]) > f(t[q], u)) {
          --q;
        }
        if (q < 0) {
          q = 0;
          s[0] = u;
        } else {
          w = 1 + std::floor((u * u - s[q] * s[q] + g[u][y] * g[u][y] -
                              g[s[q]][y] * g[s[q]][y]) /
                             (2 * (u - s[q])));
          if (w < dim[0]) {
            ++q;
            s[q] = u;
            t[q] = w;
          }
        }
      }
      for (int u = dim[0] - 1; u >= 0; --u) {
        output_map->SetValue(u, y, map_resolution_ * std::sqrt(f(u, s[q])));
        if (u == t[q]) --q;
      }
    }
  }
}
详细讲解
因为论文比较久远有些符号的使用并不像现在这样,可以点击上面原文进行观看 EDT全称: Euclidean Distance Transform,首先定义一下久远的符号 \(\operatorname{MIN}(k:P(k):f(k))\) 是指 \(k\)在 \(P(k)\)范围内使得 \(f(k)\) 最小的值
正常的距离计算通常需要逐次计算距离 \(dt[x,y]=\sqrt{EDT(x,y)}\)
\]
为了所有的EDT,MDT, CDT的计算方便后续和代码中的G 如下:
\operatorname{EDT}(x, y) &=\operatorname{MIN}\left(i: 0 \leq i<m:(x-i)^{2}+G(i, y)^{2}\right)\\
\text{where } G(i, y)&=\operatorname{MIN}(j: 0 \leq j<n \wedge b[i, j]:|y-j|)
\end{aligned}\tag{2}
\]
然后就进入了第一阶段,可以看出第一阶段的column scan是可以直接并行的(仔细对着代码看看就能看出来了),因为每个column和其他直接互不影响,然后column下在往下直接for row,第一阶段算的是G值,比如示例test中的如果把对角线都设为1,其他给0(假设>0即 被占据),原始占据图与G图如下:

比较难以理解的应该是第二阶段,但是第二阶段其实就是上面公式(2)所示,直接手推一下,取min,如上图右边,只是论文的大部分在介绍如何优雅的写出这样的并行for
第二阶段首先y是fixed,也就是一个column看下来,所以简化一下公式二就是把y去掉
\]
其中 \(f(x,i)\) 的定义,因为全文同时对其他的距离函数也进行了说明,所以原文中分了出来,但是我们在这里仅看着欧式距离计算哈,可以看到和公式(2)的区别就是y被藏起来了,因为y已经是fixed了
\]
申明下图Fig 2. \(F_i\) 就是 点 \((i,g(i))\) 的曲线连接而成,我们想要的是min 最小的 所以是solid line 实线的部分

假设这个实线的一系列点(从左到右)的index是:\(s[0],s[1],\dots,s[q]\)
对应给定了upper bound \(u >0\) 定义使 \(x\) 最小的新 index \(h\) 通常来说x是有大于1的minimizer,定义拿到的最小的那个 \(0 \le h<u\) 使得对于所有的\(i\) 都有:\(f(x, h) \leq f(x, i)\)
\]
由此定义处\(s(u)\) 即从左到右的scan,拿到minimizers
S(u) &=\{H(x, u) \mid 0 \leq x<m\} \\
T(h, u) &=\{x \mid 0 \leq x<m \wedge H(x, u)=h\} \text { if } 0 \leq h<u
\end{aligned}
\tag{4}
\]
为了找到 \(x^*\) 我们引入 \(\text{Sep}\) ,\(\text{Sep}(i,u)\) 指的是第一个不小于水平坐标相交点\(F_u\)和\(F_i\) 的整数,公式表示也就是:
\]
由此可以获取\(x^*=\text{Sep}(s[l^*],u)\), 然后\(\text{Sep}\)这个取决于我们想要计算的距离是什么,比如EDT的话就是:
& F_{i}(x) \leq F_{u}(x) \\
\Leftrightarrow &\left\{\text { definition of } F_{i}, F_{u}\right\} \\
&(x-i)^{2}+g(i)^{2} \leq(x-u)^{2}+g(h)^{2} \\
\Leftrightarrow &\{\text { calculus; } i<u ; x \text { is an integer }\} \\
& x \leq\left(u^{2}-i^{2}+g(u)^{2}-g(i)^{2}\right) \text { div }(2(u-i))
\end{aligned}
\]
总结完就是:
\]
但是感觉好像... 理论还有漏的地方,暂时就先这样吧... 后面有机会再补充.. 因为感觉超出了我的...能力范围,总感觉卡在哪里了,可能就是卡在了这for 套for 再套while里把,对着伪代码写出来 emm能用 ok;用ab的话:调用就行了, hhhh
代码对应
然后回看伪代码和代码,所有的标和变量名称基本保持了一致
  omp_set_num_threads(4);
  {
#pragma omp parallel for
    for (int y = 0; y < dim[1]; ++y) {
      int q = 0, w;
      std::vector<int> s(dim[0], 0);
      std::vector<int> t(dim[0], 0);
      auto f = [&g, &y](int x, int i) -> double {
        return (x - i) * (x - i) + g[i][y] * g[i][y];
      };//公式2中求距离的
      for (int u = 1; u < dim[0]; ++u) {
        while (q >= 0 && f(t[q], s[q]) > f(t[q], u)) {//公式4对比大小
          --q;
        }
        if (q < 0) {
          q = 0;
          s[0] = u;
        } else {
          w = 1 + std::floor((u * u - s[q] * s[q] + g[u][y] * g[u][y] -
                              g[s[q]][y] * g[s[q]][y]) /
                             (2 * (u - s[q])));//公式5 +1
          if (w < dim[0]) {
            ++q;
            s[q] = u;
            t[q] = w;
          }
        }
      }
      for (int u = dim[0] - 1; u >= 0; --u) {
        output_map->SetValue(u, y, map_resolution_ * std::sqrt(f(u, s[q])));
        if (u == t[q]) --q;
      }
    }
  }
结果展示
如果我们设一个100x100的 然后对角线设为1的话,得到的图,其中左边是grid map右边是距离转了jet color图,蓝色即代表离被占据(也就是黑色那个1)越近的距离

打印数字的话10x10的grid如下(即欧式距离 每个格的分辨率为1)

测试包内包括了Bilinear进行grid 梯度求解,后面再补充进来,结果如图:

【基础计算】ESDF栅格距离图计算并行加速版的更多相关文章
- 明风:分布式图计算的平台Spark GraphX  在淘宝的实践
		快刀初试:Spark GraphX在淘宝的实践 作者:明风 (本文由团队中梧苇和我一起撰写,并由团队中的林岳,岩岫,世仪等多人Review,发表于程序员的8月刊,由于篇幅原因,略作删减,本文为完整版) ... 
- MaxCompute 图计算用户手册(上)
		概要 ODPS GRAPH是一套面向迭代的图计算处理框架.图计算作业使用图进行建模,图由点(Vertex)和边(Edge)组成,点和边包含权值(Value),ODPS GRAPH支持下述图编辑操作: ... 
- 图计算 on nLive:Nebula 的图计算实践
		本文首发于 Nebula Graph Community 公众号 在 #图计算 on nLive# 直播活动中,来自 Nebula 研发团队的 nebula-plato 维护者郝彤和 nebula-a ... 
- 关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL))
		关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL)) 欢迎fork本项目原始链接:关于图计算&图学习的基础知识概览:前置知识点学习 ... 
- 关于图计算和graphx的一些思考[转]
		原文链接:http://www.tuicool.com/articles/3MjURj “全世界的网络连接起来,英特纳雄耐尔就一定要实现.”受益于这个时代,互联网从小众的角落走到了历史的中心舞台.如果 ... 
- MaxCompute 图计算开发指南
		快速入门step by step MaxCompute Studio 创建完成 MaxCompute Java Module后,即可以开始开发Graph了. 代码示例 在examples目录下有gra ... 
- Spark入门实战系列--9.Spark图计算GraphX介绍及实例
		[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .GraphX介绍 1.1 GraphX应用背景 Spark GraphX是一个分布式图处理 ... 
- GraphX 图计算实践之模式匹配抽取特定子图
		本文首发于 Nebula Graph Community 公众号 前言 Nebula Graph 本身提供了高性能的 OLTP 查询可以较好地实现各种实时的查询场景,同时它也提供了基于 Spark G ... 
- 图计算引擎分析——Gemini
		前言 Gemini 是目前 state-of-art 的分布式内存图计算引擎,由清华陈文光团队的朱晓伟博士于 2016 年发表的分布式静态数据分析引擎.Gemini 使用以计算为中心的共享内存图分布式 ... 
- 开源图计算框架GraphLab介绍
		GraphLab介绍 GraphLab 是由CMU(卡内基梅隆大学)的Select 实验室在2010 年提出的一个基于图像处理模型的开源图计算框架.框架使用C++语言开发实现. 该框架是面向机器学习( ... 
随机推荐
- 小程序中使用 lottie 动画 | 踩坑经验分享
			最近被拉去支援紧急需求(赶在五一节假日前上线的,双休需要加班),参与到项目中才知道,开发的项目是微信小程序技术栈的.由于是临时支援,笔者也很久没开发过微信小程序了,所以挑选了相对独立,业务属性相对轻薄 ... 
- 【YoloDeployCsharp】基于.NET Framework的YOLO深度学习模型部署测试平台
			1. 项目介绍 基于.NET Framework 4.8 开发的深度学习模型部署测试平台,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det.Seg.Pose.Obb ... 
- 【爬虫+情感判定+饼图+Top10高频词+词云图】"王心凌"热门弹幕python舆情分析
			目录 一.背景介绍 二.代码讲解-爬虫部分 2.1 分析弹幕接口 2.2 讲解爬虫代码 三.代码讲解-情感分析部分 3.1 整体思路 3.2 情感分析打标 3.3 统计top10高频词 3.4 绘制词 ... 
- WebKit Inside: CSS 样式表的解析
			CSS 全称为层叠样式表(Cascading Style Sheet),用来定义 HTML 文件最终显示的外观. 为了理解 CSS 的加载与解析,需要对 CSS 样式表的组成,尤其是 CSS Sele ... 
- U.2与M.2接口
			U.2接口 U.2接口别称SFF-8639,是由固态硬盘形态工作组(SSD Factor Work Group)推出的接口规范.U.2接口不但能支持SATA-Express(一种PCI-E与SATA混 ... 
- WEB服务与NGINX(8)-NGINX的长连接功能
			1. 长连接配置 keepalive_timeout; 定义客户端保持连接超时时长,0表示禁止长连接,默认为65s,建议使用15s即可. 在ngx_http_upstream_module中也有此项设 ... 
- UE4 绘制Gizmo
			Unity的Gizmos可以很方便的在编辑器下进行调试,Unreal中也有一些办法可以达到效果. 本文主要参考:https://zhuanlan.zhihu.com/p/363625037,进行了一些 ... 
- layui表格内可编辑下拉框
			表格内可编辑下拉框扩展自别人的表格内下拉框 一.列模板,这是列配置的templet字段需要使用的. 1.inputdiv,输入框覆盖在下拉框上面左半部.这个样式用来调整输入框和下拉框不会超出单元格. ... 
- linux下常用的快捷键和$参数
			1.下面介绍两个在linux下非常有用的$参数 2.!$ 表示引用上一个命令的最后一个参数,例子如下: [root@node5 ~]# echo '!$的作用是引用上一个命令的最后一个参数' > ... 
- MyBatis反射模块源码分析
			说明:本文参考至https://www.jianshu.com/p/baba62bbc107 MyBatis 在进行参数处理.结果映射时等操作时,会涉及大量的反射操作.为了简化这些反射相关操作,MyB ... 
