3D理解在从自动驾驶汽车和自主机器人到虚拟现实和增强现实的众多应用中发挥着至关重要的作用。在过去的一年里,PyTorch3D已经成为一个越来越流行的开源框架,用于使用Python进行3D深度学习。值得庆幸的是,PyTorch3D 库背后的人员已经完成了实现几个常见的 3D 运算符、损失函数和可微渲染 API 的繁琐工作,使 PyTorch3D 更易于访问,更容易开始使用。
推荐:用 NSDT编辑器 快速搭建可编程3D场景
一些关键的 PyTorch3D 组件包括:
- 用于存储和操作三角形网格的数据结构
- 三角形网格的高效操作
- 可微分网格渲染接口
渲染是计算机图形管道中必不可少的构建块,可将 3D 表示 --- 无论是网格 (.obj) 还是点云 (.ply)--- 转换为 2D 图像。
在这篇文章中,我们将构建有关如何从各种角度渲染 3D .obj 文件以创建 2D 图像的背景知识,如果你需要渲染其他格式的3D模型,可以使用NSDT 3DConvert在线工具将其 转换为.obj模型;我们还将使用 Python 中的 PyTorch3D 构建一个基本的 3D 渲染管线,组件如下所示。
这篇文章只假设对 3D 文件表示有基本的了解,所以希望每个人都可以访问它:)但是,如果你想了解有关3D重建的更多信息,请查看这个神奇的最新资源列表或斯坦福CS231A 和CS468的课程笔记。
在本文结束时,你将了解如何:
- 使用 .obj 和 .mtl 文件加载 3D 网格
- 创建渲染器
- 渲染网格
- 可选:使用批处理属性从不同视点高效渲染网格
只是想要代码?整个代码在此 GitHub 存储库 中可用。
让我们开始吧!🎉
1、导入库并初始化参数
我们首先从 pytorch3d 库中导入先决条件库,例如 torch 或 numpy,以及各种实用程序函数和结构。
最后,第 43 行从 utils.py 导入类参数,从配置文件加载重要的超参数。通常,最好将所有参数写入单个文件并从此特定文件加载它们。这允许你跟踪正在测试的超参数,并查看哪些超参数导致了最佳性能。在我们的例子中,超参数存储在 params_demo.json 中:
如果不理解其中一些参数的意义,不要担心,我将在本教程的后面部分解释它们!
加载超参数通过以下方式完成:
params = Params("params_demo.json")
# Access batch size parameter
print(params.elevation)
初始化 params 对象后,你还可以使用 params.update('your_other_params.json') 方法使用另一个 .json 文件更新它。
好的,现在我们导入了库并声明了参数,可以加载网格了。🎉
2、加载3D网格
有几种方法可以表示 3D 数据,例如点云、网格或体素 。在本教程中,我们将重点介绍 3D 网格,尽管 PyTorch3D 中的相同过程也适用于点云。
有关 3D 纹理网格的信息通常存储在以下文件中:
- .obj 文件,用于存储顶点和面
- .mtl 文件,用于存储材料属性
- .jpg或.png纹理图像
在本教程中,我们将使用存储在 data/capsule文件夹中的 3D 胶囊对象。示例文件是从此处托管的公共存储库获得的。为了可视化我们正在使用的网格,我们可以使用 Blender:
PyTorch3D 包含多个用于加载 .obj 文件的函数,例如 load_obj 或 load_objs_as_meshes。我们将使用第一个,并使用以下语句加载 .obj 文件:
verts, faces, aux = load_obj(filename)
在这里, verts是顶点的 (V, 3) 张量,faces.verts_idx
是每个面顶点索引的 (F, 3) 张量, aux 存储有关网格的辅助信息,例如 UV 坐标、材质颜色或纹理。然后,我们将这些 verts、 faces.verts_idx和 aux传递到 Meshes 构造函数中,该构造函数创建一个名为 capsule_mesh 的对象:
最后,第 33 行检查胶囊网格中的面和顶点数。这将返回:
We have 5252 vertices and 10200 faces.
这是我们通过检查 .obj 文件结构所期望的。
如果你想了解更多,可以在这里找到 Meshes对象的官方文档。
3、创建渲染器
这可能是最重要的一步。现在我们成功读取了胶囊网格,我们需要使用 MeshRenderer 类创建一个渲染器。查看 MeshRenderer 的文档 ,我们看到它由 2 个组件组成:
- rasterizer:光栅器
- shader:着色器
因此,让我们将此任务分解为两个步骤,最后将它们放在一起。
3.1 创建光栅器
栅格化是指采用以多边形或三角形(.obj 文件)描述的图像表示,并将其转换为以像素(.png或.jpg文件)描述的光栅图像。
我们的光栅器是使用一个名为 MeshRasterizer 的类创建的,该类还具有多个子组件,例如 cameras和 raster_setting 参数。基本上,相机负责将3D坐标从世界空间转换为屏幕空间。要初始化相机,我们需要 3 个重要参数。这些是:1) 距离,2) 方位角和 3) 仰角。如果这听起来很多,请不要担心,我将逐步介绍它们。
- 距离( distance)是指相机与物体之间的距离。
- 仰角( elevation angle)是指从物体到相机的矢量与水平面y=0(平面xz)之间的角度。 elevation基本上指的是我们从多高的地方看物体。
- 方位角( azimuth angle)是指从物体到相机的矢量被投射到水平面y=0上。方位角是投影矢量与参考平面(水平面)上 (0, 0, 1) 处的参考矢量之间的角度。方位角取值范围为 0º 到 360º。它基本上告诉我们从哪一侧(例如左侧大小,右侧,前视图,后视图等)查看对象。
在我们的 params.json 文件中,我们声明距离为 3,仰角为 0,方位角为 90,因此如果我们渲染此网格,我们应该直接从 3 个单位的距离查看它。
关于栅格设置,最重要的参数是生成的 2D 图像的大小。尺寸越小,图像的像素化程度就越高。
3.2 创建着色器
PyTorch3D提供多种类型的着色器,包括 SoftPhongShader或 HardPhongShader。在这里,我们将使用预定义的 SoftPhongShader 并传入相机和要初始化默认参数的设备。
最后但并非最不重要的一点是,我们将光栅器和着色器结合起来:
4、渲染网格
这是一个非常简单的步骤,因为我们只需要在 Meshes 对象上调用渲染器方法。让我们渲染胶囊网格并绘制结果:
渲染的结果看起来与前面图中的 Blender 可视化几乎相同,这是一个好兆头!😃
5、可选:使用批处理属性
如果要从多个视点渲染网格,则使用批处理属性可能很有用。在我们深入研究代码之前,有必要了解当前的批处理实现是如何工作的。
当前实现依赖于单个参数,即批大小。然后,此批大小将仰角和方位角空间划分为 n 个相等的增量。因此,如果你的批大小为 4,那么你的仰角和方位角空间是 torch.linspace(0, 360, 4) 即张量 ([0, 120, 240, 360])。
在每个批次中,索引沿高程和方位角列表移动,并在所有元素耗尽后停止。结果,我们只得到 4 张渲染图片:a) 高度和方位角 = 0;b) 高度和方位角 = 120;c) 高度和方位角 = 240,d) 高度和方位角 = 360。
这类似于 Python的 map() 函数,其中你传递两个可迭代的参数 --- 你也不会从这两个参数的所有成对组合中获得结果。因此,如果你希望获得仰角和方位角的所有成对组合,那么像列表理解这样的东西是一种方法。
好了,回到批处理属性...我们将批大小设置为 4,表示要从中渲染网格的视点数。我们使用此批量大小来扩展网格、高程矢量和方位角矢量。渲染图像后,生成的张量具有形状 [4, 256, 256, 4]。