longer95479@home:~$

SOAR: Simultaneous Exploration and Photographing with Heterogeneous UAVs for Fast Autonomous Reconstruction


整体思路:探索 -> 视角生成 -> 任务分配(多机)-> 规划轨迹

Surface Frontier-based Exploration

表面前沿体素 $v_{sf}$ 定义

步骤:

  • 对 $v_{sf}$ 聚类,求质心
  • 在多个类的质心的某个半径内采样生成不同高度、yaw 的候选视角
  • 对每个类上的候选视角选择一个作为代表,标准为视角可以看到最多的 $v_{sf}$
  • 使用 LKH 算法求解 ATSP,获得最短的遍历以上选出的视角的路径

以上思路主要借鉴于 FUEL

尝试改进探索步骤

Incremental Viewpoint Generation

Uncovered Surface Extraction

对已知表面体素进行聚类,不与任何表面前沿接触的(对角线接触也算接触)体素聚类被称作 完全探索表面(Completelt Explored Surface),之后会从这些完全探索表面体素中 提取 原始点云,并将这些体素标记为“已提取”。下一时刻地图更新,会有新的完全探索表面,并对其提取点云,但之前已经提取过点云的体素不会再次提取了。

为了减少非法视角的生成,对提取的点云 $\mathbf{PT}_{new}$ 进行滤波。

\[\mathbf{PT}_{new} \ \mathrm{filtered\ by\ } \mathbf{CV}_{hq} + \mathbf{PT}_{unc,prev}\rightarrow PT_{unc}\]

$\mathbf{PT}_{new}$ 的点云无论是从论文还是从代码上来看,都是几块聚类上的点云去提取得到的,之后会在函数 updateSurfaceState

  • 插入到 VR.all_filtered_pts 末尾
  • 也会插入到 VR.new_filtered_pts_,但 VR.new_filtered_pts_ 会在函数 surfaceViewpointsGeneration 中的末尾进行清空操作 VR.new_filtered_pts_.clear();

VR.vps_voxcount_[vp_id] 也是全局的

数据结构 VR 里是有对覆盖点独占数和贡献度作区分的,在 updateViewpointInfo 里会对 VR 里的贡献度 VR.vps_contri_num_[vp_id] 作计算更新,这是唯一进行赋值的地方。原版的贡献度是 视角看到的点数,而不是独占数。 而 VR.vps_voxcount_[vp_id] -= 1; 则是分配后的独占数,从这句代码也可以看出来,并且 Prune 里对视角的排序也是基于 VR.vps_voxcount_ 的。

prune 里的 updateViewpointInfo 是用来更新引力筛选后仍然活跃的微调后的视角的信息

利用法向来生成视角,原方法不知道正反向,可以尝试改进成我跟墙中的方法。需要修改的主要函数如下:

void SurfaceCoverage::computeUncoveredViewpoints(
    const Vector3d& pt_pos, const Vector3d& pt_normal, vector<pcl::PointNormal>& unc_vps)

已修改测试完成,主要是利用 sdf 的 grad 来作方向参考

[computeUncoveredViewpoints] cost time: 0.000015     
[computeUncoveredViewpointsUsingSdfGrad] cost time: 0.000003
[computeUncoveredViewpoints] total time: 0.002430
---
[computeUncoveredViewpoints] cost time: 0.000016
[computeUncoveredViewpointsUsingSdfGrad] cost time: 0.000002
[computeUncoveredViewpoints] total time: 0.001961
---
[computeUncoveredViewpoints] cost time: 0.000016
[computeUncoveredViewpointsUsingSdfGrad] cost time: 0.000003
[computeUncoveredViewpoints] total time: 0.001898
---
[computeUncoveredViewpoints] cost time: 0.000005
[computeUncoveredViewpointsUsingSdfGrad] cost time: 0.000003
[computeUncoveredViewpoints] total time: 0.000860

代码中的算法和论文的有些出入:采样不是基于法向量,而是表面聚类平均位置周围的圆柱体作采样,然后计算平均 yaw 角(朝向点云,此时生成的视角的 pitch 均设为 0,有点奇怪),最后只把覆盖数最大的视角 push 进去,相当于只对一个 surface 分配一个视角。

为什么加起来不相等?

[ WARN] [1773303869.745404627]: [Coverage Manager] Number of Total Voxels = 2563
[ WARN] [1773303869.745459548]: [Coverage Manager] Seen all voxels num = 2500
[ WARN] [1773303869.745496276]: [Coverage Manager] Number of Uncovered Voxels = 52
vector<bool> new_cover_state_all;
vector<int> new_cover_contrib_num_all;
vector<int> new_contrib_id_all;

src/planner/heterogeneous_manager/src/coverage_manager.cpp 715 这几个 vector 的 idx 都是 subspace-idx,也就是对应 VR.new_filtered_pts_ 这些新提取的点,而不是全局的点云 idx。

vector<int> uncovered_all_pt_ids;  // find the id corresponding to the global point cloud

这句代码则是将未被已有视角覆盖的新点云的“局部 idx” 转换到“全局 idx”。“局部”是指新提取的点云这个集合,“全局”是指程序运行开始后累积下来的所有点云。

多机之间的通信结构如何?

Consistent-MDMTSP for Task Assignment

在 FC-Planner 中是通过骨架来分割空间,对视角进行聚类。而在本方法 SOAR 中,则是通过可见性和距离阈值来对视角进行聚类,再将问题建模成 MDMTSP,即 $N_p$ 架 photographer 去不重复地遍历 $N_{vct}$ 个未访问的 VCTs(viewpoint clustering task)。

FUEL

栅格地图局部更新触发 FIS(Frontier Information Structure)的局部更新,进一步触发分层规划,生成探索轨迹。

INCREMENTAL FRONTIER INFORMATION STRUCTURE

对 cluster 进行主成分分析,如果 first axis 大于阈值,则沿着 first axis 对 cluster 进行分割,分成两个小 cluster,递归进行,不断分割。

生成视角中,对于一个 cluster,不会直接使用其质心作为视角位置,而是以质心为中心的极坐标系中去均匀采样视角位置,然后在每个视角位置上以优化方式求出最佳 yaw,基准是覆盖到最多的 frontier 栅格。

HIERARCHICAL EXPLORATION PLANNING

(1) Global Exploration Tour Planning

构建邻接矩阵,使用 tsp 求解器进行求解。

(2) Local Viewpoint Refinement

相邻的连续几个 cluster 的候选视角组成一个有向无环图(DAG),使用 Dijstra 来查找最短路径,称为 Refinement。

Total views. cowboys. Hits