赞
踩
【版权声明】
本文为博主原创文章,未经博主允许严禁转载,我们会定期进行侵权检索。
更多算法总结请关注我的博客:https://blog.csdn.net/suiyingy,或”乐乐感知学堂“公众号。
本文章来自于专栏《Python三维模型处理基础》的系列文章,专栏地址为:https://blog.csdn.net/suiyingy/category_12462636.html。
根据上一篇博文所介绍的三角面片内容,三角面方向性的另一个重要影响是体积计算。很多软件或者体积计算库,将三维模型体积称为有向体积,计算结果有正负之分。例如,就一个单独的三角面来说,如果不指明方向,那么正面或者反面构成的空间都可以计算体积,这样可能带来计算歧义。我们所希望计算的模型体积实际上是背面围绕而成的空间体积,下面会提供详细的介绍示例。因此,在计算三维模型体积过程中,我们需要注意以下两点:
(1)物体表面法向量方向一致,均指向外部,从而对建模时的三角面片的顶点顺序进行相应约束。
(2)大部分时候需要表面是封闭的,否则仍然无法确定体积计算空间。
针对第一点,如果物体表面法向量方向不一致,体积计算过程会计算各个空间的有向体积,积分或求和后的总体积很有可能比实际体积小很多,而绝对值积分或求和后的总体积则很有可能比实际体积大很多。我们仍然以一个三棱柱为例,如果一个面的背面朝外,那背面会对应一个比较大的空间。但这个空间并不好定义,并且其体积计算方法会因软件不同而异,可能会以坐标原点为参考计算出一个负的有向体积,也可能根据封闭性自动调整并计算出一个正确的体积。
针对第二点,当一个物体表面不是封闭的时候,即表面存在孔洞时,程序无法完全确定所需要计算的体积空间,因而会根据自身算法得到一个相对合理的结果,并且这种结果很可能不是我们所需要的。CloudCompare遇到不封闭的体积计算时,会提示表面不是封闭的,计算结果可能不准确,具体内容为“[Mesh Volume] The above volume might be invalid (mesh has holes)”。一些Python体积计算库在表面不封闭时,会直接报错,从而无法计算体积。当有一个多余额外的三角面与物体表面的一个三角面完全重叠的时候,这个多余的表面也会引起不封闭问题。这在手动建模的时候可能会遇到,需要及时检查并进行调整优化。本专栏后续博文也会详细介绍一些手动建模的方法和示例。
图1 CouldCompare提示封闭性问题
三维模型体积计算的基本原理主要包含两类,体素法和积分法。体素法是指将三维空间划分成一些列比较小的单位体素,根据模型占据的体素数量来计算模型体积。显然,单位体素尺寸越小,三维模型体积计算结果越准确。积分法则是将模型划分成一些列比较小的子模型,子模型通过常规几何计算即可得到体积,然后将所有体积进行求和得到总体积。我们可以通过python库或者软件直接计算模型体积,下面将分别介绍cloud compare、timesh和open3d中体积计算方法。
以下体积计算时采用的模型为三棱柱模型,其顶点和三角网格面分别为[[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 5.0], [3.0, 0.0, 5.0], [0.0, 4.0, 5.0]]、[[1, 3, 2], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]]。三棱柱示意图如下所示,底面是一个直角三角形,其体积可使用底面积乘以高来进行计算。体积为1/2 * 3 * 4 * 5 = 30。
图2 三棱柱示意图
cloud compare体积计算步骤:首先加载并选中mesh模型,然后依次点击Edit->Mesh->Measure volume,如下图所示。
图3 CouldCompare体积计算过程
体积计算结果会显示在下方提示区域,如下图所示。体积计算结果为V=30,与上述手动计算结果一致。
图4 CouldCompare体积计算结果
我们现在将两个底面的顶点顺序进行改变,即[[1, 2, 3], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 6, 5]],从而使朝向发生改变,然后使用上面步骤进行体积计算,其结果如下图所示。此时体积计算结果已变成V=10。再改变一个侧面顶点顺序后([[1, 2, 3], [3, 2, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 6, 5]])的体积计算结果仍然为10。
图5 底面顺序调整后CouldCompare体积计算结果
我们另外再测试将其中一个底面删掉后([[2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]])进行体积计算。此时软件会提示“[Mesh Volume] The above volume might be invalid (mesh has holes)”,即体积计算结果可能无效(网格存在孔洞)。但是,体积计算结果仍然为V=30,结果是正确的。再删掉另外一个底面后([[2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4]])体积计算结果为V=20,此时结果不再正确,并且仍然提示存在孔洞。
图6 删掉一个底面后CouldCompare体积计算结果
trimesh求解体积直接使用mesh.volume即可,也可在计算体积之前使用mesh=mesh.convex_hull,这样可以得到点云凸包体积。原始三棱柱([[1, 3, 2], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]])计算得到的体积和凸包体积均为30。两个底面顶点顺序改变后([[1, 2, 3], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 6, 5]])的三棱柱的体积和凸包体积也均为30。再改变一个侧面顶点顺序后([[1, 2, 3], [3, 2, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 6, 5]])的三棱柱的体积和凸包体积则均为-10。删掉一个底面后([[2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]])的三棱柱的体积和凸包体积也均为30。再删掉一个底面后([[2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4]])的三棱柱的体积和凸包体积也均为30。这里删掉两个底面后的体积计算结果与cloud compare是不一致的。在这个例子中,模型体积和凸包体积是相等的,但这并不是一定的。如果模型表面有凹进去的部分,那么这两种体积计算结果会有所差异。
详细Python示例程序下载地址为“https://download.csdn.net/download/suiyingy/88433633”,或者在”乐乐感知学堂“內回复”3d处理基础“即可。
- vertices = np.array([[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 5.0], [3.0, 0.0, 5.0], [0.0, 4.0, 5.0]])
- faces = np.array([[1, 3, 2], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]])
- faces = faces - 1 # 减1是因为索引从0开始
- mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
- volume = mesh.volume
- print('volume result: ', volume)
- mesh = mesh.convex_hull
- print('convex hull volume result: ', volume)
open3d中计算体积的函数为open3d.geometry.TriangleMesh.get_volume (Python method, in open3d.geometry.TriangleMesh)。原始三棱柱体积计算结果为30。更改两个底面顶点顺序之后的体积计算结果为10,再改变一个侧面顶点顺序后的体积计算结果仍为10。删除1个或两个底面后计算体积直接报错,无法进行计算,报错信息为“TriangleMesh.cpp:1201: The mesh is not watertight, and the volume cannot be computed.”,即网格不是封闭的并且体积无法计算。
详细Python示例程序下载地址为“https://download.csdn.net/download/suiyingy/88433633”,或者在”乐乐感知学堂“內回复”3d处理基础“即可。
- vertices = np.array([[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 5.0], [3.0, 0.0, 5.0], [0.0, 4.0, 5.0]])
- faces = np.array([[1, 3, 2], [2, 3, 5], [5, 3, 6], [1, 4, 3], [3, 4, 6], [1, 2, 4], [2, 5, 4], [4, 5, 6]])
- faces = faces - 1 # 减1是因为索引从0开始
- # 创建TriangleMesh对象
- mesh = o3d.geometry.TriangleMesh()
- # 设置顶点和面片
- mesh.vertices = o3d.utility.Vector3dVector(vertices)
- mesh.triangles = o3d.utility.Vector3iVector(faces)
- # 计算三角网格的体积
- volume = mesh.get_volume()
- # 输出体积结果
- print("volume:", volume)
以上三种方法体积计算结果如下图所示。在表面朝向一致并且封闭的情况下,三种方法都能够计算得到正确的结果,相反,三种方法的计算结果可能会存在差异。因此,在三维模型体积计算过程中,我们需要确认模型的表面网格是不是正确。网格是否封闭可直接通过cloudcompare或open3d的报错信息来验证,而网格朝向问题需要在可视化软件和程序中进行检查,或者分别计算三角面片表面法向量与中心向量直接的夹角以进行初步判断。下一篇博文我们将补充法向量计算,并详细介绍三维模型的表面积计算与注意事项。
图7 三种体积计算方法对比
积分法主要是将三维模型划分若干子模型,而子模型可近似采用公式直接进行计算。模型体积基本计算公式为底面积乘以高。当底面是不规则轮廓时,其面积可以通过投影到图像上的有效像素点来进行计算,也可使用其它图像库来计算轮廓面积。上面示例三棱柱是一个规则的模型,不进行子模型划分也可直接计算出体积结果。这里,我们仍然假设对齐在高度方向划分成1000份,然后计算其体积,结果为30.035。详细Python示例程序下载地址为“https://download.csdn.net/download/suiyingy/88433633”,或者在”乐乐感知学堂“內回复”3d处理基础“即可。不规则的三维模型体积计算也可参照该方法。
自定义积分法得到的体积结果通常是一个近似体积。为了快速得到近似体积,我们也可采用上文trimesh中凸包的体积作为近似。
在三维模型体积计算过程中,我们需要注意以下两点:
(1)方向性:物体表面的法向量方向应一致且指向外部,以确保计算得到的体积是背面围绕而成的空间体积。如果法向量方向不一致,会导致计算结果偏小或偏大。
(2)封闭性:大多数情况下,模型表面应该是封闭的,如果存在孔洞或缺失的面片,无法确定体积计算空间,可能导致计算结果不准确。需要注意检查和修复模型的封闭性问题。
另外,体积计算方法主要包括体素法和积分法。体素法将空间划分成小立方体单位进行计算,而积分法则将模型划分成子模型通过几何计算得到体积,然后累加得到总体积。常用的库如cloud compare、trimesh和open3d都提供了体积计算的函数或方法。
自定义积分法也是一种计算体积的方法,可以通过划分子模型并使用适当的公式近似计算体积。这种方法适用于不规则轮廓的底面,可以使用图像库计算轮廓面积或采用其他方法计算底面积。需要注意的是,自定义积分法得到的体积结果通常是一个近似值,而且对于复杂形状的模型计算较为复杂。如果只需要快速得到一个近似体积,可以使用凸包的体积作为近似值。
综上所述,三维模型体积计算涉及到方向性、封闭性和选择合适的计算方法。正确处理这些注意事项可以获得准确的体积结果。
【版权声明】
本文为博主原创文章,未经博主允许严禁转载,我们会定期进行侵权检索。
更多算法总结请关注我的博客:https://blog.csdn.net/suiyingy,或”乐乐感知学堂“公众号。
本文章来自于专栏《Python三维模型处理基础》的系列文章,专栏地址为:https://blog.csdn.net/suiyingy/category_12462636.html。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。