3D模型处理实战【Open3D】

news2024/11/15 7:48:24

在本文中,我们将学习如何使用 Python 的 Open3D 库探索、处理和可视化 3D 模型。

如果你正在考虑为特定任务处理 3D 数据/模型,例如为 3D 模型分类和/或分割训练 AI 模型,可能会发现本演练很有帮助。 在 Internet 上找到的 3D 模型(在 ShapeNet 等数据集中)有多种格式,例如 .obj、.glb、.gltf 等。 使用像 Open3D 这样的库,这样的模型可以很容易地处理、可视化并转换成其他格式,比如点云,这些格式更容易理解和解释。

对于那些希望跟随并在本地运行代码的人,本文还可以作为 Jupyter Notebook 使用。 包含 Jupyter Notebook 以及所有其他数据和资产的 zip 文件可以从这里下载。
在这里插入图片描述

推荐:用 NSDT设计器 快速搭建可编程3D场景。

0、开发环境准备

让我们从导入所有必要的库开始:

# Importing open3d and all other necessary libraries.

import open3d as o3d
import os
import copy
import numpy as np
import pandas as pd
from PIL import Image

np.random.seed(42)
# Checking the installed version on open3d.

o3d.__version__
# Open3D version used in this exercise: 0.16.0

1、加载 3D 模型并可视化

通过运行以下代码行,可以将 3D 模型读取为网格:

# Defining the path to the 3D model file.
mesh_path = "data/3d_model.obj"

# Reading the 3D model file as a 3D mesh using open3d.
mesh = o3d.io.read_triangle_mesh(mesh_path)

要可视化网格,请运行以下代码行:

# Visualizing the mesh.
draw_geoms_list = [mesh]
o3d.visualization.draw_geometries(draw_geoms_list)

网格应该会在新窗口中打开,并且应该如下图所示(请注意,网格打开时是静态图像,而不是此处显示的动画图像)。 可以使用鼠标指针根据需要旋转网格图像。
在这里插入图片描述

如上所示,汽车网格看起来不像典型的 3D 模型,而是涂成统一的灰色。 这是因为网格没有任何关于 3D 模型中顶点和表面法线的信息。

什么是法线? — 曲面在给定点的法向量是在该点垂直于曲面的向量。 法线向量通常简称为“法线”。 要阅读有关此主题的更多信息,可以参考这两个链接:Normal Vector 和 Estimating Surface Normals in a PointCloud。

上面 3D 网格的法线可以通过运行以下代码行来估算:

# Computing the normals for the mesh.
mesh.compute_vertex_normals()

# Visualizing the mesh.
draw_geoms_list = [mesh]
o3d.visualization.draw_geometries(draw_geoms_list)

可视化后,网格现在应如下图所示。 计算法线后,汽车正确渲染,看起来像一个 3D 模型。
在这里插入图片描述

现在让我们创建一个 XYZ 坐标系来了解该汽车模型在欧几里德空间中的方向。 XYZ 坐标系可以叠加在上面的 3D 网格上,并通过运行以下代码行进行可视化:

# Creating a mesh of the XYZ axes Cartesian coordinates frame.
# This mesh will show the directions in which the X, Y & Z-axes point,
# and can be overlaid on the 3D mesh to visualize its orientation in
# the Euclidean space.
# X-axis : Red arrow
# Y-axis : Green arrow
# Z-axis : Blue arrow
mesh_coord_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5, origin=[0, 0, 0])

# Visualizing the mesh with the coordinate frame to understand the orientation.
draw_geoms_list = [mesh_coord_frame, mesh]
o3d.visualization.draw_geometries(draw_geoms_list)

从上面的可视化中,我们看到这个汽车网格的方向如下:

在这里插入图片描述

  • XYZ 轴的原点:在汽车模型的体积中心(在上图中未显示,因为它位于汽车网格内部)。
  • X 轴(红色箭头):沿着汽车的长度方向,正 X 轴指向汽车的引擎盖(上图中未显示,因为它位于汽车网格内部)。
  • Y 轴(绿色箭头):沿着汽车的高度尺寸,正 Y 轴指向汽车的车顶。
  • Z 轴(蓝色箭头):沿着汽车的宽度尺寸,正 Z 轴指向汽车的右侧。

现在让我们来看看这款车型的内部结构。 为此,我们将在 Z 轴上裁剪网格并移除汽车的右半部分(正 Z 轴)。

