Ken Xiao
tech

饶蘑菇

November 21, 2025
import re  
  
  
def calculate_obj_volume(obj_file_path):  
    """  
    计算 OBJ 格式的 3D 模型文件的体积。  
  
    此函数假设 OBJ 文件表示一个封闭的、无孔洞的、流形的三角形网格。  
    它使用三角形的顶点坐标来计算每个三角形的带符号体积,并将它们相加以获得总的带符号体积。    总带符号体积的绝对值即为模型的体积。  
  
    Args:      obj_file_path: OBJ 文件的路径。  
  
    Returns:      模型的体积(浮点数)。如果文件无法打开或处理,则返回 None。  
    """  
    try:  
        with open(obj_file_path, 'r') as f:  
            lines = f.readlines()  
    except FileNotFoundError:  
        print(f"Error: File not found: {obj_file_path}")  
        return None  
  
    vertices = []  
    faces = []  
  
    for line in lines:  
        if line.startswith('v '):  
            # 顶点行 (v x y z)            vertex = [float(x) for x in line.split()[1:]]  
            vertices.append(vertex)  
        elif line.startswith('f '):  
            # 面行 (f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...)            # 只考虑顶点索引,忽略纹理和法线索引  
            face = []  
            for i in line.split()[1:]:  
                vertex_index = int(re.split(r'[/]', i)[0]) - 1  
                face.append(vertex_index)  
            # 处理非三角形面,将其转换为三角形  
            if len(face) == 3:  
                faces.append(face)  
            elif len(face) > 3:  
                for i in range(1, len(face) - 1):  
                    faces.append([face[0], face[i], face[i + 1]])  
  
    total_volume = 0  
    for face in faces:  
        v1 = vertices[face[0]]  
        v2 = vertices[face[1]]  
        v3 = vertices[face[2]]  
  
        # 计算四面体体积  
        volume = (  
                         v1[0] * (v2[1] * v3[2] - v3[1] * v2[2])  
                         - v2[0] * (v1[1] * v3[2] - v3[1] * v1[2])  
                         + v3[0] * (v1[1] * v2[2] - v2[1] * v1[2])  
                 ) / 6.0  
  
        total_volume += volume  
  
    return abs(total_volume)  
  
  
# 示例用法:  
obj_file = "C://Users//xfc05//Downloads//Untitled_Scan_10_32_59//textured_output.obj"  # 替换为你的 OBJ 文件路径  
volume = calculate_obj_volume(obj_file)  
  
if volume is not None:  
    print(f"The volume of the model is: {volume}")

The volume of the model is: 40274.703435695505


数学原理:

1. 三角剖分 (Triangulation):

  • 概念: 将一个多边形(或多面体)分割成一系列互不重叠的三角形(或四面体)的过程。
  • 应用: 在代码中,OBJ 文件中的每个面(可能是多边形)都被分割成多个三角形。这是因为三角形是最简单的多边形,更容易进行体积计算。

2. 向量叉乘 (Cross Product):

  • 概念: 两个三维向量 a\mathbf{a}b\mathbf{b} 的叉乘结果是一个新的向量 c\mathbf{c},其方向垂直于 a\mathbf{a}b\mathbf{b} 构成的平面,大小等于 a\mathbf{a}b\mathbf{b} 构成的平行四边形的面积。 公式如下:

    a×b=ijkaxayazbxbybz=(aybzazby)i(axbzazbx)j+(axbyaybx)k\mathbf{a} \times \mathbf{b} = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ a_x & a_y & a_z \\ b_x & b_y & b_z \end{vmatrix} = (a_y b_z - a_z b_y)\mathbf{i} - (a_x b_z - a_z b_x)\mathbf{j} + (a_x b_y - a_y b_x)\mathbf{k}

    其中 i\mathbf{i}, j\mathbf{j}, k\mathbf{k} 分别是 x, y, z 轴的单位向量。

  • 应用:tetrahedron_volume 函数中,叉乘被隐式地用于计算行列式,进而计算四面体的体积。叉乘可以帮助我们确定四面体的底面积和高。


3. 行列式 (Determinant):

  • 概念: 行列式是一个与方阵相关的标量值,可以用来计算线性变换的缩放因子、平行六面体的体积等。 对于三阶方阵:

    A=[abcdefghi]A = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix}

    其行列式为:

    det(A)=a(eifh)b(difg)+c(dheg)\text{det}(A) = a(ei - fh) - b(di - fg) + c(dh - eg)

  • 应用:tetrahedron_volume 函数中,使用了三阶行列式来计算四面体的体积。具体来说,行列式的值表示由三个向量构成的平行六面体的有向体积,而四面体的体积是该平行六面体体积的六分之一。


