Learning Open3D

Install

(1) ソースのダウンロード

git clone https://github.com/IntelVCL/Open3D

(2) インストール

cd Open3D
./util/scripts/install-deps-ubuntu.sh
mkdir build
cd build
cmake ../src
make -j
sudo make install

Pythonから使うために Anaconda-python3.6を使用していると仮定して

cp lib/py3d.cpython-36m-x86_64-linux-gnu.so  /opt/anaconda3/lib/python3.6/

個人的な設定に関する注意:このnotebookは、これがあるディレクトリに Open3D/build/lib/TestData へのリンクがあるものとしている。つまり、実際には ln -s ~/Open3D/build/lib/Test* ./という設定をする。

  • ゼミ生のためのデータ提供: TestData.zipをダウンロードし、このファイルがあるディレクトリの下でunzip -x TestData.zipで展開する
  • 元サイトは `Open3D/build/lib/Tutorial/Basic'などを作業用ディレクトリとして、そこにあるPythonプログラムを実行するという形式をとっている。しかしここではnotebookからコードを実行するため、このような変更を行っている)

Getting Started

py3d モジュールのimportができることを確認する

In [1]:
import py3d

Python tutorialの紹介

注意: Open3DのPython tutorial では、 numpy, matplotlib, opencvなどのモジュールを用いている。 ただしOpenCV は再構築システムとしてだけ使っている。用いているモジュールなど詳しくは scripts/install-deps-python.sh を読むこと。

  • テスト: 2つのRGB-Dデータを読み、それを可視化するプログラム
    cd lib/Tutorial/Basic
    python rgbd_redwood.py
    RGB-D画像をポイントクラウドに変換し、Open3D可視化プログラムでレンダリングする。
In [2]:
%matplotlib inline

# rgbd_redwood.py
#conda install pillow matplotlib
from py3d import *
import matplotlib.pyplot as plt

def run():
	print("Read Redwood dataset")
	color_raw = read_image("./TestData/RGBD/color/00000.jpg")
	depth_raw = read_image("./TestData/RGBD/depth/00000.png")
	rgbd_image = create_rgbd_image_from_color_and_depth(
		color_raw, depth_raw);
	print(rgbd_image)
	plt.figure(figsize=(12,5))    
	plt.subplot(1, 3, 1)
	plt.title('Redwood original image')
	plt.imshow(color_raw)
	plt.subplot(1, 3, 2)
	plt.title('Redwood grayscale image')
	plt.imshow(rgbd_image.color,cmap='gray')
	plt.subplot(1, 3, 3)
	plt.title('Redwood depth image')
	plt.imshow(rgbd_image.depth)
	plt.show()
	try:    # py3d old version?
		pcd = create_point_cloud_from_rgbd_image(rgbd_image,
			PinholeCameraIntrinsic.prime_sense_default)
	except:    # new version?
		pcd = create_point_cloud_from_rgbd_image(rgbd_image,
			PinholeCameraIntrinsic.get_prime_sense_default())        
	# Flip it, otherwise the pointcloud will be upside down
	pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
	draw_geometries([pcd])

if __name__ == "__main__":
    run()
Read Redwood dataset
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.

表示される画像: http://www.open3d.org/docs/_images/redwood_pcd.png

このPythonのコードは極めて簡単であり、詳しい説明は Redwood データセットにあるので参照のこと。Tutorialページにはこれ以外にいろいろなものが試せるので参照のこと。

In [7]:
color_raw = read_image("./TestData/RGBD/color/00000.jpg")
depth_raw = read_image("./TestData/RGBD/depth/00000.png")
rgbd_image = create_rgbd_image_from_color_and_depth(
		color_raw, depth_raw)
In [12]:
color_raw
Out[12]:
Image of size 640x480, with 3 channels.
Use numpy.asarray to access buffer data.
In [5]:
depth_raw
Out[5]:
Image of size 640x480, with 1 channels.
Use numpy.asarray to access buffer data.
In [6]:
rgbd_image
Out[6]:
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.
In [13]:
import numpy as np
print(np.asarray(color_raw)[200:201], '\n',np.asarray(color_raw)[200,300])
[[[219 165 163]
  [220 169 166]
  [227 176 173]
  ...
  [149 161 173]
  [150 163 172]
  [147 160 169]]] 
 [255 255 255]
In [18]:
print(np.asarray(depth_raw)[200:201],'\n',np.asarray(depth_raw)[200][300])
[[   0    0    0    0    0    0    0    0 2178 2178 2195 2178 2195 2195
  2195 2212 2212 2212 2230 2230 2230 2230 2248 2230 2266 2248 2266 2266
  2266 2266 2284 2303 2303 2322 2322 2322 2322 2342 2342 2342 2342 2342
  2361 2361 2381 2361 2381 2381 2402 2402 2422 2422 2422 2422 2443 2443
  2443 2443 2443 2443 2465 2487 2465 2487 2509 2509 2509 2509 2509 2531
  2554 2531 2554 2554 2578 2578 1398 1391 1391 1391 1391 1391 1398 1391
  1391 1391 1384 1391 1384 1391 1384 1384 1377 1384 1377 1377 1377 1377
  1377 1377 1377 1370 1377 1377 1370 1370 1364 1370 1377 1377 1384 1384
  1398 1398 1405 1398 1412 1405 2602 2626 2602 2602 2602 2602 2602 2602
  2578 2602 2602 2602 2578 2578 2602 2602 2602 2578 2578 2602 2578 2578
  2578 2578 2578 2578 2578 2578 2578 2578 2554 2578 2554 2578 2578 2578
  2554 2578 2554 2554 2578 2554 2578 2578 2554 2554 2554 2554 2554 2554
  2554 2554 2554 2554 2554 2554 1662 1653 1662 1662 1662 1662 1662 1662
  1672 1662 1662 1662 1662 1672 1672 1672 1672 1672 1672 1682 1672 1672
  1682 1682 1682 1682 1682 1682 1693 1682 1693 1682 1693 1693 1790 1790
  1790 1801 1801 1790 1801 1801 1801 1801 1801 1801 1813 1801 1813 1801
  1813 1813 1813 1813 1824 1813 1824 1824 1924 1938 1938 1938 1938 1938
  1938 1938 1938 1938 1938 1938 1938 1938 2051 2051 2051 2051 2051 2051
  2051 2051 2066 2051 2066 2066 2066 2066 2178 2178 2178 2178 2178 2178
  2195 2178 2178 2178 2195 2195 2212 2195 2266 2284 2266 2266 2266 2266
  2266 2266 2266 2266 2266 2266 2248 2266 2266 2248 2266 2248 2248 2248
  2248 2248 2248 2248 2248 2230 2248 2230 2230 2230 2230 2230 2230 2230
  2230 2230 2212 2230 2212 2195 2212 2212 2212 2212 2212 2212 2195 2195
  2178 2195 2178 2195 2195 2195 2195 2178 2178 2178 2178 2178 2178 2195
  2161 2178 2178 2178 2161 2161 1790 1790 1767 1767 1767 1767 1767 1756
  1745 1756 1756 1756 1767 1767 1778 1778 1778 1778 1778 1778 1778 1778
  1778 1767 1778 1778 1778 1767 1433 1433 1405 1405 1391 1398 1384 1384
  1377 1377 1370 1377 1370 1377 1370 1377 1370 1370 1384 1384 1778 1790
  1790 1790 1790 1790 1801 1801 1790 1801 1801 1801 1801 1801 1801 1801
  1801 1813 1813 1813 1813 1813 1801 1813 1813 1813 1813 1813 1813 1813
  1813 1813 1813 1813 1813 1824 1824 1824 1824 1824 1824 1824 1824 1813
  1824 1824 1836 1824 1824 1824 1824 1824 1824 1824 1824 1824 1824 1824
  1824 1824 1836 1824 1836 1836 1836 1824 1836 1836 1836 1836 1836 1836
  1836 1836 1836 1836 1836 1836 1836 1836 1836 1836 1836 1836 1836 1836
  1836 1836 1848 1836 1848 1848 1836 1848 1848 1848 1848 1848 1861 1861
  2007 2007 1861 1861 1861 1861 1848 1848 1848 1824 1836 1836 1836 1836
  1836 1836 1993 1993 1993 1979 1979 1993 1979 1979 1979 1979 1979 1979
  1979 1965 1965 1965 1965 1965 1965 1965 1965 1965 1965 1951 1965 1951
  1951 1951 1951 1951 1951 1965 1951 1951 1951 1951 1951 1552 1552 1552
  1535 1527 1527 1527 1510 1510 1502 1502 1502 1502 1494 1494 1486 1486
  1479 1479 1471 1471 1471 1471 1471 1471 1479 1479 1911 1911 1911 1911
  1911 1911 1911 1911 1911 1898 1911 1898 1911 1898 1898 1898 1898 1898
  1898 1898 1898 1898 1898 1898 1898 1898 1886 1898 1898 1898 1886 1898
  1898 1898 1886 1886 1886 1886    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0]] 
 2248

Tutorial

Open3D には C++ と Pythonの2つのインターフェスがある. そしてPythonは使いやすいため、Open3D の主要なインターフェスとして位置づけられている。


Python binding

まずhelp情報を表示するための関数2つを紹介。

In [19]:
def example_help_function():
    import py3d as py3d
    help(py3d)
    help(py3d.PointCloud)
    help(py3d.read_point_cloud)

def example_import_function(path):
    from py3d import read_point_cloud
    pcd = read_point_cloud(path)
    print(path," = ",pcd)
In [20]:
example_import_function("./TestData/ICP/cloud_bin_0.pcd")
./TestData/ICP/cloud_bin_0.pcd  =  PointCloud with 198835 points.

example_import_function関数の中で使われているread_point_cloud 関数は、PCLファイルを読み込み、PointCloudクラスのインスタンスを返す。

In [12]:
import py3d
help(py3d.read_point_cloud)
Help on built-in function read_point_cloud in module py3d:

read_point_cloud(...) method of builtins.PyCapsule instance
    read_point_cloud(filename: str) -> py3d.PointCloud
    
    Function to read PointCloud from file

PointCloudクラスの説明は以下:

In [13]:
help(py3d.PointCloud)
Help on class PointCloud in module py3d:

class PointCloud(Geometry3D)
 |  Method resolution order:
 |      PointCloud
 |      Geometry3D
 |      Geometry
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __add__(...)
 |      __add__(self: py3d.PointCloud, arg0: py3d.PointCloud) -> py3d.PointCloud
 |  
 |  __copy__(...)
 |      __copy__(self: py3d.PointCloud) -> py3d.PointCloud
 |  
 |  __deepcopy__(...)
 |      __deepcopy__(self: py3d.PointCloud, arg0: dict) -> py3d.PointCloud
 |  
 |  __iadd__(...)
 |      __iadd__(self: py3d.PointCloud, arg0: py3d.PointCloud) -> py3d.PointCloud
 |  
 |  __init__(...)
 |      __init__(*args, **kwargs)
 |      Overloaded function.
 |      
 |      1. __init__(self: py3d.PointCloud) -> None
 |      
 |      Default constructor
 |      
 |      2. __init__(self: py3d.PointCloud, arg0: py3d.PointCloud) -> None
 |      
 |      Copy constructor
 |  
 |  __repr__(...)
 |      __repr__(self: py3d.PointCloud) -> str
 |  
 |  has_colors(...)
 |      has_colors(self: py3d.PointCloud) -> bool
 |  
 |  has_normals(...)
 |      has_normals(self: py3d.PointCloud) -> bool
 |  
 |  has_points(...)
 |      has_points(self: py3d.PointCloud) -> bool
 |  
 |  normalize_normals(...)
 |      normalize_normals(self: py3d.PointCloud) -> None
 |  
 |  paint_uniform_color(...)
 |      paint_uniform_color(self: py3d.PointCloud, arg0: numpy.ndarray[float64[3, 1]]) -> None
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  colors
 |  
 |  normals
 |  
 |  points
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from Geometry3D:
 |  
 |  get_max_bound(...)
 |      get_max_bound(self: py3d.Geometry3D) -> numpy.ndarray[float64[3, 1]]
 |  
 |  get_min_bound(...)
 |      get_min_bound(self: py3d.Geometry3D) -> numpy.ndarray[float64[3, 1]]
 |  
 |  transform(...)
 |      transform(self: py3d.Geometry3D, arg0: numpy.ndarray[float64[4, 4]]) -> None
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from Geometry:
 |  
 |  clear(...)
 |      clear(self: py3d.Geometry) -> None
 |  
 |  dimension(...)
 |      dimension(self: py3d.Geometry) -> int
 |  
 |  get_geometry_type(...)
 |      get_geometry_type(self: py3d.Geometry) -> three::Geometry::GeometryType
 |  
 |  is_empty(...)
 |      is_empty(self: py3d.Geometry) -> bool
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from Geometry:
 |  
 |  Image = Type.Image
 |  
 |  LineSet = Type.LineSet
 |  
 |  PointCloud = Type.PointCloud
 |  
 |  TriangleMesh = Type.TriangleMesh
 |  
 |  Type = <class 'py3d.Geometry.Type'>
 |  
 |  
 |  Unspecified = Type.Unspecified
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from pybind11_builtins.pybind11_object:
 |  
 |  __new__(*args, **kwargs) from pybind11_builtins.pybind11_type
 |      Create and return a new object.  See help(type) for accurate signature.

FIle I/O

Open3D による初等幾何ファイルの読み込み、書き込みの方法

In [15]:
# io.py
%matplotlib inline
from py3d import *

if __name__ == "__main__":

    print("Testing IO for point cloud ...")
    pcd = read_point_cloud("./TestData/fragment.pcd")
    print(pcd)
    write_point_cloud("copy_of_fragment.pcd", pcd)

    print("Testing IO for meshes ...")
    mesh = read_triangle_mesh("./TestData/knot.ply")
    print(mesh)
    write_triangle_mesh("copy_of_knot.ply", mesh)

    print("Testing IO for images ...")
    img = read_image("./TestData/lena_color.jpg")
    print(img)
    write_image("copy_of_lena_color.jpg", img)
Testing IO for point cloud ...
PointCloud with 113662 points.
Testing IO for meshes ...
TriangleMesh with 1440 points and 2880 triangles.
Testing IO for images ...
Image of size 512x512, with 3 channels.
Use numpy.asarray to access buffer data.

copy_of_lena_color.jpg

Point cloud

ポイントクラウド(点群)ファイルの読み込み(read_point_cloud)、書き込み(write_point_cloud)の関数の紹介:

In [ ]:
print("Testing IO for point cloud ...")
pcd = read_point_cloud("./TestData/fragment.pcd")
print(pcd)
write_point_cloud("copy_of_fragment.pcd", pcd)

print() 関数はpcdの内容要約の表示に使える。上記の結果表示されるメッセージは:

Testing IO for point cloud ...
PointCloud with 113662 points.

Mesh

メッシュの読み込み(read_triangle_mesh)と書き込み(write_triangle_mesh)の関数の紹介:

In [ ]:
print("Testing IO for meshes ...")
mesh = read_triangle_mesh("./TestData/knot.ply")
print(mesh)
write_triangle_mesh("copy_of_knot.ply", mesh)

ポイントクラウド(点群)とは異なり、メッシュは表面を定義する三角形の集まり、というデータ構造を持つ。

Image

画像の読み込み(read_image)と書き込み(write_image)の関数の紹介:

In [ ]:
print("Testing IO for images ...")
img = read_image("./TestData/lena_color.jpg")
print(img)
write_image("copy_of_lena_color.jpg", img)

print関数により、画像のサイズが表示される:

Testing IO for images ...
Image of size 512x512, with 3 channels.
Use numpy.asarray to access buffer data.

Point Cloud

ポイントクラウド(点群)の使い方

参考: ポイントクラウドの可視化プログラムとしてPCL提供のもの以外に、http://www.danielgm.net/cc/ がある。Ubuntuではsnap install cloudcompare によってインストールが可能。`cloudcompare.CloudCompare'で起動 (紹介記事: http://www.pointcloud.jp/blog_n23/)

