Learning Open3DTutorial上級編→ノンブロッキング可視化

draw_geometries()は、静的ジオメトリの概要をすばやく確認するのに便利な関数である。 ただしこの関数は、視覚化ウィンドウが閉じるまでプロセスを保持する。 これはジオメトリが更新されたときに最適ではないので、ウィンドウを閉じずに可視化する必要がある。 このチュートリアルでは、レンダリングループをカスタマイズする例を紹介する。

Review draw_geometries

draw_geometries関数の復習

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  (それに反応して、再レンダリングのためにフラグをセットする)

バインディング・ジオメトリとレンダリングはどちらもコストのかかる操作であるため、「怠惰」手法により実行されることに注意しよう。それらを個別に制御する2つのフラグがある。関数update_geometry()およびupdate_renderer()はこれらのフラグをオンに設定する。バインド/レンダリングの後、これらのフラグはクリアされる。

このレンダリングループは簡単にカスタマイズできる。 たとえば、ICP位置合わせを視覚化するループは次のようにできる。

In [ ]:
vis = Visualizer()
vis.create_window()
for i in range(icp_iteration):
    # do ICP single iteration
    # transform geometry using ICP
    vis.update_geometry()    # 注: このメソッドは引数を一つ取るはず...
    vis.poll_events()
    vis.update_renderer()

このアイデアを実装したプログラムは以下:

注意 作業(カレント)ディレクトリがexamples/Python/Advancedになっていることを確認すること

In [5]:
# examples/Python/Advanced/non_blocking_visualization.py

import open3d as o3d
import numpy as np
import copy

if __name__ == "__main__":
    o3d.utility.set_verbosity_level(o3d.utility.VerbosityLevel.Debug)
    # (1) Prepare example data
    source_raw = o3d.io.read_point_cloud("../../TestData/ICP/cloud_bin_0.pcd")
    target_raw = o3d.io.read_point_cloud("../../TestData/ICP/cloud_bin_1.pcd")
    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)
    # (2) Initialize Visualizer class
    vis = o3d.visualization.Visualizer()
    vis.create_window()
    vis.add_geometry(source)
    vis.add_geometry(target)
    threshold = 0.05
    icp_iteration = 100
    save_image = False

    # (3) Transform geometry and visualize it
    for i in range(icp_iteration):
        reg_p2l = o3d.registration.registration_icp(
            source, target, threshold, np.identity(4),
            o3d.registration.TransformationEstimationPointToPlane(),
            o3d.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()

注意 最後から6行目はオリジナルではvis.update_geometry()となっておりエラーが起こる。その原因を調べるためのもの:

In [2]:
help(vis.update_geometry)
Help on method update_geometry in module open3d.open3d.visualization:

update_geometry(...) method of open3d.open3d.visualization.Visualizer instance
    update_geometry(self, arg0)
    
    Function to update geometry
    
    Args:
        arg0 (open3d.geometry.Geometry)
    
    Returns:
        bool

以降の節でこのプログラムについて説明する。

Prepare example data

例題データの準備

examples/Python/Advanced/non_blocking_visualization.pyの最初の部分コード:

In [ ]:
    # (1) Prepare example data
    source_raw = o3d.io.read_point_cloud("../../TestData/ICP/cloud_bin_0.pcd")
    target_raw = o3d.io.read_point_cloud("../../TestData/ICP/cloud_bin_1.pcd")
    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)

この部分は、2つの点群(ポイントクラウド)を読み取り、それらをダウンサンプリングしている。 ソースの点群は、不整合になるよう意図的に変換を施している。可視化を良くするために、点群を2つとも反転している。

Initialize Visualizer class

Visualizerクラスの初期化

examples/Python/Advanced/non_blocking_visualization.pyの2番目の部分コード:

In [ ]:
    # (2) Initialize Visualizer class
    vis = o3d.visualization.Visualizer()
    vis.create_window()
    vis.add_geometry(source)
    vis.add_geometry(target)

これらは、ビジュアライザー(Visualizer)クラスのインスタンスを作り、ビジュアライザー・ウィンドウを開き、ビジュアライザーに2つのジオメトリを追加している。

Transform geometry and visualize it

ジオメトリを変換し可視化

examples/Python/Advanced/non_blocking_visualization.pyの3番目の部分コード:

In [ ]:
    for i in range(icp_iteration):
        reg_p2l = o3d.registration.registration_icp(
            source, target, threshold, np.identity(4),
            o3d.registration.TransformationEstimationPointToPlane(),
            o3d.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()

このコードは、繰り返しごとにregistration_icpを呼び出す。 ICPConvergenceCriteria(max_iteration = 1)によって明示的に1回だけのICP反復を行わせていることに注意しよう。これは、1回のICP反復から、わずかなポーズの更新を取得するためのトリックである。ICPの後、ソースのジオメトリはそれに応じて変換される。

コードの次の部分がこのチュートリアルの中心部分である。update_geometryは、ジオメトリが更新されたことをvisに通知する。最後に、ビジュアライザーはpoll_eventsupdate_rendererを呼び出して新しいフレームをレンダリングする。forループの後、destroy_windowはウィンドウを閉じる。

結果は次のようになる。

http://www.open3d.org/docs/release/_images/visualize_icp_iteration.gif