Skip to content

非阻塞可视化

draw_geometries() 是一个很有用的函数,可以快速浏览静态几何体。然而,这个函数会一直阻塞进程,直到可视化窗口被关闭。当几何体被更新、并且需要在不关闭窗口的情况下进行可视化时,这种做法并不理想。本教程介绍了一个自定义渲染循环(rendering loop)的示例。

draw_geometries 具有如下渲染循环(C++ 实现参见 Visualizer::Run()):

while (true):
if (geometry has changed):
re-bind geometry to shaders
if (view parameters have changed):
re-render the scene
if (any user mouse / keyboard input):
respond to it and set flags for re-rendering

注意,绑定几何体和渲染场景都是代价较高的操作,因此它们以惰性(lazy)方式执行。有两个标志位分别控制它们。函数 update_geometry 和 update_renderer 用于设置这些标志位。在重新绑定 / 重新渲染之后,这些标志位会再次被清除。

这个渲染循环可以很方便地进行自定义。例如,可以按下面的方式构造一个自定义循环,用来可视化 ICP 配准过程:

vis = Visualizer()
vis.create_window()
vis.add_geometry(geometry)
for i in range(icp_iteration):
# do ICP single iteration
# transform geometry using ICP
vis.update_geometry(geometry)
vis.poll_events()
vis.update_renderer()

实现这一思路的完整脚本如下。

examples/python/visualization/non_blocking_visualization.py
import open3d as o3d
import numpy as np
def prepare_data():
pcd_data = o3d.data.DemoICPPointClouds()
source_raw = o3d.io.read_point_cloud(pcd_data.paths[0])
target_raw = o3d.io.read_point_cloud(pcd_data.paths[1])
source = source_raw.voxel_down_sample(voxel_size=0.02)
target = target_raw.voxel_down_sample(voxel_size=0.02)
trans = [[0.862, 0.011, -0.507, 0.0], [-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4], [0.0, 0.0, 0.0, 1.0]]
source.transform(trans)
flip_transform = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]
source.transform(flip_transform)
target.transform(flip_transform)
return source, target
def demo_non_blocking_visualization():
o3d.utility.set_verbosity_level(o3d.utility.VerbosityLevel.Debug)
source, target = prepare_data()
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(source)
vis.add_geometry(target)
threshold = 0.05
icp_iteration = 100
save_image = False
for i in range(icp_iteration):
reg_p2l = o3d.pipelines.registration.registration_icp(
source, target, threshold, np.identity(4),
o3d.pipelines.registration.TransformationEstimationPointToPlane(),
o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=1))
source.transform(reg_p2l.transformation)
vis.update_geometry(source)
vis.poll_events()
vis.update_renderer()
if save_image:
vis.capture_screen_image("temp_%04d.jpg" % i)
vis.destroy_window()
o3d.utility.set_verbosity_level(o3d.utility.VerbosityLevel.Info)
if __name__ == '__main__':
demo_non_blocking_visualization()

下面几节对这段脚本进行讲解。

def prepare_data():
pcd_data = o3d.data.DemoICPPointClouds()
source_raw = o3d.io.read_point_cloud(pcd_data.paths[0])
target_raw = o3d.io.read_point_cloud(pcd_data.paths[1])
source = source_raw.voxel_down_sample(voxel_size=0.02)
target = target_raw.voxel_down_sample(voxel_size=0.02)
trans = [[0.862, 0.011, -0.507, 0.0], [-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4], [0.0, 0.0, 0.0, 1.0]]
source.transform(trans)
flip_transform = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]
source.transform(flip_transform)
target.transform(flip_transform)
return source, target

这一部分读取两个点云并对它们进行降采样。源点云被故意做了一个变换以制造错位(misalignment)。两个点云都被翻转了一下,以便获得更好的可视化效果。

def demo_non_blocking_visualization():
o3d.utility.set_verbosity_level(o3d.utility.VerbosityLevel.Debug)
source, target = prepare_data()
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(source)
vis.add_geometry(target)
threshold = 0.05
icp_iteration = 100
save_image = False

这些行创建一个可视化器类的实例,打开可视化窗口,并把两个几何体添加到可视化器中。

for i in range(icp_iteration):
reg_p2l = o3d.pipelines.registration.registration_icp(
source, target, threshold, np.identity(4),
o3d.pipelines.registration.TransformationEstimationPointToPlane(),
o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=1))
source.transform(reg_p2l.transformation)
vis.update_geometry(source)
vis.poll_events()
vis.update_renderer()
if save_image:
vis.capture_screen_image("temp_%04d.jpg" % i)
vis.destroy_window()

visualize_icp_iteration.gif

这段脚本在每次迭代中都调用一次 registration_icp。注意,它通过 ICPConvergenceCriteria(max_iteration = 1) 显式地强制只进行一次 ICP 迭代。这是一种技巧,用来从单次 ICP 迭代中获得一个微小的位姿更新。ICP 之后,源几何体会相应地进行变换。

脚本接下来的部分是本教程的核心。update_geometry 会通知 vis 相关几何体已被更新。最后,可视化器通过调用 poll_events 和 update_renderer 来渲染一个新帧。for 循环的所有迭代结束后,destroy_window 关闭窗口。

结果如下图所示。