In [18]:
# src/Python/Tutorial/Basic/pointcloud.py

import numpy as np
from py3d import *

if __name__ == "__main__":

    print("Load a ply point cloud, print it, and render it")
    pcd = read_point_cloud("./TestData/fragment.ply")
    print(pcd)
    print(np.asarray(pcd.points))
    draw_geometries([pcd])

    print("Downsample the point cloud with a voxel of 0.05")
    downpcd = voxel_down_sample(pcd, voxel_size = 0.05)
    draw_geometries([downpcd])

    print("Recompute the normal of the downsampled point cloud")
    estimate_normals(downpcd, search_param = KDTreeSearchParamHybrid(
            radius = 0.1, max_nn = 30))
    draw_geometries([downpcd])
    print("")

    print("Load a polygon volume and use it to crop the original point cloud")
    vol = read_selection_polygon_volume("./TestData/Crop/cropped.json")
    chair = vol.crop_point_cloud(pcd)
    draw_geometries([chair])
    print("")

    print("Paint chair")
    chair.paint_uniform_color([1, 0.706, 0])
    draw_geometries([chair])
    print("")
Load a ply point cloud, print it, and render it
PointCloud with 196133 points.
[[0.65234375 0.84686458 2.37890625]
 [0.65234375 0.83984375 2.38430572]
 [0.66737998 0.83984375 2.37890625]
 ...
 [2.00839925 2.39453125 1.88671875]
 [2.00390625 2.39488506 1.88671875]
 [2.00390625 2.39453125 1.88793314]]