4. 四面体体积公式 (Tetrahedron Volume Formula):

  • 概念: 已知四面体的四个顶点坐标 P1(x1,y1,z1)P_1(x_1, y_1, z_1), P2(x2,y2,z2)P_2(x_2, y_2, z_2), P3(x3,y3,z3)P_3(x_3, y_3, z_3), P4(x4,y4,z4)P_4(x_4, y_4, z_4),其体积 VV 可以通过以下行列式公式计算:

    V=16det(M)V = \frac{1}{6} | \text{det}(M) |

    其中 MM 是一个 3x3 矩阵:

    M=[x2x1y2y1z2z1x3x1y3y1z3z1x4x1y4y1z4z1]M = \begin{bmatrix} x_2-x_1 & y_2-y_1 & z_2-z_1 \\ x_3-x_1 & y_3-y_1 & z_3-z_1 \\ x_4-x_1 & y_4-y_1 & z_4-z_1 \end{bmatrix}

    或者写成:

    V=16(P2P1)((P3P1)×(P4P1))V = \frac{1}{6} |(P_2-P_1) \cdot ((P_3-P_1) \times (P_4-P_1))|

    其中 \cdot 表示点乘,×\times 表示叉乘。这个公式就是代码中 tetrahedron_volume 函数的数学基础。


5. 积分思想 (Integral Calculus - Conceptual):

  • 概念: 尽管代码没有直接使用微积分的符号或公式,但其背后的思想与积分密切相关。积分的基本思想是将一个复杂的形状分割成无限多个小的、简单的形状,然后将这些小形状的体积(或其他属性)加起来,得到整体的体积。
  • 应用: 在代码中,我们将复杂的 3D 模型分割成许多个小的四面体,然后将这些四面体的体积加起来,得到模型的总体积。这可以看作是积分思想的一种离散化应用。

总结:

该算法通过将 3D 模型三角剖分成许多个四面体,然后利用向量叉乘和行列式计算每个四面体的体积,最后将所有四面体的体积相加,从而得到模型的总体积。这个过程体现了积分思想的离散化应用。


import numpy as np  
import os  
  
def calculate_obj_volume(filepath):  
    """  
    Calculates the volume of a 3D model from an OBJ file.  
    Args:        filepath (str): The path to the OBJ file.  
    Returns:        float: The volume of the 3D model. Returns None if there's an error.    """    vertices = []  
    faces = []  
  
    try:  
        with open(filepath, 'r') as f:  
            for line in f:  
                if line.startswith('v '):  
                    parts = line.strip().split()  
                    vertex = list(map(float, parts[1:]))  
                    vertices.append(vertex)  
                elif line.startswith('f '):  
                    parts = line.strip().split()  
                    face_indices = []  
                    for part in parts[1:]:  
                        # Handle cases like 'f v1/vt1/vn1' or 'f v1//vn1'  
                        index_str = part.split('/')[0]  
                        face_indices.append(int(index_str) - 1)  # OBJ indices are 1-based  
                    faces.append(face_indices)  
    except FileNotFoundError:  
        print(f"Error: File not found at {filepath}")  
        return None  
    except Exception as e:  
        print(f"Error reading OBJ file: {e}")  
        return None  
  
    if not vertices or not faces:  
        print("Error: No vertices or faces found in the OBJ file.")  
        return None  
  
    volume = 0.0  
    for face in faces:  
        # Assuming triangular faces for simplicity.  
        # For other polygons, you'd need to triangulate them.        if len(face) >= 3:  
            v1 = np.array(vertices[face[0]])  
            v2 = np.array(vertices[face[1]])  
            v3 = np.array(vertices[face[2]])  
  
            # Calculate the volume of the tetrahedron formed by the origin and the triangle  
            volume += np.dot(np.cross(v1, v2), v3) / 6.0  
  
    return abs(volume)  
  
if __name__ == "__main__":  
    obj_file_path = "C://Users//xfc05//Downloads//Untitled_Scan_10_32_59//textured_output.obj"  
  
    # Ensure the path is platform-independent  
    obj_file_path = os.path.normpath(obj_file_path)  
  
    print(f"Processing OBJ file: {obj_file_path}")  
  
    volume = calculate_obj_volume(obj_file_path)  
  
    if volume is not None:  
        print(f"The volume of the 3D model is: {volume}")

Processing OBJ file: C:\Users\xfc05\Downloads\Untitled_Scan_10_32_59\textured_output.obj The volume of the 3D model is: 41301.32030471508