# Cropping the car mesh using its bouding box to remove its right half (positive Z-axis).
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)

# Visualizing the cropped mesh.
draw_geoms_list = [mesh_coord_frame, mesh_cropped]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

从上面的可视化图中,我们可以看到这款汽车模型拥有详细的内饰。 现在我们已经看到了这个 3D 网格内部的内容,我们可以在移除属于汽车内部的“隐藏”点之前将其转换为点云。

2、通过采样点将网格转换为点云

通过定义我们希望从网格中采样的点数,可以在 Open3D 中轻松将网格转换为点云。

# Uniformly sampling 100,000 points from the mesh to convert it to a point cloud.
n_pts = 100_000
pcd = mesh.sample_points_uniformly(n_pts)

# Visualizing the point cloud.
draw_geoms_list = [mesh_coord_frame, pcd]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

请注意,上面点云中的颜色仅表示点沿 Z 轴的位置。

如果我们像上面的网格一样裁剪点云,这就是它的样子:

# Cropping the car point cloud using bounding box to remove its right half (positive Z-axis).
pcd_cropped = pcd.crop(bbox_cropped)

# Visualizing the cropped point cloud.
draw_geoms_list = [mesh_coord_frame, pcd_cropped]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

我们在裁剪点云的可视化中看到它还包含属于汽车模型内部的点。 这是意料之中的,因为此点云是通过从整个网格中均匀采样点创建的。 在下一节中,我们将移除这些属于汽车内部且不在点云外表面上的“隐藏”点。

3、从点云中移除隐藏点

想象一下,你将一盏灯指向汽车模型的右侧。 所有落在 3D 模型右外表面上的点都会被照亮,而点云中的所有其他点都不会。
在这里插入图片描述

现在让我们将这些被照亮的点标记为“可见”,将所有未被照亮的点标记为“隐藏”。 这些“隐藏”点还包括属于汽车内部的所有点。

此操作在 Open3D 中称为隐藏点移除(Hidden Point Removal)。 为了使用 Open3D 在点云上执行此操作,请运行以下代码行:

# Defining the camera and radius parameters for the hidden point removal operation.
diameter = np.linalg.norm(np.asarray(pcd.get_min_bound()) - np.asarray(pcd.get_max_bound()))
camera = [0, 0, diameter]
radius = diameter * 100

# Performing the hidden point removal operation on the point cloud using the
# camera and radius parameters defined above.
# The output is a list of indexes of points that are visible.
_, pt_map = pcd.hidden_point_removal(camera, radius)

使用上面的可见点索引输出列表,我们可以在可视化点云之前用不同的颜色为可见点和隐藏点着色。

# Painting all the visible points in the point cloud in blue, and all the hidden points in red.

pcd_visible = pcd.select_by_index(pt_map)
pcd_visible.paint_uniform_color([0, 0, 1])    # Blue points are visible points (to be kept).
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])    # Red points are hidden points (to be removed).
print("No. of hidden points : ", pcd_hidden)

# Visualizing the visible (blue) and hidden (red) points in the point cloud.
draw_geoms_list = [mesh_coord_frame, pcd_visible, pcd_hidden]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

从上面的可视化中,我们可以看到隐藏点移除操作是如何从给定的相机视角进行的。 该操作从给定的相机视点消除了被前景中的点遮挡的背景中的所有点。

为了更好地理解这一点,我们可以再次重复相同的操作,但这次是在稍微旋转点云之后。 实际上,我们正在尝试改变这里的观点。 但是我们不是通过重新定义相机参数来改变它,而是旋转点云本身。

# Defining a function to convert degrees to radians.
def deg2rad(deg):
    return deg * np.pi/180

# Rotating the point cloud about the X-axis by 90 degrees.
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))

# Visualizing the rotated point cloud.
draw_geoms_list = [mesh_coord_frame, tmp_pcd_r]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

在这里插入图片描述

通过对旋转的汽车模型再次重复相同的过程,我们会看到这次落在 3D 模型(车顶)上部外表面上的所有点都会被照亮,而该点中的所有其他点 云不会。

我们可以通过运行以下代码行对旋转后的点云重复隐藏点移除操作:

# Performing the hidden point removal operation on the rotated point cloud
# using the same camera and radius parameters defined above.
# The output is a list of indexes of points that are visible.
_, pt_map = tmp_pcd_r.hidden_point_removal(camera, radius)