Downsample the point cloud with a voxel of 0.05
Recompute the normal of the downsampled point cloud

Load a polygon volume and use it to crop the original point cloud

Paint chair

Visualize point cloud

はじめに、ポイントクラウド(点群)ファイルを読み込み、それを可視化する方法を紹介

In [22]:
print("Load a ply point cloud, print it, and render it")
pcd = read_point_cloud("./TestData/fragment.ply")
print(pcd)
print(np.asarray(pcd.points))
draw_geometries([pcd])
Load a ply point cloud, print it, and render it
PointCloud with 196133 points.
[[0.65234375 0.84686458 2.37890625]
 [0.65234375 0.83984375 2.38430572]
 [0.66737998 0.83984375 2.37890625]
 ...
 [2.00839925 2.39453125 1.88671875]
 [2.00390625 2.39488506 1.88671875]
 [2.00390625 2.39453125 1.88793314]]

read_point_cloud 関数はファイルからポイントクラウドを読み込み、その拡張子に基づいてデコードを試みる。扱える拡張子は: pcd, ply, xyz, xyzrgb, xyzn, pts.

draw_geometries は ポイントクラウドを可視化する。マウスを用いて、いろいろな視点からその幾何形状をみることができる。 http://www.open3d.org/docs/_images/scene.png