提一嘴计算机拟合方法不同的算法库会得到不同的结果


精简版(more formal/International-Bitch-style)

数学原理的积分形式表达

将离散的四面体体积求和转化为积分形式可以从几何体积的积分公式出发,通过利用散度定理和多面体表面的参数化描述,实现离散与连续形式的联系。


1. 体积的积分定义

对于一个封闭体,其体积 VV 定义为:

V=Ω1dVV = \iiint_{\Omega} 1 \, dV

其中:

  • Ω\Omega 是多面体的占据区域。
  • dVdV 是体积微元。

利用 散度定理,体积积分可以转化为表面积分:

V=13ΩrndAV = \frac{1}{3} \iint_{\partial \Omega} \mathbf{r} \cdot \mathbf{n} \, dA

其中:

  • r=(x,y,z)\mathbf{r} = (x, y, z) 是空间点的位置矢量。
  • n\mathbf{n} 是面片的外法向量(单位向量)。
  • dAdA 是微小的面积元。
  • Ω\partial \Omega 是多面体的边界表面。

2. 三角形面的参数化表示

多面体的边界表面由三角形面片组成。每个三角形面由三个顶点 v0,v1,v2\mathbf{v}_0, \mathbf{v}_1, \mathbf{v}_2 定义,面上的点可以用两个参数 u,vu, v 表示为:

r(u,v)=v0+u(v1v0)+v(v2v0),0u1,0v1u\mathbf{r}(u, v) = \mathbf{v}_0 + u (\mathbf{v}_1 - \mathbf{v}_0) + v (\mathbf{v}_2 - \mathbf{v}_0), \quad 0 \leq u \leq 1, \, 0 \leq v \leq 1-u

微面积元 dAdA 的大小通过叉积计算为:

dA=(v1v0)×(v2v0)dudvdA = \left\| (\mathbf{v}_1 - \mathbf{v}_0) \times (\mathbf{v}_2 - \mathbf{v}_0) \right\| \, du \, dv

3. 体积积分的具体形式

将三角形参数化代入体积公式,体积 VV 表示为所有三角形面的积分: V=13facestriangler(u,v)ndAV = \frac{1}{3} \sum_{\text{faces}} \iint_{\text{triangle}} \mathbf{r}(u, v) \cdot \mathbf{n} \, dA

对于每个三角形面:

  1. 外法向量 n\mathbf{n} 的单位化公式为: n=(v1v0)×(v2v0)(v1v0)×(v2v0)\mathbf{n} = \frac{(\mathbf{v}_1 - \mathbf{v}_0) \times (\mathbf{v}_2 - \mathbf{v}_0)}{\| (\mathbf{v}_1 - \mathbf{v}_0) \times (\mathbf{v}_2 - \mathbf{v}_0) \|}
  2. r(u,v)\mathbf{r}(u, v) 是三角形面上的点,按参数化表达。
  3. dAdA 是面积元,按叉积计算。

最终公式为:

V=13facestrianglern(v1v0)×(v2v0)dudvV = \frac{1}{3} \sum_{\text{faces}} \iint_{\text{triangle}} \mathbf{r} \cdot \mathbf{n} \, \left\| (\mathbf{v}_1 - \mathbf{v}_0) \times (\mathbf{v}_2 - \mathbf{v}_0) \right\| \, du \, dv

4. 离散公式与积分的关系

离散方法通过三角面的顶点直接近似积分公式:

离散求和:V16facesv0((v1v0)×(v2v0))\text{离散求和:} \quad V \approx \frac{1}{6} \sum_{\text{faces}} \mathbf{v}_0 \cdot \big( (\mathbf{v}_1 - \mathbf{v}_0) \times (\mathbf{v}_2 - \mathbf{v}_0) \big)

这与积分形式的对应关系如下:

积分形式:V=13facestrianglerndA\text{积分形式:} \quad V = \frac{1}{3} \sum_{\text{faces}} \iint_{\text{triangle}} \mathbf{r} \cdot \mathbf{n} \, dA

当三角形面片数量趋近无穷,离散方法逐渐逼近连续积分结果。


5. 直观理解

  • 离散公式本质是对积分形式的数值近似。
  • 每个三角形面的积分结果由其三个顶点决定,直接代替积分计算。
  • 当模型三角化足够精细时,离散求和与积分形式几乎一致。

通过这一方式,可以理解离散体积公式的数学原理及其与连续积分形式的紧密联系。


注意

像上次饶手测量一样,代码用较小字体,框黑框 代码只保留关键的函数/算法/库(import ~) 可删掉注释


Comments