# Painting all the visible points in the rotated point cloud in blue,
# and all the hidden points in red.

pcd_visible = tmp_pcd_r.select_by_index(pt_map)
pcd_visible.paint_uniform_color([0, 0, 1])    # Blue points are visible points (to be kept).
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])    # Red points are hidden points (to be removed).
print("No. of hidden points : ", pcd_hidden)

# Visualizing the visible (blue) and hidden (red) points in the rotated point cloud.
draw_geoms_list = [mesh_coord_frame, pcd_visible, pcd_hidden]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

上面旋转点云的可视化清楚地说明了隐藏点移除操作的工作原理。 所以现在,为了从这个汽车点云中移除所有“隐藏”点,我们可以依次执行这个隐藏点移除操作,方法是将点云围绕所有三个轴从 -90 度到 +90 度轻微旋转。 在每次隐藏点移除操作之后,我们可以聚合点索引的输出列表。 在所有隐藏点移除操作之后,聚合的点索引列表将包含所有未隐藏的点(即点云外表面上的点)。 以下代码执行此顺序隐藏点删除操作:

# Defining a function to rotate a point cloud in X, Y and Z-axis.
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

# Defining a function to get the camera and radius parameters for the point cloud
# for the hidden point removal operation.
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

# Defining a function to perform the hidden point removal operation on the
# point cloud using the camera and radius parameters defined earlier.
# The output is a list of indexes of points that are not hidden.
def get_hpr_pt_map(pcd, camera, radius):
    _, pt_map = pcd.hidden_point_removal(camera, radius)    
    return pt_map
# Performing the hidden point removal operation sequentially by rotating the
# point cloud slightly in each of the three axes from -90 to +90 degrees,
# and aggregating the list of indexes of points that are not hidden after
# each operation.

# Defining a list to store the aggregated output lists from each hidden
# point removal operation.
pt_map_aggregated = []

# Defining the steps and range of angle values by which to rotate the point cloud.
theta_range = np.linspace(-90, 90, 7)

# Counting the number of sequential operations.
view_counter = 1
total_views = theta_range.shape[0] ** 3

# Obtaining the camera and radius parameters for the hidden point removal operation.
camera, radius = get_hpr_camera_radius(pcd)

# Looping through the angle values defined above for each axis.
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}.")

            # Rotating the point cloud by the given angle values.
            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)
            
            # Performing the hidden point removal operation on the rotated
            # point cloud using the camera and radius parameters defined above.
            pt_map = get_hpr_pt_map(pcd_rotated, camera, radius)
            
            # Aggregating the output list of indexes of points that are not hidden.
            pt_map_aggregated += pt_map

            view_counter += 1

# Removing all the duplicated points from the aggregated list by converting it to a set.
pt_map_aggregated = list(set(pt_map_aggregated))

在这里插入图片描述

让我们再次裁剪点云,看看属于汽车内部的点。

# Cropping the point cloud of visible points using bounding box defined
# earlier to remove its right half (positive Z-axis).
pcd_visible_cropped = pcd_visible.crop(bbox_cropped)

# Cropping the point cloud of hidden points using bounding box defined
# earlier to remove its right half (positive Z-axis).
pcd_hidden_cropped = pcd_hidden.crop(bbox_cropped)

# Visualizing the cropped point clouds.
draw_geoms_list = [mesh_coord_frame, pcd_visible_cropped, pcd_hidden_cropped]
o3d.visualization.draw_geometries(draw_geoms_list)

在这里插入图片描述

从上面隐藏点去除操作后裁剪点云的可视化,我们看到所有属于汽车模型内部的“隐藏”点(红色)现在都与“可见”点分开了 点云的外表面(蓝色)。

4、将点云转换为数据帧

正如人们所预料的那样,点云中每个点的位置可以由三个数值定义——X、Y 和 Z 坐标。 然而,回想一下在上一节中,我们还估计了 3D 网格中每个点的表面法线。 当我们从这个网格中采样点来创建点云时,点云中的每个点还包含三个与这些表面法线相关的附加属性——X、Y 和 Z 方向上的法线单位矢量坐标。

因此,为了将点云转换为数据帧,点云中的每个点都可以由以下七个属性列表示在一行中:

  • X 坐标(float)
  • Y 坐标(float)
  • Z 坐标(float)
  • X方向的法向量坐标(float)
  • Y方向的法向量坐标(float)
  • Z方向的法向量坐标(float)
  • 点可见(布尔值 True 或 False)