みかけは密度のある曲面のようにみえるが、実際にはsurfles(曲面要素)としてレンダリングされたものである。GUIによりいろいろなキーボード関数がサポートされている。そのひとつは、- キーで点群数を小さくする。これを何回か押してみると次のように見えるだろう; http://www.open3d.org/docs/_images/scene_small.png

注意: hキーを押せば以下に示すリストがえられる。詳しい情報は、可視化可視化のカスタム化の項を参照。

-- Mouse view control --
    Left button + drag        : Rotate. (回転)
    Ctrl + left button + drag : Translate. (平行移動)
    Wheel                     : Zoom in/out. (ズームイン・アウト)

  -- Keyboard view control --
    [/]          : Increase/decrease field of view.
    R            : Reset view point.
    Ctrl/Cmd + C : Copy current view status into the clipboard.
    Ctrl/Cmd + V : Paste view status from clipboard.

  -- General control --
    Q, Esc       : Exit window. (終了)
    H            : Print help message.  (ヘルプメッセージの表示)
    P, PrtScn    : Take a screen capture.  (スクリーンキャプチャ)
    D            : Take a depth capture.   (深さキャプチャ)
    O            : Take a capture of current rendering settings. (現在のレンダリング設定のキャプチャ)

  -- Render mode control --
    L            : Turn on/off lighting.
    +/-          : Increase/decrease point size.
    N            : Turn on/off point cloud normal rendering.
    S            : Toggle between mesh flat shading and smooth shading.
    W            : Turn on/off mesh wireframe.
    B            : Turn on/off back face rendering.
    I            : Turn on/off image zoom in interpolation.
    T            : Toggle among image render:
                   no stretch / keep ratio / freely stretch.

  -- Color control --
    0..4,9       : Set point cloud color option.
                   0 - Default behavior, render point color.
                   1 - Render point color.
                   2 - x coordinate as color.
                   3 - y coordinate as color.
                   4 - z coordinate as color.
                   9 - normal as color.
    Ctrl + 0..4,9: Set mesh color option.
                   0 - Default behavior, render uniform gray color.
                   1 - Render point color.
                   2 - x coordinate as color.
                   3 - y coordinate as color.
                   4 - z coordinate as color.
                   9 - normal as color.
    Shift + 0..4 : Color map options.
                   0 - Gray scale color.
                   1 - JET color map.
                   2 - SUMMER color map.
                   3 - WINTER color map.
                   4 - HOT color map.

Voxel downsampling

ボクセル(voxel)の説明: https://ja.wikipedia.org/wiki/%E3%83%9C%E3%82%AF%E3%82%BB%E3%83%AB

ボクセルのダウンサンプリングは、通常のボクセル・グリッドを使用して、入力点群から一様にダウンサンプリングされたポイントクラウド(点群)を作成する。 これは、多くのポイント・クラウド処理タスクの前処理ステップとしてよく使用される。 アルゴリズムは2つのステップで動作する。

  1. ポイントをボクセルに入れる。
  2. 占有された各ボクセルは、内部のすべての点を平均化することによって正確な1点を生成する。
In [23]:
print("Downsample the point cloud with a voxel of 0.05")
downpcd = voxel_down_sample(pcd, voxel_size = 0.05)
draw_geometries([downpcd])
Downsample the point cloud with a voxel of 0.05

ダウンサンプリングした結果は以下のようなもの: http://www.open3d.org/docs/_images/downsampled.png

Vertex normal estimation

ポイントクラウドに対するもうひとつの基本操作は、点法線推定(point normal estimation)である.

