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 几何和材质属性的完整描述。
简单几何体序列
Section titled “简单几何体序列”让我们从保存和可视化简单的几何数据(如立方体和圆柱体)开始。模型的颜色在每个步骤都会变化。首先,我们设置所需的导入并创建一些简单的几何数据。
import open3d as o3d# Monkey-patch torch.utils.tensorboard.SummaryWriterfrom open3d.visualization.tensorboard_plugin import summary# Utility function to convert Open3D geometry to a dictionary formatfrom open3d.visualization.tensorboard_plugin.util import to_dict_batchfrom 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)TensorFlow 用户
Section titled “TensorFlow 用户”仓库中同时包含 PyTorch 和 TensorFlow 的代码示例。下面是对应的 TensorFlow 代码。需要注意的主要区别是:add_3d() 是 open3d.visualization.tensorboard_plugin.summary 模块中的一个独立函数,并且需要一个 logdir 参数。data 中也接受 TensorFlow 张量。
import open3d as o3dfrom open3d.visualization.tensorboard_plugin import summary# Utility function to convert Open3D geometry to a dictionary formatfrom open3d.visualization.tensorboard_plugin.util import to_dict_batchimport 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 提示符中来运行此示例,或者运行示例:
python examples/python/gui/tensorboard_pytorch.py small_scale现在启动 tensorboard 来可视化数据:
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)几何属性张量被替换为一个整数(步骤引用),指向一个较早的步骤作为该几何属性的来源。你可以用以下命令运行此示例:
python examples/python/gui/tensorboard_pytorch.py property_referencesummary 文件夹的大小会变小,但你在 tensorboard 中看到的几何显示是一样的。
使用 PBR 材质的丰富 3D 模型
Section titled “使用 PBR 材质的丰富 3D 模型”除了几何属性之外,我们还可以使用 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 dictsummary_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 材质着色器。
3DML 模型的训练与推理
Section titled “3DML 模型的训练与推理”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/ 文件夹中。
自定义属性与语义分割
Section titled “自定义属性与语义分割”我们将从磁盘读取示例 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 目标检测
Section titled “3D 目标检测”与语义分割类似,我们可以保存和可视化输入点云,以及目标检测模型输出的边界框真值和预测。在下一个示例中,我们将从磁盘读取示例 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(允许自动播放)”,然后重新加载页面。