Skip to content

Open3D for TensorBoard

现在你可以在 TensorBoard 中使用 Open3D 进行交互式 3D 可视化。以下是一些令人兴奋的特性:

  • 保存并可视化几何体序列及其属性。观察你的 3D 数据在训练或任何处理步骤中如何更新,从而更深入地洞察你的 3D 算法。
  • 支持 PBR 材质。你的 3D 数据和模型在 TensorBoard 中看起来会与在 Open3D 中一样惊艳,并且材质也可以随算法步骤而变化。
  • 专门支持可视化 3D 语义分割(semantic segmentation)和 3D 目标检测(object detection)的输入数据、真值(ground truth)和结果。通过浏览器内控件自定义数据可视化。此外,点云的任何标量或向量自定义属性都可以被可视化,帮助你轻松调试 3D 算法。
  • 借助同步时间步和视角等功能,轻松比较算法不同运行的结果。

你可以在使用 TensorFlow 或 PyTorch 时将 3D 数据保存为 TensorBoard summary。3D 数据以 .msgpack 文件(序列化的 Open3D 几何数据)的形式保存在与 event 文件同级的 plugins/Open3D 子文件夹中。下面描述的示例可在 Open3D 仓库的 examples/python/gui/tensorboard_pytorch.py 和 examples/python/gui/tensorboard_tensorflow.py 文件中找到。另请参阅 add_3d() 函数文档,以获取对所支持的 3D 几何和材质属性的完整描述。

让我们从保存和可视化简单的几何数据(如立方体和圆柱体)开始。模型的颜色在每个步骤都会变化。首先,我们设置所需的导入并创建一些简单的几何数据。

import open3d as o3d
# Monkey-patch torch.utils.tensorboard.SummaryWriter
from open3d.visualization.tensorboard_plugin import summary
# Utility function to convert Open3D geometry to a dictionary format
from open3d.visualization.tensorboard_plugin.util import to_dict_batch
from torch.utils.tensorboard import SummaryWriter
cube = o3d.geometry.TriangleMesh.create_box(1, 2, 4)
cube.compute_vertex_normals()
cylinder = o3d.geometry.TriangleMesh.create_cylinder(radius=1.0,
height=2.0,
resolution=20,
split=4)
cylinder.compute_vertex_normals()
colors = [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)]

现在让我们将其写入 summary。

logdir = "demo_logs/pytorch/small_scale"
writer = SummaryWriter(logdir)
for step in range(3):
cube.paint_uniform_color(colors[step])
writer.add_3d('cube', to_dict_batch([cube]), step=step)
cylinder.paint_uniform_color(colors[step])
writer.add_3d('cylinder', to_dict_batch([cylinder]), step=step)

仓库中同时包含 PyTorch 和 TensorFlow 的代码示例。下面是对应的 TensorFlow 代码。需要注意的主要区别是:add_3d() 是 open3d.visualization.tensorboard_plugin.summary 模块中的一个独立函数,并且需要一个 logdir 参数。data 中也接受 TensorFlow 张量。

import open3d as o3d
from open3d.visualization.tensorboard_plugin import summary
# Utility function to convert Open3D geometry to a dictionary format
from open3d.visualization.tensorboard_plugin.util import to_dict_batch
import tensorflow as tf
# ... geometry creation code as above ...
logdir = "demo_logs/tf/small_scale"
writer = tf.summary.create_file_writer(logdir)
with writer.as_default():
for step in range(3):
cube.paint_uniform_color(colors[step])
summary.add_3d('cube', to_dict_batch([cube]), step=step, logdir=logdir)
cylinder.paint_uniform_color(colors[step])
summary.add_3d('cylinder', to_dict_batch([cylinder]), step=step,
logdir=logdir)

你可以通过将上述代码粘贴到 Python 提示符中来运行此示例,或者运行示例:

Terminal window
python examples/python/gui/tensorboard_pytorch.py small_scale

现在启动 tensorboard 来可视化数据:

Terminal window
tensorboard --logdir demo_logs/pytorch

在 Jupyter notebook 中,你可以改用:

%load_ext tensorboard
%tensorboard --logdir demo_logs/pytorch

展示每次运行的 widget 是一个通过 WebRTC 显示的 O3DVisualizer 窗口,因此所有 Open3D 控件(如缩放、平移、旋转视角、改变光照方向等)都可用。点击窗口右上角的齿轮图标可访问更多控件,例如背景颜色和点大小。

