【问题标题】:In Python, how do I voxelize a 3D mesh在 Python 中,如何体素化 3D 网格
【发布时间】:2012-08-04 18:50:52
【问题描述】:

我需要帮助才能开始使用 Python(我几乎一无所知)来体素化从 Rhino 生成的 3D 网格。数据输入将是一个 .OBJ 文件,输出也是如此。这种用法的最终目的是找到建筑物内两点之间的最短距离。但那是以后的事了。至于现在,我需要首先体素化一个 3D 网格。体素化原语可能只是一个简单的立方体。

到目前为止,我可以从 OBJ 文件解析器中读取已解析的 obj,并去除 V、VT、VN、F 前缀,并使用这些坐标找到 3D 对象的边界框。体素化网格的正确方法应该是什么?

import objParser
import math

inputFile = 'test.obj'
vList = []; vtList = []; vnList = []; fList = []

def parseOBJ(inputFile):
list = []
vList, vtList, vnList, fList = objParser.getObj(inputFile)
print 'in parseOBJ'
#print vList, vtList, vnList, fList
return vList, vtList, vnList, fList

def findBBox(vList):
   i = 0; j=0; x_min = float('inf'); x_max = float('-inf'); y_min = float('inf');
   y_max =  float('-inf'); z_min = float('inf'); z_max = float('-inf');
   xWidth = 0; yWidth = 0; zWidth =0

print 'in findBBox'
while i < len(vList):
        #find min and max x value
        if vList[i][j] < x_min:
            x_min = float(vList[i][j])
        elif vList[i][j] > x_max:
            x_max = float(vList[i][j])

        #find min and max y value
        if vList[i][j + 1] < y_min:
            y_min = float(vList[i][j + 1])
        elif vList[i][j + 1] > y_max:
            y_max = float(vList[i][j + 1])

        #find min and max x value
        if vList[i][j + 2] < z_min:
            z_min = vList[i][j + 2]
        elif vList[i][j + 2] > z_max:
            z_max = vList[i][j + 2]

        #incriment the counter int by 3 to go to the next set of (x, y, z)
        i += 3; j=0

xWidth = x_max - x_min
yWidth = y_max - y_min
zWidth = z_max - z_min
length = xWidth, yWidth, zWidth
volume = xWidth* yWidth* zWidth
print 'x_min, y_min, z_min : ', x_min, y_min, z_min
print 'x_max, y_max, z_max : ', x_max, y_max, z_max
print 'xWidth, yWidth, zWidth : ', xWidth, yWidth, zWidth
return length, volume

def init():
    list = parseOBJ(inputFile)
    findBBox(list[0])

print init()