In [25]:
print("Recompute the normal of the downsampled point cloud")
estimate_normals(downpcd, search_param = KDTreeSearchParamHybrid(
        radius = 0.1, max_nn = 30))
draw_geometries([downpcd])
print("")
Recompute the normal of the downsampled point cloud

estimate_normalsはすべての点の法線を計算する。 この関数は共分散分析を用いて隣接点を見つけ、隣接点の主軸を計算する。

この関数は、KDTreeSearchParamHybridクラスのインスタンスを引数として取る。 radius = 0.1max_nn = 30という2つの重要な引数は、検索半径と最大最近傍を指定するもの。 これにより10cmの探索半径を持ち、計算時間を節約するため最大30個の近傍しか考慮しない。

共分散分析アルゴリズムは、法線の候補として2つの互いに反対方向のベクトルを生成する。 ジオメトリのグローバルな構造を知らなければ、どちらも正しい可能性がある。 これは、法線方向問題として知られている問題である。 Open3Dは、法線が存在する場合、元の法線と揃うように法線の方向付けを試みる。 それ以外の場合、Open3Dはランダムな推測を行う。 方向が重要な場合は、orient_normals_to_align_with_directionorient_normals_towards_camera_locationなどの方向付け関数を呼び出す必要がある。

draw_geometriesを使用して点群を可視化し、nを押して点の法線を表示する。 -キーと+キーを使用して法線の長さを制御できる。 http://www.open3d.org/docs/_images/downsampled_normal.png

Crop point cloud

read_selection_polygon_volume はポリゴン選択領域を指定するJSONファイルを読み込む。 vol.crop_point_cloud(pcd)は点群をフィルターする。 以下を実行すると椅子だけが選択されて残る。

In [26]:
print("We load a polygon volume and use it to crop the original point cloud")
vol = read_selection_polygon_volume("./TestData/Crop/cropped.json")
chair = vol.crop_point_cloud(pcd)
draw_geometries([chair])
print("")
We load a polygon volume and use it to crop the original point cloud

./TestData/Crop/cropped.json ファイルの内容:

{
    "axis_max" : 4.022921085357666,
    "axis_min" : -0.76341366767883301,
    "bounding_polygon" : 
    [
        [ 2.6509309513852526, 0.0, 1.6834473132326844 ],
        [ 2.5786428246917148, 0.0, 1.6892074266735244 ],
        [ 2.4625790337552154, 0.0, 1.6665777078297999 ],
        [ 2.2228544982251655, 0.0, 1.6168160446813649 ],
        [ 2.166993206001413, 0.0, 1.6115495157201662 ],
        [ 2.1167895865303286, 0.0, 1.6257706054969348 ],
        [ 2.0634657721747383, 0.0, 1.623021658624539 ],
        [ 2.0568612343437236, 0.0, 1.5853892911207643 ],
        [ 2.1605399001237027, 0.0, 0.96228993255083017 ],
        [ 2.1956669387205228, 0.0, 0.95572746049785073 ],
        [ 2.2191318790575583, 0.0, 0.88734449982108754 ],
        [ 2.2484881847925919, 0.0, 0.87042807267013633 ],
        [ 2.6891234157295827, 0.0, 0.94140677988967603 ],
        [ 2.7328692490470647, 0.0, 0.98775740674840251 ],
        [ 2.7129337547575547, 0.0, 1.0398850034649203 ],
        [ 2.7592174072415405, 0.0, 1.0692940558509485 ],
        [ 2.7689216419453428, 0.0, 1.0953914441371593 ],
        [ 2.6851455625455669, 0.0, 1.6307334122162018 ],
        [ 2.6714776099981239, 0.0, 1.675524657088997 ],
        [ 2.6579576128816544, 0.0, 1.6819127849749496 ]
    ],
    "class_name" : "SelectionPolygonVolume",
    "orthogonal_axis" : "Y",
    "version_major" : 1,
    "version_minor" : 0
}

実行結果の画像: http://www.open3d.org/docs/_images/crop.png

Paint point cloud

paint_uniform_color は一様な色で全ての点に色付けする。色はRGB空間 [0, 1] の範囲で指定する。 下記のプログラムの実行結果: http://www.open3d.org/docs/_images/crop_color.png

In [27]:
print("Paint chair")
chair.paint_uniform_color([1, 0.706, 0])
draw_geometries([chair])
print("")
Paint chair

Mesh

Open3D には三角メッシュのためのデータ構造がある。

In [110]:
# src/Python/Tutorial/Basic/mesh.py

import numpy as np
import copy
from py3d import *