注意,在本示例中,每个步骤中只有颜色发生变化,而顶点、边和法向量保持不变。我们可以指示 Open3D 复用先前步骤的几何属性,而不是将冗余数据写入 summary 文件。将上面的 for 循环替换为:

for step in range(3):
cube.paint_uniform_color(colors[step])
cube_summary = to_dict_batch([cube])
if step > 0:
cube_summary['vertex_positions'] = 0
cube_summary['vertex_normals'] = 0
writer.add_3d('cube', cube_summary, step=step)
cylinder.paint_uniform_color(colors[step])
cylinder_summary = to_dict_batch([cylinder])
if step > 0:
cylinder_summary['vertex_positions'] = 0
cylinder_summary['vertex_normals'] = 0
writer.add_3d('cylinder', cylinder_summary, step=step)

几何属性张量被替换为一个整数(步骤引用),指向一个较早的步骤作为该几何属性的来源。你可以用以下命令运行此示例:

Terminal window
python examples/python/gui/tensorboard_pytorch.py property_reference

summary 文件夹的大小会变小,但你在 tensorboard 中看到的几何显示是一样的。

除了几何属性之外,我们还可以使用 PBR 材质属性来保存和可视化丰富的 3D 模型。

model_dir = "examples/test_data/monkey"
logdir = "demo_logs/pytorch/monkey"
model = o3d.t.geometry.TriangleMesh.from_legacy(
o3d.io.read_triangle_mesh(os.path.join(model_dir, "monkey.obj")))
# Create geometry dict
summary_3d = {
"vertex_positions": model.vertex["positions"],
"vertex_normals": model.vertex["normals"],
"triangle_texture_uvs": model.triangle["texture_uvs"],
"triangle_indices": model.triangle["indices"],
"material_name": "defaultLit"
}
# translate material property names (from texture map file names) to Open3D
# names, if needed.
names_to_o3dprop = {"ao": "ambient_occlusion"}
for texture in ("albedo", "normal", "ao", "metallic", "roughness"):
texture_file = os.path.join(model_dir, texture + ".png")
if os.path.exists(texture_file):
texture = names_to_o3dprop.get(texture, texture)
summary_3d.update({
("material_texture_map_" + texture):
o3d.t.io.read_image(texture_file)
})
# "metallic" texture map needs the base metallic scalar property.
if texture == "metallic":
summary_3d.update(material_scalar_metallic=1.0)
writer = SummaryWriter(logdir)
writer.add_3d("monkey", summary_3d, step=0)

PBR 材质属性可以是标量(如 metallic),也可以是 4 元素向量(如 base_color),适用于对整个几何体均匀的属性。随空间变化的材质属性以纹理图(texture map)图像的形式表示,并根据 UV 坐标映射到几何体表面。因此,任何纹理图都必须提供 UV 坐标(vertex_texture_uvs 或 triangle_texture_uvs 之一)。属性的键名形式为 material_[TYPE]_[PROP_NAME],其中 TYPE 为 scalar、vector 或 texture_map。在上面的示例中,使用的键为 material_texture_map_albedo、material_texture_map_normal、material_texture_map_ambient_occlusion、material_texture_map_metallic 和 material_texture_map_roughness。我们还提供了一个 material_name,用于指定所使用的 Open3D 材质着色器。

Open3D 的 3DML 模型内置了对可视化输入数据、真值和网络预测的支持。编辑 Open3D-ML 仓库中 ml3d/configs 文件夹下与你的模型 + 数据集组合对应的 YAML 配置文件,即可开始保存 summary 3D 数据:

# Open3D for Tensorboard summary (3D data)
summary:
# Record summary in these stages (from train, valid, test)
record_for: ['valid']
# Subsample point cloud if n_pts exceeds this value. save all
# points in the summary.
max_pts: 10000
# Only write input point cloud in the first epoch. In other epochs, use
# reference to the first step. Do not use if each epoch has a different
# order of minibatches. Do not use for RandLaNet or KPConv.
use_reference: false
# Write at most this many samples in each batch
max_outputs: 1

此配置只会在验证阶段(validation)保存 summary 数据。每个点云将被均匀地下采样到最多 10000 个点。每一批数据中,只会保存第一个元素。use_reference 标志只会在第一个 epoch 保存点云,并在其余 epoch 中写入对该数据的引用。这将大幅减小 3D summary 的大小,并提升可视化的响应速度。但如果每个 epoch 以不同的顺序遍历 mini-batch,则不应使用此选项。