【问题讨论】:

    标签: python 3d mesh .obj voxel


    【解决方案1】:

    我没用过,你可以试试这个:http://packages.python.org/glitter/api/examples.voxelization-module.html

    或者这个工具:http://www.patrickmin.com/binvox/

    如果你想自己做这件事,你有两种主要方法:

    • “真正的”体素化,当您考虑到网格内部时 - 相当复杂,您需要大量的 CSG 操作。我帮不了你。
    • 一个“假”的 - 只需体素化网格的每个三角形。这要简单得多,您需要做的就是检查三角形和轴对齐立方体的交集。然后你就这样做:

      for every triagle:
          for every cube:
              if triangle intersects cube:
                  set cube = full
              else:
                  set cube = empty
      

    您需要做的就是实现一个 BoundingBox-Triangle 相交。当然,你可以稍微优化一下那些 for 循环 :)

    【讨论】:

      【解决方案2】:

      对于仍然存在此问题的每个人。我已经编写了一个文件,该文件制作了一个 STL 3d 文件的体素(您可以将 .obj 文件另存为 stl)。我使用了 kolenda 建议的方法来解决这个问题。这是为了所谓的“假”体素化。仍在努力填充这个体素。 我使用 numpy.stl 来导入文件,并且只使用标准包来导入文件的其余部分。我为我的程序使用了以下链接。

      For line plane collision

      to check if point is in triangle

      这可能不是最有效的,但确实有效。

      import numpy as np
      import os
      from stl import mesh
      from mpl_toolkits import mplot3d
      import matplotlib.pyplot as plt
      import math
      
      
      if not os.getcwd() == 'path_to_model':
          os.chdir('path_to model')
      
      
      your_mesh = mesh.Mesh.from_file('file.stl') #the stl file you want to voxelize
      
      
      ## set the height of your mesh
      for i in range(len(your_mesh.vectors)):
          for j in range(len(your_mesh.vectors[i])):
              for k in range(len(your_mesh.vectors[i][j])):
                  your_mesh.vectors[i][j][k] *= 5
      
      ## define functions
      def triangle_voxalize(triangle):
          trix = []
          triy = []
          triz= []
          triangle = list(triangle)
      
          #corners of triangle in array formats
          p0 = np.array(triangle[0])
          p1 = np.array(triangle[1])
          p2 = np.array(triangle[2])
      
          #vectors and the plane of the triangle
          v0 = p1 - p0
          v1 = p2 - p1
          v2 = p0 - p2
          v3 = p2 - p0
          plane = np.cross(v0,v3)
      
          #minimun and maximun coordinates of the triangle
          for i in range(3):
              trix.append(triangle[i][0])
              triy.append(triangle[i][1])
              triz.append(triangle[i][2])
          minx, maxx = int(np.floor(np.min(trix))), int(np.ceil(np.max(trix)))
          miny, maxy = int(np.floor(np.min(triy))), int(np.ceil(np.max(triy)))
          minz, maxz = int(np.floor(np.min(triz))), int(np.ceil(np.max(triz)))
      
          #safe the points that are inside triangle
          points = []
      
          #go through each point in the box of minimum and maximum x,y,z
          for x in range (minx,maxx+1):
              for y in range(miny,maxy+1):
                  for z in range(minz,maxz+1):
      
                      #check the diagnals of each voxel cube if they are inside triangle
                      if LinePlaneCollision(triangle,plane,p0,[1,1,1],[x-0.5,y-0.5,z-0.5],[x,y,z]):
                          points.append([x,y,z])
                      elif LinePlaneCollision(triangle,plane,p0,[-1,-1,1],[x+0.5,y+0.5,z-0.5],[x,y,z]):
                          points.append([x,y,z])
                      elif LinePlaneCollision(triangle,plane,p0,[-1,1,1],[x+0.5,y-0.5,z-0.5],[x,y,z]):
                          points.append([x,y,z])
                      elif LinePlaneCollision(triangle,plane,p0,[1,-1,1],[x-0.5,y+0.5,z-0.5],[x,y,z]):
                          points.append([x,y,z])
      
                      #check edge cases and if the triangle is completly inside the box
                      elif intersect(triangle,[x,y,z],v0,p0):
                          points.append([x,y,z])
                      elif intersect(triangle,[x,y,z],v1,p1):
                          points.append([x,y,z])
                      elif intersect(triangle,[x,y,z],v2,p2):
                          points.append([x,y,z])
      
          #return the points that are inside the triangle
          return(points)
      
      #check if the point is on the triangle border
      def intersect(triangle,point,vector,origin):
          x,y,z = point[0],point[1],point[2]
          origin = np.array(origin)
      
          #check the x faces of the voxel point
          for xcube in range(x,x+2):
              xcube -= 0.5
              if LinePlaneCollision(triangle,[1,0,0], [xcube,y,z], vector, origin,[x,y,z]):
                  return(True)
      
          #same for y and z
          for ycube in range(y,y+2):
              ycube -= 0.5
              if LinePlaneCollision(triangle,[0,1,0], [x,ycube,z], vector, origin,[x,y,z]):
                  return(True)
          for zcube in range(z,z+2):
              zcube -= 0.5
              if LinePlaneCollision(triangle,[0,0,1], [x,y,zcube], vector, origin,[x,y,z]):
                  return(True)
      
          #check if the point is inside the triangle (in case the whole tri is in the voxel point)
          if origin[0] <= x+0.5 and origin[0] >= x-0.5:
              if origin[1] <= y+0.5 and origin[1] >= y-0.5:
                  if origin[2] <= z+0.5 and origin[2] >= z-0.5:
                      return(True)
      
          return(False)
      
      # I modified this file to suit my needs:
      # https://gist.github.com/TimSC/8c25ca941d614bf48ebba6b473747d72
      #check if the cube diagnals cross the triangle in the cube
      def LinePlaneCollision(triangle,planeNormal, planePoint, rayDirection, rayPoint,point, epsilon=1e-6):
          planeNormal = np.array(planeNormal)
          planePoint = np.array(planePoint)
          rayDirection = np.array(rayDirection)
          rayPoint = np.array(rayPoint)
      
      
          ndotu = planeNormal.dot(rayDirection)
          if abs(ndotu) < epsilon:
              return(False)
      
          w = rayPoint - planePoint
          si = -planeNormal.dot(w) / ndotu
          Psi = w + si * rayDirection + planePoint
      
          #check if they cross inside the voxel cube
          if np.abs(Psi[0]-point[0]) <= 0.5 and np.abs(Psi[1]-point[1]) <= 0.5 and np.abs(Psi[2]-point[2]) <= 0.5:
              #check if the point is inside the triangle and not only on the plane
              if PointInTriangle(Psi, triangle):
                  return (True)
          return (False)
      
      # I used the following file for the next 2 functions, I converted them to python. Read the article. It explains everything way better than I can.
      # https://blackpawn.com/texts/pointinpoly#:~:text=A%20common%20way%20to%20check,triangle%2C%20otherwise%20it%20is%20not.
      #check if point is inside triangle
      def SameSide(p1,p2, a,b):
          cp1 = np.cross(b-a, p1-a)
          cp2 = np.cross(b-a, p2-a)
          if np.dot(cp1, cp2) >= 0:
              return (True)
          return (False)
      
      def PointInTriangle(p, triangle):
          a = triangle[0]
          b = triangle[1]
          c = triangle[2]
          if SameSide(p,a, b,c) and SameSide(p,b, a,c) and SameSide(p,c, a,b):
              return (True)
          return (False)
      
      ##
      my_mesh = your_mesh.vectors.copy() #shorten the name
      
      voxel = []
      for i in range (len(my_mesh)): # go though each triangle and voxelize it
          new_voxel = triangle_voxalize(my_mesh[i])
          for j in new_voxel:
              if j not in voxel: #if the point is new add it to the voxel
                  voxel.append(j)
      
      ##
      print(len(voxel)) #number of points in the voxel
      
      #split in x,y,z points
      x_points = []
      y_points = []
      z_points = []
      for a in range (len(voxel)):
          x_points.append(voxel[a][0])
          y_points.append(voxel[a][1])
          z_points.append(voxel[a][2])
      
      ## plot the voxel
      ax = plt.axes(projection="3d")
      ax.scatter3D(x_points, y_points, z_points)
      plt.xlabel('x')
      plt.ylabel('y')
      plt.show()
      
      ## plot 1 layer of the voxel
      for a in range (len(z_points)):
          if z_points[a] == 300:
              plt.scatter(x_points[a],y_points[a])
      
      plt.show()
      

      【讨论】:

        【解决方案3】:

        您可以通过pymadcad 实现此目的 PositionMap 类有一个非常优化的方法,可以将点、线和三角形光栅化为体素。

        这只是填充网格表面的体素,而不是填充内部。但是使用这些功能,内部将始终与外部分开;)

        它应该是这样的:

        from madcad.hashing import PositionMap
        from madcad.io import read
        
        # load the obj file in a madcad Mesh
        mymesh = read('mymesh.obj')
        # choose the cell size of the voxel
        size = 1
        
        voxel = set()    # this is a sparse voxel
        hasher = PositionMap(size)   # ugly object creation, just to use one of its methods
        for face in mymesh.faces:
            voxel.update(hasher.keysfor(mymesh.facepoints(face)))
        

        是的,它不是那么漂亮,因为即使函数存在,madcad 也没有(还)任何惯用的方法来做到这一点。

        解释

        • 我们使用仅存储非零单元位置的 set 而不是 3d 布尔数组(存储大部分零的成本非常高且过大)
        • hasher.keysfor 方法生成输入基元(此处为三角形)到达的体素单元的所有位置
        • set 是一个哈希表,可以非常有效地检查元素是否在内部(例如,可以非常有效地检查单元格是否被原语占用)
        • 所以循环只是用图元占据的单元格更新体素,占据的单元格然后是网格表面上的单元格

        另一种寻路方式

        您似乎正在尝试使用一些寻路来找到您在这栋建筑物内的最短距离。体素化区域是一个很好的方法。如果您需要尝试,还有另一个:

        寻路(如 A* 或 dikstra)通常适用于图。它可以是体素中的连接细胞,也可以是任何不规则间隔的细胞。

        因此,另一种解决方案是通过生成四面体来对建筑物墙壁之间的 3d 空间进行三角测量。不是将您的算法从体素单元传播到体素单元,而是从四面体传播到四面体。 四面体确实具有从随机网格生成速度更快的优势,而且不需要与区域体积成比例的内存,并且不会降低任何障碍物精度(四面体具有自适应大小)。

        你可以看看here它的样子。

        【讨论】:

          【解决方案4】:

          还有兴趣的可以试试voxeltool 这似乎正是为此目的而制作的。

          来自文档:

          体素工具为 Rhino 提供轻量级体素几何体。它允许您从网格、brep、曲线和点快速生成和操作体素化几何体,并提供体素网格之间的布尔运算。它可以将体素网格转换为实体网格外壳。

          【讨论】:

            猜你喜欢
            • 2011-04-18
            • 2022-10-30
            • 2020-08-14
            • 1970-01-01
            • 2011-10-24
            • 2013-11-20
            • 1970-01-01
            • 1970-01-01
            • 2018-11-04
            相关资源
            最近更新 更多