if __name__ == "__main__":
   print("Testing mesh in py3d ...")
   mesh = read_triangle_mesh("./TestData/knot.ply")
   print(mesh)
   print(np.asarray(mesh.vertices))
   print(np.asarray(mesh.triangles))
   print("")
   print("Try to render a mesh with normals (exist: " +
        str(mesh.has_vertex_normals()) +
        ") and colors (exist: " + str(mesh.has_vertex_colors()) + ")")
   draw_geometries([mesh])
   print("A mesh with no normals and no colors does not seem good.")
   print("Computing normal and rendering it.")
   mesh.compute_vertex_normals()
   print(np.asarray(mesh.triangle_normals))
   draw_geometries([mesh])
   print("We make a partial mesh of only the first half triangles.")
   mesh1 = copy.deepcopy(mesh)
   mesh1.triangles = Vector3iVector(
        np.asarray(mesh1.triangles)[:len(mesh1.triangles)//2, :])
   mesh1.triangle_normals = Vector3dVector(
        np.asarray(mesh1.triangle_normals)
        [:len(mesh1.triangle_normals)//2, :])
   print(mesh1.triangles)
   draw_geometries([mesh1])
   print("Painting the mesh")
   mesh1.paint_uniform_color([1, 0.706, 0])
   draw_geometries([mesh1])
Testing mesh in py3d ...
TriangleMesh with 1440 points and 2880 triangles.
[[  4.51268387  28.68865967 -76.55680847]
 [  7.63622284  35.52046967 -69.78063965]
 [  6.21986008  44.22465134 -64.82303619]
 ...
 [-22.12651634  31.28466606 -87.37570953]
 [-13.91188431  25.4865818  -86.25827026]
 [ -5.27768707  23.36245346 -81.43279266]]
[[   0   12   13]
 [   0   13    1]
 [   1   13   14]
 ...
 [1438   11 1439]
 [1439   11    0]
 [1439    0 1428]]

Try to render a mesh with normals (exist: False) and colors (exist: False)
A mesh with no normals and no colors does not seem good.
Computing normal and rendering it.
[[ 0.79164373 -0.53951444  0.28674793]
 [ 0.8319824  -0.53303008  0.15389681]
 [ 0.83488162 -0.09250101  0.54260136]
 ...
 [ 0.16269924 -0.76215917 -0.6266118 ]
 [ 0.52755226 -0.83707495 -0.14489352]
 [ 0.56778973 -0.76467734 -0.30476777]]
We make a partial mesh of only the first half triangles.
std::vector<Eigen::Vector3i> with 1440 elements.
Use numpy.asarray() to access data.
Painting the mesh

(ノードと三角の表示)

In [ ]:
print("Testing mesh in py3d ...")
mesh = read_triangle_mesh("./TestData/knot.ply")
print(mesh)
print(np.asarray(mesh.vertices))
print(np.asarray(mesh.triangles))
print("")

この出力は

TriangleMesh with 1440 points and 2880 triangles.
[[  4.51268387  28.68865967 -76.55680847]
 [  7.63622284  35.52046967 -69.78063965]
 [  6.21986008  44.22465134 -64.82303619]
 ...,
 [-22.12651634  31.28466606 -87.37570953]
 [-13.91188431  25.4865818  -86.25827026]
 [ -5.27768707  23.36245346 -81.43279266]]
[[   0   12   13]
 [   0   13    1]
 [   1   13   14]
 ...,
 [1438   11 1439]
 [1439   11    0]
 [1439    0 1428]]

TriangleMeshクラスには、頂点や三角形などのいくつかのデータフィールドがある。 Open3Dは、numpy配列を介してこれらのフィールドへの直接的なメモリ・アクセスを提供する。

Visualize 3D mesh

3D メッシュの可視化

In [ ]:
print("Try to render a mesh with normals (exist: " +
        str(mesh.has_vertex_normals()) +
        ") and colors (exist: " + str(mesh.has_vertex_colors()) + ")")
draw_geometries([mesh])
print("A mesh with no normals and no colors does not seem good.")
Try to render a mesh with normals (exist: False) and colors (exist: False)
A mesh with no normals and no colors does not seem good.

これによりメッシュを可視化する。 http://www.open3d.org/docs/_images/without_shading.png

メッシュを回転したり移動したりすることはできるが、灰色で均一に塗られており、「3d」のように見えない。その理由は、この時点のメッシュに頂点や面の法線がないためである。 したがって、より洗練されたPhongシェーディングの代わりに、均一なカラーシェーディングが使用される。

Surface normal estimation (表面法線推定)

表面法線でメッシュを書くことにしよう。

In [ ]:
print("Computing normal, painting the mesh, and rendering it.")
mesh.compute_vertex_normals()
print(np.asarray(mesh.triangle_normals))
draw_geometries([mesh])
Computing normal and rendering it.
[[ 0.79164373 -0.53951444  0.28674793]
 [ 0.8319824  -0.53303008  0.15389681]
 [ 0.83488162 -0.09250101  0.54260136]
 ...
 [ 0.16269924 -0.76215917 -0.6266118 ]
 [ 0.52755226 -0.83707495 -0.14489352]
 [ 0.56778973 -0.76467734 -0.30476777]]

これはmeshモジュールの関数のcompute_vertex_normalspaint_uniform_color を用いて次のような表示を行う: http://www.open3d.org/docs/_images/with_shading.png

Crop mesh

この三角メッシュとtriangle_normalsデータに直接操作し、半分くらいを削除しよう。それにはnumpy配列を介して行う。

In [ ]:
print("We make a partial mesh of only the first half triangles.")
mesh1 = copy.deepcopy(mesh)
mesh1.triangles = Vector3iVector(
        np.asarray(mesh1.triangles)[:len(mesh1.triangles)//2, :])
mesh1.triangle_normals = Vector3dVector(
        np.asarray(mesh1.triangle_normals)
        [:len(mesh1.triangle_normals)//2, :])
print(mesh1.triangles)
draw_geometries([mesh1])

その結果は:

We make a partial mesh of only the first half triangles.
std::vector<Eigen::Vector3i> with 1440 elements.
Use numpy.asarray() to access data.

http://www.open3d.org/docs/_images/half.png

In [111]:
import numpy as np
np.asarray(mesh)
Out[111]:
array(TriangleMesh with 1440 points and 2880 triangles., dtype=object)

Paint mesh

メッシュの色付けは、ポイントクラウドと同様に行える。

In [ ]:
print("Painting the mesh")
mesh1.paint_uniform_color([1, 0.706, 0])
draw_geometries([mesh1])

その結果: http://www.open3d.org/docs/_images/half_color.png

RGBD Images

Open3Dには画像用のデータ構造がある。 read_image、write_image、filter_image、draw_geometriesなどのさまざまな関数を用意している。 Open3Dイメージは、numpy配列に直接変換することができる。

Open3Dの RGBDImageは、RGBDImage.depthとRGBDImage.colorの2つの画像で構成されている。 ただし2つの画像を同じカメラフレームで位置合わせし、同じ解像度にする必要がある。 以下のチュートリアルでは、よく知られているいくつかのRGBDデータセットからRGBDイメージを読み込んで使用する方法を示す。

Redwood dataset

ここでは Redwoodデータセット Choi et al.(2015)からGBDImageを読み込み、可視化する。

  • Choi, S., Zhou, Q.-Y., & V. Koltun. (2015). Robust Reconstruction of Indoor Scenes, CVPR.
In [112]:
# src/Python/Tutorial/Basic/rgbd_redwood.py
%matplotlib inline
#conda install pillow matplotlib
from py3d import *
import matplotlib.pyplot as plt

if __name__ == "__main__":
        print("Read Redwood dataset")
        color_raw = read_image("./TestData/RGBD/color/00000.jpg")
        depth_raw = read_image("./TestData/RGBD/depth/00000.png")
        rgbd_image = create_rgbd_image_from_color_and_depth(
                color_raw, depth_raw);
        print(rgbd_image)
        plt.figure(figsize=(12,5))    
        plt.subplot(1, 3, 1)
        plt.title('Redwood original image')
        plt.imshow(color_raw)
        plt.subplot(1, 3,  2)
        plt.title('Redwood grayscale image')
        plt.imshow(rgbd_image.color, cmap='gray')
        plt.subplot(1, 3, 3)
        plt.title('Redwood depth image')
        plt.imshow(rgbd_image.depth)
        plt.show()
        pcd = create_point_cloud_from_rgbd_image(rgbd_image,
                        PinholeCameraIntrinsic.prime_sense_default)
        # Flip it, otherwise the pointcloud will be upside down
        pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
        draw_geometries([pcd])
Read Redwood dataset
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.

(Getting Startedでみたデータと同じ)

Redwoodフォーマットは、16ビットのシングルチャンネル画像に深度を格納している。 整数値はミリメートル単位の深度測定値を表す。 Open3Dが奥行き画像を解析するためのデフォルトのフォーマットである。

In [ ]:
print("Read Redwood dataset")
color_raw = read_image("./TestData/RGBD/color/00000.jpg")
depth_raw = read_image("./TestData/RGBD/depth/00000.png")
rgbd_image = create_rgbd_image_from_color_and_depth(
        color_raw, depth_raw);
print(rgbd_image)

デフォルトの変換関数create_rgbd_image_from_color_and_depthは、色と奥行き画像のペアからRGBDImageを作成する。 カラー画像はグレースケール画像に変換され、[0、1]の範囲のfloatに格納される。 奥行き画像は、深度値をメートルで表した浮動小数点数で格納さる。 print(rgbd_image)は以下を表示する:

RGBDImage of size
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.

変換された画像は、numpy配列としてレンダリングすることができる。

In [ ]:
plt.subplot(1, 2, 1)
plt.title('Redwood grayscale image')
plt.imshow(rgbd_image.color)
plt.subplot(1, 2, 2)
plt.title('Redwood depth image')
plt.imshow(rgbd_image.depth)
plt.show()

RGBD画像は、カメラ・パラメータにより、ポイントクラウド(点群)に変換することができる。

In [ ]:
pcd = create_point_cloud_from_rgbd_image(rgbd_image,
                PinholeCameraIntrinsic.prime_sense_default)
# Flip it, otherwise the pointcloud will be upside down
pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
draw_geometries([pcd])

ここでは、デフォルトのカメラ・パラメータとしてPinholeCameraIntrinsic.prime_sense_defaultを使用する。 画像の解像度は640x480、焦点距離(fx、fy)=(525.0,525.0)、光学中心(cx、cy)=(319.5,239.5)である。 恒等行列(単位行列)がデフォルトの外部パラメータとして使用される。 pcd.transformは、わかりやすい視点にするため、ポイントクラウド上で上下反転変換を適用する。 この結果は: http://www.open3d.org/docs/_images/redwood_pcd1.png

SUN dataset

ここではSUN dataset (Song et al. 2015)を読み、可視化する。

Song,S., Lichtenberg, S. & J. Xiao (2015). SUN RGB-D: A RGB-D Scene Understanding Benchmark Suite, CVPR.

In [114]:
# src/Python/Tutorial/Basic/rgbd_sun.py
%matplotlib inline
#conda install pillow matplotlib
from py3d import *
import matplotlib.pyplot as plt


if __name__ == "__main__":
        print("Read SUN dataset")
        color_raw = read_image("./TestData/RGBD/other_formats/SUN_color.jpg")
        depth_raw = read_image("./TestData/RGBD/other_formats/SUN_depth.png")
        rgbd_image = create_rgbd_image_from_sun_format(color_raw, depth_raw);
        print(rgbd_image)
        plt.figure(figsize=(12,5))    
        plt.subplot(1, 3, 1)
        plt.title('SUN original image')
        plt.imshow(color_raw)
        plt.subplot(1, 3,  2)
        plt.title('SUN grayscale image')
        plt.imshow(rgbd_image.color, cmap='gray')
        plt.subplot(1, 3, 3)
        plt.title('SUN depth image')
        plt.imshow(rgbd_image.depth)
        plt.show()
        pcd = create_point_cloud_from_rgbd_image(rgbd_image,
                        PinholeCameraIntrinsic.prime_sense_default)
        # Flip it, otherwise the pointcloud will be upside down
        pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
        draw_geometries([pcd])
Read SUN dataset
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.

このチュートリアルは、Redwoodデータセットを処理するチュートリアルとほぼ同じであるが、唯一の違いは、変換関数create_rgbd_image_from_sun_formatを使用して、SUNデータセットの深度画像を解析することである。

RGBDImagenumpy配列としてレンダリングすることもできる:

ポイントクラウドとしても出力できる: http://www.open3d.org/docs/_images/sun_pcd.png

NYU dataset

このチュートリアルは、NYU dataset (Silberman et al. 2012)のRGBDImageを読み込み可視化する。

Silberman, N., Hoiem, D., Kohli, P., & R. Fergus (2012) Indoor Segmentation and Support Inference from RGBD Images, ECCV.

Redwoodデータセットを処理するチュートリアルとほぼ同じであるが、2つの違いがある。 まず、NYUの画像は標準のjpg形式でもpng形式でもない。 したがって、mpimg.imreadを使用してカラー画像を数値配列として読み込み、それをOpen3D Imageに変換する。 追加のヘルパー関数read_nyu_pgmを呼び出し、NYUデータセットで使用される特別なビッグエンディアンのpgm形式から深度イメージを読み込む。 次に、変換関数create_rgbd_image_from_nyu_formatを使用して、SUNデータセットの深度イメージを解析する。

同様に、RGBDImageをnumpy配列としてレンダリングすることもできる:

In [116]:
# src/Python/Tutorial/Basic/rgbd_nyu.py
%matplotlib inline
#conda install pillow matplotlib
from py3d import *
import numpy as np
import re
import matplotlib.image as mpimg
import matplotlib.pyplot as plt

# This is special function used for reading NYU pgm format
# as it is written in big endian byte order.
def read_nyu_pgm(filename, byteorder='>'):
        with open(filename, 'rb') as f:
                buffer = f.read()
        try:
                header, width, height, maxval = re.search(
                        b"(^P5\s(?:\s*#.*[\r\n])*"
                        b"(\d+)\s(?:\s*#.*[\r\n])*"
                        b"(\d+)\s(?:\s*#.*[\r\n])*"
                        b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups()
        except AttributeError:
                raise ValueError("Not a raw PGM file: '%s'" % filename)
        img = np.frombuffer(buffer,
                dtype=byteorder+'u2',
                count=int(width)*int(height),
                offset=len(header)).reshape((int(height), int(width)))
        img_out = img.astype('u2')
        return img_out

if __name__ == "__main__":
        print("Read NYU dataset")
        # Open3D does not support ppm/pgm file yet. Not using read_image here.
        # MathplotImage having some ISSUE with NYU pgm file. Not using imread for pgm.
        color_raw = mpimg.imread("./TestData/RGBD/other_formats/NYU_color.ppm")
        depth_raw = read_nyu_pgm("./TestData/RGBD/other_formats/NYU_depth.pgm")
        color = Image(color_raw)
        depth = Image(depth_raw)
        rgbd_image = create_rgbd_image_from_nyu_format(color, depth)
        print(rgbd_image)
        plt.figure(figsize=(12,5))
        plt.subplot(1,3,1)
        plt.title('NYU original image')
        plt.imshow(color_raw)
        plt.subplot(1, 3, 2)
        plt.title('NYU grayscale image')
        plt.imshow(rgbd_image.color,cmap='gray')
        plt.subplot(1, 3, 3)
        plt.title('NYU depth image')
        plt.imshow(rgbd_image.depth)
        plt.show()
        pcd = create_point_cloud_from_rgbd_image(rgbd_image,
                        PinholeCameraIntrinsic.prime_sense_default)
        # Flip it, otherwise the pointcloud will be upside down
        pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
        draw_geometries([pcd])
Read NYU dataset
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.

ポイントクラウドとしての表示: http://www.open3d.org/docs/_images/nyu_pcd.png

TUM dataset

TUM dataset (Strum et al. 2012)を用いた紹介。

Sturm, J., Engelhard, N., Endres, F., Burgard W., & D. Cremers (2012) A Benchmark for the Evaluation of RGB-D SLAM Systems, IROS.

これもRedwoodデータセットを処理するチュートリアルとほぼ同じであるが、唯一の違いは、変換関数create_rgbd_image_from_tum_formatを使用して、TUMデータセットの深度画像を解析することである。Redwoodデータセットと同様に、RGBDImageをnumpy配列としてレンダリングすることもできる。

In [117]:
# src/Python/Tutorial/Basic/rgbd_tum.py
#conda install pillow matplotlib
%matplotlib inline
from py3d import *
import matplotlib.pyplot as plt


if __name__ == "__main__":
        print("Read TUM dataset")
        color_raw = read_image("./TestData/RGBD/other_formats/TUM_color.png")
        depth_raw = read_image("./TestData/RGBD/other_formats/TUM_depth.png")
        rgbd_image = create_rgbd_image_from_tum_format(color_raw, depth_raw);
        print(rgbd_image)
        plt.figure(figsize=(12,5))
        plt.subplot(1,3,1)
        plt.title('TUM original image')
        plt.imshow(color_raw)
        plt.subplot(1, 3, 2)
        plt.title('TUM grayscale image')
        plt.imshow(rgbd_image.color, cmap='gray')
        plt.subplot(1, 3, 3)
        plt.title('TUM depth image')
        plt.imshow(rgbd_image.depth)
        plt.show()
        pcd = create_point_cloud_from_rgbd_image(rgbd_image,
                        PinholeCameraIntrinsic.prime_sense_default)
        # Flip it, otherwise the pointcloud will be upside down
        pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
        draw_geometries([pcd])
Read TUM dataset
RGBDImage of size 
Color image : 640x480, with 1 channels.
Depth image : 640x480, with 1 channels.
Use numpy.asarray to access buffer data.