现在你可以像之前一样在 TensorBoard 中可视化数据。web 界面允许显示和隐藏不同类别的点、更改它们的颜色,以及探索预测结果和中间网络特征。标量网络特征可以用用户可编辑的自定义 colormap 可视化,3D 特征则可以可视化为 RGB 颜色。下面是一段视频,展示了在 TensorBoard 中可视化语义分割 summary 数据的不同方式。

此视频展示了可视化目标检测数据的不同方式,包括边界框。

你还可以在并排的 widget 中可视化多次运行。为了便于比较不同运行的数据,不同运行的时间步、batch 索引和视角也可以进行同步。

接下来让我们看看如何从你自己的训练代码中保存 3D summary。下面的示例位于 Open3D-ML 仓库的 examples/ 文件夹中。

我们将从磁盘读取示例 3D 点云和标签,并将它们写入 3D summary。我们还会将一些随机的分数和特征写入磁盘。当你想在模型训练期间写入数据时,工作流程是一样的。数据可以作为 PyTorch、TensorFlow、Open3D 张量或 Numpy 数组写入。完整示例见 Open3D-ML 仓库中的 examples/tensorboard_pytorch.py 或 examples/tensorboard_tf.py。

writer = SummaryWriter(join(BASE_LOGDIR, "semseg-example"))
for step in range(len(pcd_files)):
# We will pretend these are the inputs and outputs of a Semantic
# Segmentation model
# float, shape (N, 3), or (B, N, 3) for a batch
points = np.load(pcd_files[step])
# int, shape (N, 1), or (B, N, 1) for a batch
labels = np.load(label_files[step])
# We can also visualize noisy scores (car, road, vegetation)
scores = np.hstack((labels == 1, labels == 9, labels == 15))
scores = np.clip(scores + rng.normal(0., 0.05, size=scores.shape), 0.,
1.)
# and outputs of some pretend network layers. The first 3 dimensions
# can be visualized as RGB colors. Here we will use distances from the
# centroids of (all points, road, vegetation).
centroid_all = np.mean(points, axis=0)
d_all = np.linalg.norm(points - centroid_all, axis=1)
centroid_road = np.mean(points[np.squeeze(labels) == 9, :], axis=0)
d_road = np.linalg.norm(points - centroid_road, axis=1)
centroid_vegetation = np.mean(points[np.squeeze(labels) == 15, :],
axis=0)
d_vegetation = np.linalg.norm(points - centroid_vegetation, axis=1)
features = np.stack((d_all, d_road, d_vegetation), axis=1)
# You can use Torch tensors directly too.
# Prefix the data with "vertex_" for per vertex data.
writer.add_3d(
"semantic_segmentation",
{
"vertex_positions": points, # (N, 3)
"vertex_labels": labels, # (N, 1)
"vertex_scores": scores, # (N, 3)
"vertex_features": features # (N, 3)
},
step,
label_to_names=SEMANTIC_KITTI_LABELS)

与语义分割类似,我们可以保存和可视化输入点云,以及目标检测模型输出的边界框真值和预测。在下一个示例中,我们将从磁盘读取示例 3D 点云和边界框,并将它们写入 3D summary。当你想在模型训练期间写入数据时,工作流程是一样的。数据可以作为 PyTorch、TensorFlow、Open3D 张量或 Numpy 数组写入。

writer = SummaryWriter(join(BASE_LOGDIR, "objdet-example"))
for step in range(len(val_split)): # one pointcloud per step
data = val_split.get_data(step)
# We will pretend these are the inputs and outputs of an Object
# Detection model. You can use Torch tensors directly too.
writer.add_3d(
"input_pointcloud",
{ # float, shape (N, 3), or (B, N, 3) for a batch
"vertex_positions": data['point'][:, :3],
# Extra features: float, shape (N, 1), or (B, N, 1) for a batch
# [should not be (N,)]
"vertex_intensities": data['point'][:, 3:]
},
step)
# We need label_class to be int, not str
for bb in data['bounding_boxes']:
if not isinstance(bb.label_class, int):
bb.label_class = name_to_labels[bb.label_class]
# Bounding boxes (pretend model output): (Nbb, ) or (B, Nbb) for a batch
# Write bounding boxes in a separate call.
writer.add_3d("object_detection", {"bboxes": data['bounding_boxes']},
step,
label_to_names=dset.get_label_to_names())

如果你无法与 3D 模型交互,或无法使用 WebRTC widget 中的控件,请确保为 TensorBoard 网站启用了“Allow Autoplay(允许自动播放)”,然后重新加载页面。