将机器学习应用于3D数据并不像应用于图像那样简单。3D数据有很多表示形式,但选择决定了您可以使用哪种学习策略。在本文中,我将介绍一个特别有趣的策略(至少对我来说是😆),叫做MeshCNN:一个有优势的网络。本文描述了一个用于处理3D模型的分类和分割任务的通用框架。也许它最有趣的特性是它的网格池化(mesh pooling)操作,它使我们能够在多个尺度上组合一个网格的特征(类似于视觉CNN)。这是一种学习操作,它逐渐将模型缩减到对给定任务提供最多信息的边缘。MeshCNN 结合了每个流行 3D 表示的许多最佳属性。然而,在我们详细介绍之前,让我们通过对 3D 表示的简要回顾来了解这些属性是什么。
3d数据表示
什么是表示深度学习的3D网格的最佳方法?与2D RGB图像不同,对于最佳表示方式没有共识。这个问题很难回答,因为表征的选择决定了我们必须采取的学习方法。对于分类示例,可以将模型从3D空间投影到2D图像中,并应用标准2D卷积。您可以将模型所占据的3D空间表示为体素网格,允许您应用3D卷积。你可以简单地采样网格的顶点作为一个3D点云,并应用专门的方法,如PointNet++或3D点胶囊网络。甚至还有像PolyGen这样的方法可以直接处理模型的顶点和面,我在之前的文章中用PolyGen和PyTorch生成3D模型中提到过。
投影
一些早期的3D深度学习研究直接绕过了3D表示问题,而简单地将3D模型投射到2D图像中。这使得3D数据符合经典的CNN视觉方法。例如,在3D形状识别的多视图卷积神经网络中描述的方法将一个模型投射到12个独特的视点,并将它们的激活集合起来,以产生一个softmax评分。在ModelNet40数据集上,他们报告了90.1%的可靠分类精度。根据项目网站的数据,ModelNet40数据集目前的最佳性能是97.37%,这来自于一篇名为RotationNet的论文:使用无监督视点的多视图进行联合对象分类和姿态估计。类似于上一篇论文,它对多个视图进行训练,预测一个对象类别,但同时也预测视点,将其作为一个潜在变量。它能够有效地预测对象类和视点,甚至对现实世界的对象。
虽然简单而优雅,但是投影表示a)没有考虑到模型的完整拓扑,b)对模型应该如何看待做出了假设,c)没有为非全局任务(如分割)提供一个直接的解决方案。
体积元素
体素方法在不牺牲卷积的情况下解决了投影方法的许多问题。通过将3D空间划分为箱,创建一个密集的占用网格,如果它在模型中,则为每个单元格分配一个布尔值。网格布局很容易应用3D卷积。最早使用这种表示的论文可能是3D ShapeNets: A Deep Representation for Volumetric Shapes,这篇论文实际上介绍了ModelNet40数据集。在这项研究中,他们试图通过从单一深度图像中预测3D体素来恢复物体的3D结构,并取得了相当显著的结果。
从3D体素分类是另一回事。虽然您可以获得不错的结果,但缺点是在空白空间上有过多的卷积,因为空间的占用非常稀疏。最重要的是,模型分辨率按三次缩放权重的数量,因此在大多数情况下即使是在显著利用空间的稀疏性时,大于 256x256x256 的大小是不切实际的。例如 OctNet: Learning Deep 3D representation at High resolution在这篇论文中,他们在 ModelNet40 上达到了大约 86% 的准确率,并且在速度和内存消耗方面有了很大的改进。高分辨率体素空间成本高昂,但低分辨率体素空间压缩了模型拓扑的潜在有用点云细节
很多方法都是直接处理向量点云。例如,在2019年的论文《Spherical Kernel for Efficient Graph Convolution on 3D Point Clouds》中,作者使用他们的方法在ModelNet40任务上实现了89.3%的精度。3D点云表示的好处是它的多功能性,因为从激光雷达扫描到编写的3D模型都可以表示为3D点云。即使是经典的PointNet和PointNet++模型也能在分类任务上取得很好的结果(本文中88.0%)。缺点是大多数点云方法完全不考虑表面。没有面,网格的真实拓扑是不可能知道的,因为任何向量集合都不能唯一地定义一组面。
这头骆驼的膝盖非常接近,如果不了解模型表面,它们可能无法分离
ModelNet40 上表现最好的方法之一来自一种名为 Relation-Shape Convolutional Neural Network for Point Cloud Analysis 的方法,它的准确率达到了 93.6%。这种名为 RS-CNN 的方法试图从几何先验推断给定点云的底层拓扑结构,从而赋予模型对其输入点的空间感知能力。该模型具有出色的性能,可应用于点云和网格。然而,即使网格信息可用,它也没有利用网格信息的机制。
MeshCNN
有没有一种方法可以直接研究网格,而不牺牲有价值的拓扑信息,承受体素的计算代价,或对如何查看它做出假设?MeshCNN提出的解决方案是将三维模型作为一个图或流形来共同研究顶点及其连接或边。该方法定义了卷积和池化层在三维网格的边缘,允许我们或多或少地使用卷积神经网络的标准工具集。最终,他们能够在来自SHREC 11数据集的30个类上达到98.6%的精度(他们不报告ModelNet40的精度),并且在对象部件和人体数据集上具有令人印象深刻的分割性能。
网格池化操作的描述[来自MeshCNN论文的图2]。a)三角形网格的一条给定边(红色)恰好有4个相邻边(蓝色)。b)池化操作通过合并这条边的两个顶点来溶解这条边,这两个顶点依次合并溶解边两边的边对,c)得到两条边。
任何水密三维网格的边缘都恰好发生在两个面上(边界或非流形边缘除外)。如果网格是三角形的(即它的所有面都有3条边),那么连接到任何边的两个关联面总接触到5条边。这种一致性使得三角形网格对于机器学习技术来说特别方便。由于这个原因,MeshCNN假设我们的模型都是三角形,流形网格。典型的三维重建方法如摄影测量、结构光扫描、激光扫描等产生三角形网格,因此该方法可以直接适用于此类数据。创建的网格,通常包含四边形或n-gons,通常可以转换为三角形网格,在Maya或Blender中点击几下。
如果我们可以定义一个卷积在一个边缘和它的4个邻居,我们随后可以建立一个卷积神经网络来处理整个网格。现在的挑战是定义一组具有以下属性的操作:
- 对顶点或边的顺序(局部或全局)必须不变。
- 必须是不变的相似变换(即网格平移,旋转和缩放)。
- 必须传达一个给定的顶点或边的关系到它的邻居和它在网格的全局结构中的位置。
网格卷积(Mesh Convolution)
首先我们来看看卷积运算本身。给定一条边和4个邻边,每个邻边都有自己的特征,卷积需要对这些边的顺序保持不变。本文采用的简单方法是用对称函数卷积。他们处理一对相对的边(如a,c和b,d),相对于中心边e顺时针排列,并取它们的有限和和差
边缘及其邻域(左)[从MeshCNN论文的图4中]。边a,c(红色)和边b,d(蓝色)是对立的对。需要注意的是,顺时针符号表示边对的严格顺序,即a总是在b之前,c总是在d之前。给定边(右)的输入特征也被设计为对边的顺序不变。
等变特征组合公式来自MeshCNN论文]
#Simplifiedfrommodels/layers/mesh_conv.pyinranahanocka/MeshCNNclassMeshCov(nn.Module): def__init__(self, in_c, out_c, k=5, bias=True): super(MeshConv, self).__init__() self.conv=nn.Conv2d(in_channels=in_c, out_channels=out_c, kernel_size=(1, k), bias=bias) defforward(self, x) """Forward pass given a feature tensor x with shape (N, C, E, 5):N - batchC - # featuresE - # edges in mesh5 - edges in neighborhood (0 is central edge)"""x_1=x[:, :, :, 1] +x[:, :, :, 3] x_2=x[:, :, :, 2] +x[:, :, :, 4] x_3=torch.abs(x[:, :, :, 1] -x[:, :, :, 3]) x_4=torch.abs(x[:, :, :, 2] -x[:, :, :, 4]) x=torch.stack([x[:, :, :, 0], x_1, x_2, x_3, x_4], dim=3) x=self.conv(x) returnx
输入的特征
然而,我们还没有解决一个关键问题。我们从什么特性开始?在应用第一次卷积之前,我们必须创建一个类似于二维图像中的RGB通道的输入特征表示。为此,作者简单地为每个面定义了二面角(两个相邻面之间的角)、对称对顶角(相对角的角,排序以保持顺序不变性)和两个边长比(每个三角形的高/基比,也排序),总共为5个输入特征。
#Frommodels/layers/mesh_conv.pyinranahanocka/MeshCNNdefdihedral_angle(mesh, edge_points): """Angle between two faces connected to edge."""normals_a=get_normals(mesh, edge_points, 0) normals_b=get_normals(mesh, edge_points, 3) dot=np.sum(normals_a*normals_b, axis=1).clip(-1, 1) angles=np.expand_dims(np.pi-np.arccos(dot), axis=0) returnanglesdefsymmetric_opposite_angles(mesh, edge_points): """Angles of opposite corners across edge."""angles_a=get_opposite_angles(mesh, edge_points, 0) angles_b=get_opposite_angles(mesh, edge_points, 3) angles=np.concatenate((np.expand_dims(angles_a, 0), np.expand_dims(angles_b, 0)), axis=0) angles=np.sort(angles, axis=0) returnanglesdefsymmetric_ratios(mesh, edge_points): """Height/base ratios of the triangles adjacent to edge."""ratios_a=get_ratios(mesh, edge_points, 0) ratios_b=get_ratios(mesh, edge_points, 3) ratios=np.concatenate((np.expand_dims(ratios_a, 0), np.expand_dims(ratios_b, 0)), axis=0) returnnp.sort(ratios, axis=0)
网格池化(Mesh Pooling)
最后,让我们看看池化操作。池化只是将这条边的两个顶点合并在一起,将给定邻域的5条边折叠成2条。这两条新边的特征仅仅是它们原来的边的特征的平均值。例如,在前面的图中,我们的两条新边的特征是avg(a,b,e)和avg(c,d,e)。
但是我们如何决定哪些边要折叠呢?每个网格池层都实例化一个要保持的目标边数(在代码中使用——pool_res参数)。网格池层只是根据边缘特征的平方大小对边缘进行排序。然后迭代折叠网格边缘,直到达到目标边缘数。
网格池化学习保留对给定任务最有信息的边[从MeshCNN论文的图1中]。“有把手或无把手”任务(顶部)的池化保留了花瓶中的把手,而对于“有颈或无颈”任务,它保留了花瓶的颈部。
由于网格池化操作是可学习的,它赋予模型自由学习优化给定任务的权重。还有一个网格解池操作可以恢复池化,这对于分割任务是必不可少的。这意味着网络必须跟踪在 U-Net 风格分割网络的编码器阶段完成的池化操作。MeshCNN 通过跟踪网格的 history_data 属性中的边缘折叠操作来做到这一点。
网络结构
网络结构由MResConv层组成,每个层由一个初始网格卷积(MeshConv)、几个连续的ReLU+BatchNorm+MeshConv层和一个残差连接和另一个ReLU组成。在任务层结束之前,网络遵循多次MResConv+Norm+MeshPool的模式。对于分类,任务层是简单的全局平均池,然后是两个完全连接的层。分割网络是一个U-Net风格的编码器-解码器。
总结
等变卷积运算、不变的输入特征和学习到的网格池运算使MeshCNN成为一个特别有趣的模型,它有以下几个主要优点:
比旧方法更高效,参数更少。
利用网格的拓扑结构(即顶点和面信息),而不是将其视为点云。
网格卷积保留了卷积的便利性质,但允许应用于图形数据。三维网格的5个输入特征类似于输入图像的RGB特征。
对旋转、平移和缩放不变性(见论文第5.4节)。
网格池化(即学习到的边折叠)允许网络通过将5条边折叠成2条边并同时分解两个面来学习特定任务的池化。
如果您渴望亲自尝试它,我鼓励您查看作者提供的MeshCNN源代码(https://github.com/ranahanocka/MeshCNN)。它是用PyTorch编写的,并提供了许多有用的脚本,用于在本文中讨论的各种数据集上尝试该模型。我发现它非常干净和易于使用,所以如果你好奇,它值得一看。一如既往,感谢您的阅读和继续学习!