【小白深度教程 1.26】手把手教你使用 Open3D(9)对点云进行语义分割(完整代码在最后)

【小白深度教程 1.26】手把手教你使用 Open3D(9)对点云进行语义分割(完整代码在最后)

在这篇文章中,我们将学习如何使用 Open3D 对自动驾驶目的的点云进行实时语义分割。

在这里插入图片描述
我们必须处理几个阶段,包括:

  1. 预处理,
  2. 自定义TensorFlow 算子集成
  3. 后处理
  4. 可视化

此外,我们会展示如何实现最大的精度和运行时性能,以及 Open3D 如何帮助简化这一过程。

1. 点云分割

我们使用广为人知的 PointNet++ 架构:

在这里插入图片描述

然后我们的实现使用 Open3D 重新构建,并在需要时偏离了参考设计,以提升性能,具体改进将在下文中描述。

我们使用 KITTI 的数据来进行实验:
在这里插入图片描述

在使用 KITTI 数据集进行推理时,我们将感兴趣区域设置为汽车前后各 30m,左右各 10m 以适应大小。

我们使用的语义分割模型是在 Semantic3D 数据集上训练的,我们重点介绍了使 KITTI 数据集上的实时推理成为可能的技术。

2. 使用 Open3D 加速的 TensorFlow 操作优化 PointNet++

在 PointNet++ 的集合抽象层中,原始点云会被下采样,并且下采样点的特征必须通过插值传播到所有原始点(参见 PointNet++ 的第 3.4 节)。这通过 3-近邻搜索来实现,作者提供了一个通过自定义 TensorFlow 操作 ThreeNN 实现的简单 C++ 实现 。然而,这成为了 PointNet++ 预测模型的瓶颈。

以下基准测试结果是在有颜色的 Semantic3D 数据集上,对一批 64 个样本进行推理时运行的基准测试脚本获得的。正如我们所见, ThreeNN 操作占据了图执行时间的 87%。

// Batch time
Batch size: 64, batch_time: 1.8208365440368652

// Per-op time
node name |           total execution time |  accelerator execution time |        cpu execution time |
ThreeNN            1.73sec (100.00%, 87.61%),        0us (100.00%, 0.00%),   1.73sec (100.00%, 95.87%)
ThreeInterpolate     60.68ms (12.39%, 3.07%),        0us (100.00%, 0.00%),      60.68ms (4.13%, 3.36%)
GroupPoint            27.31ms (9.32%, 1.38%),   27.03ms (100.00%, 15.85%),        275us (0.77%, 0.02%)
Conv2D                26.91ms (7.94%, 1.36%),    23.99ms (84.15%, 14.07%),       2.91ms (0.76%, 0.16%)

Open3D 使用 FLANN 构建 KD 树以快速检索最近邻,这可以用于加速 ThreeNN 操作。此自定义 TensorFlow 操作的实现必须与 Open3D 和 TensorFlow 库链接。为了方便地链接各种依赖项,我们提供了一个 CMake 文件,它会自动下载、构建并链接 Open3D。当 Open3D 正确安装(在本例中是自动的)后,可以简单地使用 Open3D 的 CMake 查找器来包含头文件并链接 Open3D,如下所示:

target_include_directories(tf_interpolate PUBLIC ${Open3D_INCLUDE_DIRS})
target_link_libraries(tf_interpolate tensorflow_framework ${Open3D_LIBRARIES})

有关如何将 C++ 项目链接到 Open3D 的更多详细信息,请参阅相关文档。

接下来,我们重构了 ThreeNN 以使用 Open3D。简而言之,首先使用参考点创建一个 KD 树:

open3d::KDTreeFlann reference_kd_tree(reference_pcd);

然后,对于每个目标点,在 KD 树中搜索 3 个最近邻:

// for each j:
reference_kd_tree.SearchKNN(target_pcd.points_[j], 3, three_indices, three_dists);

在使用 Open3D 重构 ThreeNN 之后,我们看到 ThreeNN 操作和整个模型在批量大小为 64 时的运行时间都加速了约 2 倍。

// Batch time
Batch size: 64, batch_time: 0.7777869701385498

// Per-op time
node name |             total execution time |  accelerator execution time |         cpu execution time |
ThreeNN            694.14ms (100.00%, 73.72%),         0us (100.00%, 0.00%),   694.14ms (100.00%, 90.20%)
ThreeInterpolate      62.94ms (26.28%, 6.68%),         0us (100.00%, 0.00%),       62.94ms (9.80%, 8.18%)
GroupPoint            27.18ms (19.60%, 2.89%),    26.90ms (100.00%, 15.63%),         287us (1.62%, 0.04%)
Conv2D                26.39ms (16.71%, 2.80%),     23.83ms (84.37%, 13.85%),        2.56ms (1.58%, 0.33%)

3. 后处理:加速标签插值

由于我们在向PointNet++提供点之前对原始数据集进行了抽样,因此网络输出只对应于原始点云的一个稀疏子集。

推理结果:
在这里插入图片描述
插值结果:
在这里插入图片描述
稀疏标签需要插值来生成所有输入点的标签。这种插值可以通过使用open3d的最近邻搜索来实现。KDTreeFlann和多数投票,类似于我们上面在ThreeNN op中所做的。

def interpolate_dense_labels(sparse_points, sparse_labels, dense_points, k=3):
    sparse_pcd = open3d.PointCloud()
    sparse_pcd.points = open3d.Vector3dVector(sparse_points)
    sparse_pcd_tree = open3d.KDTreeFlann(sparse_pcd)

    dense_labels = []
    for dense_point in dense_points:
        _, sparse_indexes, _ = sparse_pcd_tree.search_knn_vector_3d(
            dense_point, k
        )
        knn_sparse_labels = sparse_labels[sparse_indexes]
        dense_label = np.bincount(knn_sparse_labels).argmax()
        dense_labels.append(dense_label)
    return dense_labels

然而,在Python中这样做可能会对性能造成重大影响。我们在KITTI数据集上运行完整的kitti_predict.py推断以进行基准测试。插补步骤大约占用总运行时间的90%,并将整个管道减慢到大约1 FPS。

$ python kitti_predict.py --ckpt path/to/checkpoint.ckpt
...
[ 1.05 FPS] load_data: 0.0028, predict: 0.0375, interpolate: 0.9076, visualize: 0.0031, total: 0.9545
[ 1.06 FPS] load_data: 0.0028, predict: 0.0355, interpolate: 0.8952, visualize: 0.0025, total: 0.9396
[ 1.04 FPS] load_data: 0.0028, predict: 0.0348, interpolate: 0.9214, visualize: 0.0024, total: 0.9653
...

为了解决性能问题,我们添加了另一个自定义的 TensorFlow C++ 操作 InterpolateLabel 。该操作接受稀疏点 sparse_points 、稀疏标签 sparse_labels 、密集点 dense_points 并输出密集标签 dense_labels 。使用 OpenMP 并行化 KNN 树搜索。同时,操作中还添加了 dense_colors 输出,以直接输出标签着色的密集点。有关详细信息,请参考源代码。

使用这种方法的另一个好处是,现在整个预测和插值的管道都可以用一个 TensorFlow 操作图实现。也就是说,TensorFlow 会话接受原始的密集点,并直接返回密集标签和标签着色的密集点。这种方法比在 TensorFlow 图外部进行插值更模块化且高效。经过优化后,端到端的管道在 KITTI 数据集上实现了平均 10+ FPS 的速度,这比 KITTI 的捕获速率 10 FPS 更快。

4. 代码地址:

https://github.com/isl-org/Open3D-PointNet2-Semantic3D

>