可以通过运行以下代码行将点云转换为数据帧(Data frame):

# Creating a dataframe for the point cloud with the X, Y & Z positional coordinates
# and the normal unit vector coordinates in the X, Y & Z directions of all points.
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"]
                     )

# Adding a column to indicate whether the point is visible or not using the aggregated
# list of indexes of points from the hidden point removal operation above.
pcd_df["point_visible"] = False
pcd_df.loc[pt_map_aggregated, "point_visible"] = True

这将返回如下所示的数据框,其中每个点都是由上述七个属性列表示的一行。

在这里插入图片描述

5、保存点云和数据帧

现在可以通过运行以下代码行来保存点云(隐藏点移除前后)和数据帧:

# Saving the entire point cloud as a .pcd file.
pcd_save_path = "data/3d_model.pcd"
o3d.io.write_point_cloud(pcd_save_path, pcd)

# Saving the point cloud with the hidden points removed as a .pcd file.
pcd_visible_save_path = "data/3d_model_hpr.pcd"
o3d.io.write_point_cloud(pcd_visible_save_path, pcd_visible)

# Saving the point cloud dataframe as a .csv file.
pcd_df_save_path = "data/3d_model.csv"
pcd_df.to_csv(pcd_df_save_path, index=False)

在这里插入图片描述

就是这样! 希望这个教程让你对如何在 Python 中处理 3D 数据有所了解!


原文链接:Open3D模型处理实战 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535926.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Netty核心组件模块(二)

1.EventLoop组件 1.1.ChannelHandlerContext 1>.保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象; ChannelHandlerContext底层真实的类型为:DefaultChannelHandlerContext! 2>.即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时…

GEE遥感云大数据林业应用典型案例实践及GPT模型应用

目录 一 平台及基础开发平台 二 GEE基础知识与ChatGPT等AI模型交互 三 重要知识点微型案例串讲与GPT模型交互演示 四 典型案例综合演练 更多推荐 聚焦目前遥感应用最热门领域之一的林业,重点结合典型应用案例综合展示GEE云平台的使用技巧和强大功能&#xff0c…

Spring Aop以及SpringBoot统一功能的处理

一.SpringAop 1.SpringAop是一种思想,指的是对使用比较多的功能进行统一处理,比如我们在写博客系统项目,当我们在登录博客列表页和博客详情页以及博客编辑页的时候的时候,都需要写代码进行登录验证,这时候代码就比较繁…

linux内核篇-内存管理(虚拟内存和物理内存、进程虚拟内存布局、内存映射)

主要包括虚拟内存和物理内存、进程内存空间、用户态和内核态的内存映射。 分段机制 分段机制比较符合逻辑,比如可以把程序分成代码段、全局变量段、堆栈段等。 分段的虚拟地址主要包含段选择因子和段内偏移。段选择子就保存在段寄存器中,段选择子中有…

【资料分享】低速数字输入电路

1、方案设计:单通道、单向、反相器 该电路采用单通道,单向光耦,只支持漏型输入,电路的输入端压差满足24V DC10%(21.6V DC-26.4V DC),输出端电压在0~3.3V范围摆动。 1.1关键技术规格 1.2具体原理图 1.3电路原理详解 …

数字图像处理-基础

数字图像处理-基础 文章目录 一、闲谈二、人类视觉系统三、光和电磁波谱四、图像感知与获取五、图像取样与量化5.1. 数字图像的表示5.2. 空间和灰度分辨率5.3. 图像内插5.3.1. 最近邻内插5.3.2. 双线性内插5.3.3. 双三次内插 六、像素间的关系6.1. 相邻像素6.2. 邻接性、连通性…

【架构设计】阿里开源架构Cola4.0的项目实践:订单系统

项目介绍 使用SpringBootMybaitsPlusCola(整洁面向对象分层架构)4.0重构订单功能 项目地址 Gitee:https://gitee.com/charles_ruan/smile-cola Github:https://github.com/charles0719/smile-cola 项目核心API 新增 POST http:…

华为OD机试真题 Java 实现【整理扑克牌】【2023Q1 100分】

一、题目描述 给定一组数字,表示扑克牌的牌面数字,忽略扑克牌的花色,请按如下规则对这一组扑克牌进行整理: 步骤1 对扑克牌进行分组,形成组合牌,规则如下: 当牌面数字相同张数大于等于4时&a…

