金坛网站建设报价,网站开发岗位群,中江门户网站,国外的做外包项目的网站简介
在本文中#xff0c;我提供了一个关于如何使用Python的Open3D库#xff08;一个用于3D数据处理的开源库#xff09;来探索、处理和可视化3D模型的快速演练。 使用Open3D可视化的3D模型#xff08;链接https://sketchfab.com/3d-models/tesla-model-s-plaid-9de8855fa…简介
在本文中我提供了一个关于如何使用Python的Open3D库一个用于3D数据处理的开源库来探索、处理和可视化3D模型的快速演练。 使用Open3D可视化的3D模型链接https://sketchfab.com/3d-models/tesla-model-s-plaid-9de8855fae324e6cbbb83c9b5288c961处可找到原始3D模型
如果您正在考虑处理特定任务的3D数据/模型例如训练3D模型分类和/或分割AI模型那么您会发现本演练是很有帮助的。互联网上的3D模型在ShapeNet等数据集中有多种格式如.obj、.glb、.gltf等。使用Open3D等库可以轻松处理、可视化这些模型并将其转换为其他格式如点云因为这些格式更容易理解和解释。
注意这篇文章也可以作为Jupyter笔记本提供给那些希望跟随并在本地运行代码的人。包含Jupyter笔记本以及所有其他数据和有关资源的zip文件可以从下面的链接下载。
3D Data Processing with Open3D.zip。
在本教程中我将完成以下任务
将三维模型加载为网格并将其可视化通过采样点将网格转换为点云从点云中删除隐藏点将点云转换为数据帧保存点云和数据帧
下面让我们从导入所有必要的库开始
# 导入open3d和所有其他必要的库。import open3d as o3d
import os
import copy
import numpy as np
import pandas as pd
from PIL import Imagenp.random.seed(42)
#正在检查open3d上安装的版本。o3d.__version__
# Open3D version used in this exercise: 0.16.0
将三维模型加载为网格并将其可视化
通过运行以下代码行可以将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模型而是渲染成了统一的灰色。这是因为网格没有任何关于三维模型中顶点和曲面的法线的信息。
什么是法线呢-曲面在给定点处的法向量是垂直于该点处曲面的向量。法向量通常简称为“法线”。要关于此主题的内容可以参考以下两个链接法线向量和估计点云中的曲面法线。
可以通过运行以下代码行来估计上面三维网格的法线
# 计算网格的法线。
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(size5, origin[0, 0, 0])#使用坐标系可视化网格以了解方向。
draw_geoms_list [mesh_coord_frame, mesh]
o3d.visualization.draw_geometries(draw_geoms_list) 使用XYZ坐标系可视化的三维网格X轴红色箭头Y轴绿色箭头Z轴蓝色箭[简记为——XYZRGB]
从上面的可视化中我们可以看到这个汽车网格的方向如下
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_minNone, a_max0)
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) 在移除汽车右半部分的情况下在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) 通过从三维网格中均匀采样100000个点创建的三维点云
请注意上面点云中的颜色仅指示点沿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汽车模型中的详细内部
我们在裁剪点云的可视化中看到它还包含属于汽车模型内部的点。这是意料之中的因为该点云是通过对整个网格中的点进行均匀采样而创建的。在下一节中我们将删除这些属于汽车内部且不在点云外表面的“隐藏”点。
从点云中删除隐藏点
想象一下你把一盏灯指向汽车模型的右侧。落在三维模型右外表面上的所有点都将被照亮而点云中的所有其他点则不会被照亮。 显示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, invertTrue)
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度的三维点云。请注意与以前不同的是现在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, invertTrue)
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) 从上图所示的摄影机视点移除隐藏点操作后旋转的点云。同样“可见”点为蓝色而“隐藏”点为红色
上面旋转的点云的可视化清楚地说明了隐藏点移除操作是如何工作的。因此现在为了从这个汽车点云中移除所有“隐藏”点我们可以通过将点云围绕所有三个轴从-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 * 100return 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(fRemoving 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_mapview_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, invertTrue)
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坐标。请记住在上面的部分中我们还估计了3D网格中每个点的曲面法线。当我们从该网格中采样点以创建点云时点云中的每个点还包含与这些曲面法线相关的三个附加属性——X、Y和Z方向上的法线单位向量坐标。
因此为了将点云转换为数据帧点云中的每个点都可以用以下七个属性列在一行中来表示
X坐标浮动Y坐标浮动Z坐标浮动X方向上的法向量坐标浮点Y方向上的法向量坐标浮点Z方向上的法向量坐标浮动可见点布尔值True或False
通过运行以下代码行可以将点云转换为数据帧
# 为点云创建一个数据帧其中包含所有点的X、Y和Z位置坐标以及X、Y、Z方向上的法向单位向量坐标。
pcd_df pd.DataFrame(np.concatenate((np.asarray(pcd.points), np.asarray(pcd.normals)), axis1),columns[x, y, z, norm-x, norm-y, norm-z])# 使用上面隐藏点删除操作中的点索引聚合列表添加一列以指示点是否可见。
pcd_df[point_visible] False
pcd_df.loc[pt_map_aggregated, point_visible] True
这将返回如下所示的数据帧其中每个点都是由上面解释的七个属性列表示的行。 转换为数据帧的三维点云
保存点云和数据帧
现在可以通过运行以下代码行来保存点云删除隐藏点之前和之后和数据帧
# 将整个点云保存为.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, indexFalse) 三维模型顶部整体底部裁剪可视化为1——网格2——一个点云3——删除隐藏点后的点云