DUICUO

PythonのOpen3Dオープンソースライブラリを使用して3Dデータ処理を行う方法

51CTO ウェブサイト コンテンツ アンケートに参加するには、ここをクリックしてください。

翻訳者 |朱賢宗

校正者 | Chonglou

導入

この記事では、Python の Open3D ライブラリ (3D データ処理用のオープンソース ライブラリ) を使用して 3D モデルを探索、操作、視覚化する方法を簡単に説明します。


3D モデルは Open3D を使用して視覚化されました (元の 3D モデルはリンク https://sketchfab.com/3d-models/tesla-model-s-plaid-9de8855fae324e6cbbb83c9b5288c961 にあります)。

分類やセグメンテーション AI モデル用の 3D モデルのトレーニングなど、特定のタスクのために 3D データ/モデルを使用することを検討している場合は、このチュートリアルが役立ちます。インターネット上の 3D モデル (ShapeNet などのデータセット内) には、.obj、.glb、.gltf など、さまざまな形式があります。Open3D などのライブラリを使用すると、これらのモデルを簡単に処理、視覚化して、ポイント クラウドなどの理解と解釈が容易な他の形式に変換できます。

この記事は、Jupyter Notebook としても提供されており、ローカルでコードを実行したい方にもご利用いただけます。Jupyter Notebook とその他のデータおよび関連リソースを含む zip ファイルは、以下のリンクからダウンロードできます。

Open3D による 3D データ処理.zip。

このチュートリアルでは、次のタスクを完了します。

  1. 3D モデルをメッシュとして読み込み、視覚化します。
  2. ポイントをサンプリングしてグリッドをポイント クラウドに変換します。
  3. ポイントクラウドから隠れたポイントを削除する
  4. ポイントクラウドをデータフレームに変換する
  5. ポイントクラウドとデータフレームを保存する

さて、まずは必要なライブラリをすべてインポートすることから始めましょう。

 # 导入open3d和所有其他必要的库。 import open3d as o3d import os import copy import numpy as np import pandas as pd from PIL import Image np.random.seed(42) #正在检查open3d上安装的版本。 o3d.__version__ # Open3D version used in this exercise: 0.16.0

3D モデルをメッシュとして読み込み、視覚化します。

次のコード行を実行すると、3D モデルをメッシュとして読み取ることができます。

 #定义三维模型文件的路径。 mesh_path = "data/3d_model.obj" # 使用open3d将三维模型文件读取为三维网格。 mesh = o3d.io.read_triangle_mesh(mesh_path)

グリッドを視覚化するには、次のコード行を実行します。

 #可视化网格。 draw_geoms_list = [mesh] o3d.visualization.draw_geometries(draw_geoms_list)

グリッドは新しいウィンドウで開き、下の画像のようになります(グリッドはここで示されているようなアニメーション画像ではなく、静止画像として開きます)。マウスポインターを使って、必要に応じてグリッド画像を回転させることができます。


3D モデルをメッシュとして視覚化します (表面法線を推定する前に)。

前述の通り、車のメッシュは典型的な3Dモデルとは異なり、均一なグレーでレンダリングされています。これは、メッシュに3Dモデルの頂点と面の法線に関する情報がないためである。

法線とは? - 面上の任意の点における法線ベクトルとは、その点における面に対して垂直なベクトルです。法線ベクトルは単に「法線」と呼ばれることもあります。このトピックについて詳しくは、以下の2つのリンクをご覧ください。「法線ベクトル」と「点群における面法線の推定」。

上記の 3D メッシュの法線は、次のコード行を実行することで推定できます。

 # 计算网格的法线。 mesh.compute_vertex_normals() #可视化网格。 draw_geoms_list = [mesh] o3d.visualization.draw_geometries(draw_geoms_list)

メッシュを視覚化すると、下の画像のようになります。法線を計算すると、車は正しくレンダリングされ、3Dモデルのように見えます。

メッシュとして視覚化された 3D モデル (表面法線を推定した後)。

さて、この車のモデルのユークリッド空間における向きを決定するためのXYZ座標系を作成しましょう。以下のコードを実行することで、このXYZ座標系を上記の3Dメッシュに重ねて視覚化できます。

 # 创建XYZ轴笛卡尔坐标系的网格。 # 该网格将显示X、Y和Z轴指向的方向,并且可以覆盖在3D网格上,以可视化其在欧几里得空间中的方向。 # X-axis : 红色箭头# Y-axis : 绿色箭头# Z-axis : 蓝色箭头mesh_coord_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5, origin=[0, 0, 0]) #使用坐标系可视化网格,以了解方向。 draw_geoms_list = [mesh_coord_frame, mesh] o3d.visualization.draw_geometries(draw_geoms_list) 


XYZ 座標系 (X 軸: 赤い矢印、Y 軸: 緑の矢印、Z 軸: 青い矢印、[XYZ::RGB と略記]) を使用して視覚化された 3D メッシュ。

上記の視覚化から、この車のメッシュの方向は次のようになっていることがわかります。

  • XYZ 軸の原点は、自動車モデルのボリュームの中心にあります (自動車のメッシュ内にあるため、上の画像では表示されていません)。
  • X 軸 (赤い矢印): 車の長さに沿って、正の X 軸は車のボンネットを指します (車のメッシュ内にあるため、上の画像では表示されていません)。
  • Y 軸 (緑の矢印): 車の高さ方向に沿って、正の Y 軸は車の屋根を指します。
  • Z 軸 (青い矢印): 車の幅に沿って、正の Z 軸は車の右側を指します。

さて、この車のモデルの内部を見てみましょう。そのためには、メッシュをZ軸に沿ってトリミングし、車の右半分(Z軸の正方向)を削除します。

 #使用其束框裁剪汽车网格以移除其右半部分(正Z轴)。 bbox = mesh.get_axis_aligned_bounding_box() bbox_points = np.asarray(bbox.get_box_points()) bbox_points[:, 2] = np.clip(bbox_points[:, 2], a_min=None, a_max=0) bbox_cropped = o3d.geometry.AxisAlignedBoundingBox.create_from_points(o3d.utility.Vector3dVector(bbox_points)) mesh_cropped = mesh.crop(bbox_cropped) # 可视化裁剪的网格。 draw_geoms_list = [mesh_coord_frame, mesh_cropped] o3d.visualization.draw_geometries(draw_geoms_list) 

車の右半分が削除され、3DメッシュはZ軸(正のZ軸)に沿って切り取られます。切り取られたメッシュにより、この3D車モデルの詳細な内部構造が明らかになります。

上記の視覚化から、この車のモデルには精巧な内装が施されていることがわかります。3Dメッシュ内のコンテンツを確認できたので、それをポイントクラウドに変換し、車内に存在する「隠れた」ポイントを削除します。

ポイントをサンプリングしてグリッドをポイント クラウドに変換します。

メッシュからサンプリングするポイントの数を定義することで、メッシュを Open3D のポイント クラウドに簡単に変換できます。

 #从网格中均匀采样100000个点,将其转换为点云。 n_pts = 100_000 pcd = mesh.sample_points_uniformly(n_pts) #可视化点云。 draw_geoms_list = [mesh_coord_frame, pcd] o3d.visualization.draw_geometries(draw_geoms_list) 


3D メッシュから 100,000 ポイントを均一にサンプリングして 3D ポイント クラウドを作成しました。

上記のポイント クラウドの色は、Z 軸に沿ったポイントの位置のみを示していることに注意してください。

上記のグリッドを切り取ったのと同じようにポイント クラウドを切り取ると、次のようになります。

 #使用边界框裁剪汽车点云以移除其右半部分(正Z轴)。 pcd_cropped = pcd.crop(bbox_cropped) #可视化裁剪的点云。 draw_geoms_list = [mesh_coord_frame, pcd_cropped] o3d.visualization.draw_geometries(draw_geoms_list) 




車の右半分を削除した後、Z軸(Z軸正方向)に沿ってクリッピングされた3Dポイントクラウド。上記のクリッピングされたメッシュと同様に、クリッピングされたポイントクラウドからも、この3D車モデルの詳細な内装が明らかになります。

切り取られた点群の可視化では、車体モデルの内部に属する点も含まれていることがわかります。これは、点群がメッシュ全体にわたって均一に点をサンプリングして作成されたため、予想通りの結果です。次のセクションでは、車体の内部に属し、点群の外面上にはないこれらの「隠れた」点を削除します。

ポイントクラウドから隠れたポイントを削除する

車のモデルの右側にライトを向けると想像してみてください。3Dモデルの右側の外側にあるすべてのポイントが照らされますが、ポイントクラウド内の他のポイントは照らされません。

Open3Dの隠れ点除去機能が、特定の視点から点群データを処理する様子を示した図。照明が当たっている点はすべて「見える」とみなされ、それ以外の点は「隠れている」とみなされます。

さて、これらの光っている点を「可視」、光っていない点を「隠蔽」としてマークしましょう。これらの「隠蔽」された点には、車内の点も含まれます。

この操作はOpen3Dでは「隠れ点の削除」と呼ばれます。Open3Dを使用して点群に対してこの操作を実行するには、次のコードを実行します。

 # 定义隐藏点删除操作的摄影机和半径参数。 diameter = np.linalg.norm(np.asarray(pcd.get_min_bound()) - np.asarray(pcd.get_max_bound())) camera = [0, 0, diameter] radius = diameter * 100 # 使用上面定义的摄影机和半径参数对点云执行隐藏点删除操作。 #输出是可见点的索引列表。 _, pt_map = pcd.hidden_point_removal(camera, radius)

上記の可視ポイント インデックス出力リストを使用すると、ポイント クラウドを視覚化する前に、可視ポイントと非表示ポイントを異なる色で色分けできます。

 # 将点云中的所有可见点绘制为蓝色,将所有隐藏点绘制为红色。 pcd_visible = pcd.select_by_index(pt_map) pcd_visible.paint_uniform_color([0, 0, 1]) #蓝色点是可见点(需要保留)。 print("No. of visible points : ", pcd_visible) pcd_hidden = pcd.select_by_index(pt_map, invert=True) pcd_hidden.paint_uniform_color([1, 0, 0]) # 红色点是隐藏点(要删除)。 print("No. of hidden points : ", pcd_hidden) # 可视化点云中的可见(蓝色)和隐藏(红色)点。 draw_geoms_list = [mesh_coord_frame, pcd_visible, pcd_hidden] o3d.visualization.draw_geometries(draw_geoms_list) 


上の点群は、カメラの視点から隠れた点を削除した結果を示しています。見える点は青で、隠れた点は赤で表示されています。

上の図から、特定のカメラ視点から隠れ点除去操作がどのように機能するかがわかります。この操作は、特定のカメラ視点から、前景の点によって隠されている背景の点をすべて削除します。

これをよりよく理解するために、同じ操作を繰り返しますが、今回はポイントクラウドを少し回転させます。実際には、ここでは視点を変えようとしています。ただし、カメラパラメータを再定義して視点を変えるのではなく、ポイントクラウド自体を回転させます。

 #定义将度数转换为弧度的函数。 def deg2rad(deg): return deg * np.pi/180 #将点云绕X轴旋转90度。 x_theta = deg2rad(90) y_theta = deg2rad(0) z_theta = deg2rad(0) tmp_pcd_r = copy.deepcopy(pcd) R = tmp_pcd_r.get_rotation_matrix_from_axis_angle([x_theta, y_theta, z_theta]) tmp_pcd_r.rotate(R, center=(0, 0, 0)) #可视化旋转的点云。 draw_geoms_list = [mesh_coord_frame, tmp_pcd_r] o3d.visualization.draw_geometries(draw_geoms_list) 

X軸を中心に90度回転した3D点群。以前とは異なり、Y軸(緑の矢印)は車両の幅方向、Z軸(青の矢印)は車両の高さ方向に沿っていることに注意してください。X軸(赤の矢印)は変更されておらず、車両の長さ方向に沿っています。

この図は、回転した点群に対して、前と同じ視点から隠れた点の削除操作がどのように行われるかを示しています。前述のように、照明が当たっている点はすべて「見える」とみなされ、それ以外の点はすべて「隠れている」とみなされます。

回転する車のモデルで同じプロセスを繰り返すと、今度は 3D モデルの外面 (屋根) 上のすべてのポイントが照らされ、ポイント クラウド内の他のすべてのポイントは照らされないことがわかります。

次のコード行を実行すると、回転するポイント クラウド上で隠れたポイントの削除操作を繰り返すことができます。

 # 使用上面定义的相同摄影机和半径参数对旋转的点云执行隐藏点移除操作。 #输出是可见点的索引列表。 _, pt_map = tmp_pcd_r.hidden_point_removal(camera, radius) # 将旋转的点云中的所有可见点绘制为蓝色,将所有隐藏点绘制为红色。 pcd_visible = tmp_pcd_r.select_by_index(pt_map) pcd_visible.paint_uniform_color([0, 0, 1]) # 蓝色点是可见点(需要保留)。 print("No. of visible points : ", pcd_visible) pcd_hidden = tmp_pcd_r.select_by_index(pt_map, invert=True) pcd_hidden.paint_uniform_color([1, 0, 0]) #红色点是隐藏点(要删除)。 print("No. of hidden points : ", pcd_hidden) #可视化旋转的点云中的可见(蓝色)和隐藏(红色)点。 draw_geoms_list = [mesh_coord_frame, pcd_visible, pcd_hidden] o3d.visualization.draw_geometries(draw_geoms_list) 

上の画像は、カメラの視点から隠れた点を削除した後、回転した点群を示しています。ここでも、「見える」点は青、「隠れた」点は赤で表示されています。

上記の回転ポイントクラウドの視覚化は、隠れ点除去操作がどのように機能するかを明確に示しています。したがって、この車のポイントクラウドからすべての「隠れた」ポイントを削除するには、ポイントクラウドを3つの軸すべてを中心に-90度から+90度まで少しずつ回転させることで、隠れ点除去操作を順次実行できます。各隠れ点除去操作の後、ポイントインデックスの出力リストを集約できます。すべての隠れ点除去操作の後、集約されたポイントインデックスリストには、隠れていないすべてのポイント(つまり、ポイントクラウドの外側にあるポイント)が含まれます。以下のコードは、この隠れ点除去操作を順次実行します。

 # 定义一个函数以在X、Y和Z轴上旋转点云。 def get_rotated_pcd(pcd, x_theta, y_theta, z_theta): pcd_rotated = copy.deepcopy(pcd) R = pcd_rotated.get_rotation_matrix_from_axis_angle([x_theta, y_theta, z_theta]) pcd_rotated.rotate(R, center=(0, 0, 0)) return pcd_rotated # 定义一个函数以获取隐藏点移除操作的点云的相机和半径参数。 def get_hpr_camera_radius(pcd): diameter = np.linalg.norm(np.asarray(pcd.get_min_bound()) - np.asarray(pcd.get_max_bound())) camera = [0, 0, diameter] radius = diameter * 100 return camera, radius # 定义一个函数,使用前面定义的摄影机和半径参数对点云执行隐藏点删除操作。 #输出是未隐藏的点的索引列表。 def get_hpr_pt_map(pcd, camera, radius): _, pt_map = pcd.hidden_point_removal(camera, radius) return pt_map # 通过在三个轴中的每一个轴上将点云从-90度略微旋转到+90度,依次执行隐藏点移除操作,并在每次操作后聚合未隐藏的点的索引列表。 # 定义一个列表来存储每个隐藏点删除操作的聚合输出列表。 pt_map_aggregated = [] # 定义旋转点云的步长和角度值范围。 theta_range = np.linspace(-90, 90, 7) # 对顺序操作的次数进行计数。 view_counter = 1 total_views = theta_range.shape[0] ** 3 # 获取隐藏点移除操作的相机和半径参数。 camera, radius = get_hpr_camera_radius(pcd) # 循环使用上面为每个轴定义的角度值。 for x_theta_deg in theta_range: for y_theta_deg in theta_range: for z_theta_deg in theta_range: print(f"Removing hidden points - processing view {view_counter} of {total_views}.") #按给定的角度值旋转点云。 x_theta = deg2rad(x_theta_deg) y_theta = deg2rad(y_theta_deg) z_theta = deg2rad(z_theta_deg) pcd_rotated = get_rotated_pcd(pcd, x_theta, y_theta, z_theta) # 使用上面定义的摄影机和半径参数对旋转的点云执行隐藏点移除操作。 pt_map = get_hpr_pt_map(pcd_rotated, camera, radius) # 聚合未隐藏的点的索引的输出列表。 pt_map_aggregated += pt_map view_counter += 1 # 通过将聚合列表转换为集合,从聚合列表中删除所有重复的点。 pt_map_aggregated = list(set(pt_map_aggregated)) # 将点云中的所有可见点绘制为蓝色,将所有隐藏点绘制为红色。 pcd_visible = pcd.select_by_index(pt_map_aggregated) pcd_visible.paint_uniform_color([0, 0, 1]) # 蓝色点是可见点(需要保留)。 print("No. of visible points : ", pcd_visible) pcd_hidden = pcd.select_by_index(pt_map_aggregated, invert=True) pcd_hidden.paint_uniform_color([1, 0, 0]) # 红色点是隐藏点(要删除)。 print("No. of hidden points : ", pcd_hidden) # 可视化点云中的可见(蓝色)和隐藏(红色)点。 draw_geoms_list = [mesh_coord_frame, pcd_visible, pcd_hidden] # draw_geoms_list = [mesh_coord_frame, pcd_visible] # draw_geoms_list = [mesh_coord_frame, pcd_hidden] o3d.visualization.draw_geometries(draw_geoms_list) 



同じカメラ視点から、すべての連続した隠れ点除去操作を実行した後の点群。集約された「見える」点(つまり、点群の外面にある点)は青で表示され、「隠れた」点(点群の外面に面していない点など)は赤で表示されます。

もう一度ポイントクラウドを切り取って、車内のポイントを見てみましょう。

 #使用先前定义的边界框裁剪可见点的点云,以移除其右半部分(正Z轴)。 pcd_visible_cropped = pcd_visible.crop(bbox_cropped) # 使用先前定义的边界框裁剪隐藏点的点云,以移除其右半部分(正Z轴)。 pcd_hidden_cropped = pcd_hidden.crop(bbox_cropped) # 可视化裁剪的点云。 draw_geoms_list = [mesh_coord_frame, pcd_visible_cropped, pcd_hidden_cropped] o3d.visualization.draw_geometries(draw_geoms_list) 



すべての連続した隠れたポイントの削除操作の後に切り取られたポイント クラウドには、3D 車両モデルの内部に属するすべての「隠れた」ポイントが赤で表示されます。

上記の隠れた点の削除操作後の点群の視覚化では、自動車モデルの内部に属するすべての「隠れた」点 (赤) が、点群の外側の表面にある「見える」点 (青) から分離されていることがわかります。

ポイントクラウドをデータフレームに変換する

当然のことながら、ポイントクラウド内の各ポイントの位置は、X、Y、Z座標の3つの数値で定義できます。前のセクションでは、3Dメッシュ内の各ポイントの面法線も推定しました。メッシュからポイントをサンプリングしてポイントクラウドを作成すると、ポイントクラウド内の各ポイントには、これらの面法線に関連付けられた3つの追加プロパティ、つまりX、Y、Z方向の法線の単位ベクトル座標が含まれます。

したがって、ポイント クラウドをデータ フレームに変換するには、ポイント クラウド内の各ポイントを 1 行にリストされた次の 7 つの属性で表すことができます。

  1. X座標(浮動)
  2. Y座標(浮動)
  3. Z座標(浮動)
  4. X方向の法線ベクトル座標(浮動小数点)
  5. Y方向の法線ベクトル座標(浮動小数点)
  6. Z方向の法線ベクトル座標(浮動小数点)
  7. 表示ポイント(ブール値:TrueまたはFalse)

次のコード行を実行すると、ポイント クラウドをデータ フレームに変換できます。

 # 为点云创建一个数据帧,其中包含所有点的X、Y和Z位置坐标以及X、Y、Z方向上的法向单位向量坐标。 pcd_df = pd.DataFrame(np.concatenate((np.asarray(pcd.points), np.asarray(pcd.normals)), axis=1), columns=["x", "y", "z", "norm-x", "norm-y", "norm-z"] ) # 使用上面隐藏点删除操作中的点索引聚合列表,添加一列以指示点是否可见。 pcd_df["point_visible"] = False pcd_df.loc[pt_map_aggregated, "point_visible"] = True

これにより、以下に示すようなデータ フレームが返されます。各ポイントは、上で説明した 7 つの属性列によって表される行です。

3Dポイントクラウドをデータフレームに変換

ポイントクラウドとデータフレームを保存する

次のコード行を実行すると、ポイント クラウド (非表示のポイントを削除する前と後) とデータ フレームを保存できるようになります。

 # 将整个点云保存为.pcd文件。 pcd_save_path = "data/3d_model.pcd" o3d.io.write_point_cloud(pcd_save_path, pcd) # 将删除了隐藏点的点云保存为.pcd文件。 pcd_visible_save_path = "data/3d_model_hpr.pcd" o3d.io.write_point_cloud(pcd_visible_save_path, pcd_visible) # 将点云数据帧保存为.csv文件。 pcd_df_save_path = "data/3d_model.csv" pcd_df.to_csv(pcd_df_save_path, index=False) 


3D モデル (上: 全体、下: 切り取り) は、1 – メッシュ、2 – ポイント クラウド、3 – 隠れたポイントを削除した後のポイント クラウドとして視覚化されます。

要約

このチュートリアルを通して、Python を用いた 3D データの処理方法をより深く理解していただければ幸いです。最後に、このチュートリアルで使用している 3D カーモデルは、この演習の目的に合わせて元のファイルから若干変更されています。これは、ValentunW 氏によるクリエイティブ・コモンズ表示ライセンスのもとでライセンスされている、元のファイルの作成者である「Tesla Model S Plaid」氏に感謝の意を表します。

翻訳者紹介

Zhu Xianzong 氏は、51CTO コミュニティ エディター、51CTO 専門ブロガー兼講師、維坊の大学のコンピューター教師、そしてフリー プログラミング コミュニティのベテランです。

原題: Open3D による 3D データ処理、著者: Prera​​k Agarwal