※当サイトはPRを含みます

【Python_3D点群処理】RANSACを用いた直線推定,平面推定

2023年8月30日

はじめに

RANSACを使って点群データの面やエッジの抽出をします。

このように平面を検出できます。(参照元)
image.png

RANSACとは

RANSACはRandom Sample Consensus の略でロバストなモデル推定アルゴリズムです。
(ロバスト推定とは与えられた観測値に外れ値が含まれているとして、その影響を抑えることを目的とした推定)

処理の手順は以下の通りです。
①データからランダムに数点を抽出する

②誤差がしきい値以下であればそのモデルを正しいモデル候補に追加する

image.png

③抽出したデータを元に最小二乗法などで仮のモデルを推定し、そのモデルと全体の誤差を求める

image.png

④仮のモデルからの距離がしきい値以下のデータ点をインライア(inlier: 外れ値でないデータ)とする

image.png

⑤インライアの数がしきい値以上、または既定の回数に達するまで上記の手順を繰り返す

簡単にアルゴリズムの説明をしましたが、今回は3次元の点群データ(x,y,z)で実装してみます。
直線ならば下記のようなモデル(A、Bはベクトル)をおき、

y=Ax+B

平面を推定したいなら

ax+by+cz+d=0

として計算をします。

Open3Dでの実装

まずはじめにOpen3Dのライブラリを使って実装してみます。

Open3Dのチュートリアルはこちら

以下が全体のコードです。

pcd.segment_planeの引数は

distance_threshold :インライアとするしきい値
ransac_n :平面を推定する最小の点数
num_iterations :試行回数

になります。

# 点群の読み込み
pcd = o3d.io.read_point_cloud("../../test_data/fragment.pcd")
# 引数を設定して、平面を推定
plane_model, inliers = pcd.segment_plane(distance_threshold=0.01,
                                         ransac_n=3,
                                         num_iterations=1000)
# 平面モデルの係数を出力
[a, b, c, d] = plane_model
print(f"Plane equation: {a:.2f}x + {b:.2f}y + {c:.2f}z + {d:.2f} = 0")

# インライアの点を抽出して色を付ける
inlier_cloud = pcd.select_by_index(inliers)
inlier_cloud.paint_uniform_color([1.0, 0, 0])

# 平面以外の点を抽出
outlier_cloud = pcd.select_by_index(inliers, invert=True)

# 可視化
o3d.visualization.draw_geometries([inlier_cloud, outlier_cloud],
                                  zoom=0.8,
                                  front=[-0.4999, -0.1659, -0.8499],
                                  lookat=[2.1813, 2.0619, 2.0999],
                                  up=[0.1204, -0.9852, 0.1215])

推定された平面の式は以下のように出力されました。

Plane equation: -0.06x + -0.10y + 0.99z + -1.06 = 0

赤くプロットされている箇所が平面と推定された点群になります。

pyRANSAC-3Dでの実装

今回はpyRANSAC-3Dというライブラリを用います。
pyRANSAC-3Dは直線や平面だけでなく球の推定などもできます。
詳細を知りたい方は下記ドキュメントを参照してください。
pyRANSAC-3D

まずはインストールします。

pip3 install pyransac3d

gitに例があるのでこちらを使用するためにクローンします。

git clone https://github.com/leomariga/pyRANSAC-3D.git

直線推定

直線推定の例を試します。以下をコマンドラインで実行すると結果が出力されます。

python3 pyRANSAC-3D/tests/test_line.py 

左が元の点群、右がRANSACで直線を推定した結果です。
赤色の点がインライア、赤色の線が近似した直線です。
image.png

コード(一部抜粋)を確認します。

# Open3Dのインポート
import open3d as o3d

# pyransac3d のインポート
import pyransac3d as pyrsc

# データをnumpy配列に変換
points = np.asarray(pcd_load.points)

# 直線モデルを定義
line = pyrsc.Line()

# RANSACによる直線推定。直線の係数(A,B)とインライア(inlier)を計算。しきい値は15
A, B, inliers = line.fit(points, thresh=15)

# 元のデータにおけるインライアの点の色を変更
plane = pcd_load.select_by_index(inliers).paint_uniform_color([1, 0, 0])

# Open3Dで可視化(元の点群、直線のインライア)
o3d.visualization.draw_geometries([pcd_load, plane])

平面推定

平面推定の例を試します。以下をコマンドラインで実行すると結果が出力されます。

python3 pyRANSAC-3D/tests/test_plane.py  

赤色の点が推定した平面のインライア、青色の線が推定した平面のバウンディングボックスです。

image.png
# 点群データ読み込み
pcd_load = o3d.io.read_point_cloud('/tests/dataset/caixa.ply')

# numpy配列に変換
points = np.asarray(pcd_load.points)

# 平面モデルを定義
plano1 = pyrsc.Plane()

# RANSACによる平面推定。平面の式とインライア(inlier)を計算。しきい値は0.01
best_eq, best_inliers = plano1.fit(points, 0.01)

# 元のデータにおけるインライアの点の色を変更
plane = pcd_load.select_by_index(best_inliers).paint_uniform_color([1, 0, 0])

# 平面のバウンディングボックスを取得
obb = plane.get_oriented_bounding_box()

# バウンディングボックスの色を定義
obb.color = [0, 0, 1]

# 平面以外の点を抽出
not_plane = pcd_load.select_by_index(best_inliers, invert=True)

# Open3Dで可視化(平面以外の点、平面の点、バウンディングボックス)
o3d.visualization.draw_geometries([not_plane, plane, obb])

おまけ:Jupyter Lab (k3d)での可視化

Open3Dをインストールしていない、Jupyterを使って可視化したい方はk3dというライブラリを使って可視化できます。

image.png
import k3d
import numpy as np

not_plane1 = np.asarray(not_plane.points)
plane1 = np.asarray(plane.points)

plot = k3d.plot(name='ransac_plane')
plot +=k3d.points(not_plane1, point_size=0.01)
plot +=k3d.points(plane1, point_size=0.01, color = 0xff0000)

plot.display()

※その他k3dの使い方はこちらの記事でも紹介しています。
【Python_K3D】K3Dでクラスごとに色を付けて点群データを可視化

おすすめ参考書