【FPGA】Verilog:锁存器 Latch | RS Flip-Flop 与 D Flip-Flop 的实现

💭 写在前面:本章将理解 RS/D 锁存器的概念,了解 RS/D/JK 触发器的概念,使用 Verilog 实现各种锁存器 (Latch) 和翻转器 (Flip-Flop),并通过 FPGA 验证用 Verilog 的实现。 📜 本章目录: Ⅰ. …

Java中synchronized的优化

本文介绍为了实现高效并发,虚拟机对 synchronized 做的一系列的锁优化措施 高效并发是从 JDK5 升级到 JDK6 后一项重要的改进项,HotSpot 虚拟机开发团队在 JDK6 这个版本上花费了大量的资源去实现各种锁优化技术,如适应性自旋(Ada…

【fly-iot飞凡物联】(6):通过docker镜像使用gitbook启动ActorCloud项目文档,发现是个IOT功能非常丰富的项目,可以继续研究下去。

目录 前言1,关于 ActorCloud 使用手册2,使用docker 构建文档4,或者使用别人的gitbook镜像5,总结 前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/108971807 fly-iot飞凡物联专栏: https://…

含sop的配电网重构(含风光|可多时段拓展)

目录 1 主要内容 2 部分程序 3 下载链接 1 主要内容 之前分享了很多配电网重构的程序,每个程序针对场景限定性比较大,程序初学者修改起来难度较大,本次分享一个基础程序,针对含sop的配电网重构模型,含风电和光伏&…

skywalking安全认证问题

skywalking安全认证 一、问题二、步骤2.1 skywalking-aop配置文件修改2.2 agent配置文件修改 一、问题 在springboot项目使用java-agent接入skywalking时,为保证两者之间的数据安全传输,准备加个安全认证 参考文章: https://www.helloworld…

尝试探索水下目标检测,基于yolov5轻量级系列模型n/s/m开发构建海底生物检测系统

其实,水下目标检测相关的项目早在之前就已经做了几个了,但是没有系统性地对比过,感兴趣的话可以先看下之前的文章,如下: 《基于自建数据集【海底生物检测】使用YOLOv5-v6.1/2版本构建目标检测模型超详细教程》 《基于…

Qt编写视频监控系统73-不同视频流不同类型的判断和解析(http/m3u8/rtsp/rtmp等)

一、前言 这套视频监控系统大概从2018年起步整体框架,一步步积累到现在,中间经历了无数次的各种视频文件、视频流、视频设备的播放测试,比如光视频文件就有mp4/wmv/rmvb/mkv/avi等格式,视频设备有本地USB摄像头、桌面等&#xff…

【k8s】【ELK】【zookeeper+kafka+efak】日志环境部署

1、日志收集基本概念 k8s中pod的路径: containers log: /var/log/containers/*.log Pod log: /var/log/pods docker log: /var/lib/docker/containers/*/*.log如何收集日志 使用 EFKLogstashKafka 1、filebeat读取容器中的日志,然后写入K…

camunda如何发布和调用rest服务接口

一、camunda如何发布rest服务接口 Camunda BPM 平台本身提供了 REST API 接口,可以用于管理和操作 Camunda 平台中的各种资源和数据,如流程定义、流程实例、任务等。因此,我们可以通过编写 Camunda 应用程序的方式,将 Camunda RE…

Ubuntu系统远程桌面安装运行记录

Ubuntu系统远程桌面安装运行记录 分别测试了20.04和22.04两个版本 一、widows远程 参考连接https://blog.csdn.net/qq_50263172/article/details/128465149 安装步骤如下: sudo apt-get install xrdpsudo apt install tightvncserversudo apt-get install xubuntu-…

CSS基础-超详解

目录 什么是CSS? CSS的引入方式 内部样式 外部样式 内联样式 CSS选择器 CSS标签选择器 类选择器 id选择器 通配符选择器 CSS属性设置 字体 文本 什么是CSS? CSS也被叫做层叠样式表, 英文全称为: Cascading Style Sheets, 是一种用来表现HTML(标准通用标记语言的一个应用)…

【云原生】k8s管理工具--Kubectl(二)

k8s管理工具--Kubectl(二) 一、项目生命周期1、项目生命周期2、创建kubectl run命令3、发布kubectl expose命令(1) Service的作用(2)Service的类型(3)查看Pod网络状态详细信息和Serv…