在Python中,我如何体素化3D网格

时间:2012-08-07 17:47:05

标签: python 3d mesh .obj voxel

我需要帮助开始使用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()

3 个答案:

答案 0 :(得分:6)

我没有使用它,但您可以试试这个: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交集。当然,你可以稍微优化那些循环:)

答案 1 :(得分:1)

对于每个仍然存在此问题的人。我写了一个文件,它制作了 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()

答案 2 :(得分:0)

您可以使用 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 生成输入基元(此处为三角形)到达的体素单元的所有位置
  • a set 是一个哈希表,可以非常有效地检查元素是否在内部(例如,可以非常有效地检查单元格是否被原语占用)
  • 所以循环只是用图元占据的单元更新体素,然后占据的单元就是网格表面上的单元

另一种进行寻路的方法

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

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

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

您可以看看here它的样子。