将纹理映射到网格时,Vtk 在节点之间插入不正确的颜色
Vtk inserts incorrect color between nodes when mapping texture to mesh
您好,我正在尝试使用 Mayavi 和 vtk 的 Python 绑定将纹理映射到 3d 网格。我正在可视化 .obj 波前。这个对象是一张脸的 3D 照片。纹理图像是由三张二维照片合成的。
网格中的每个节点在图像中都有一个 (uv) 坐标,它定义了它的颜色。网格的不同区域从图像的不同部分绘制颜色。为了说明这一点,我用这个替换了实际的纹理图像:
并将其映射到网格。
我遇到的问题是鼻子周围的问题。在红色和绿色的边界处有一个蓝色的轮廓。在线框模式下对该区域的仔细检查表明,这不是 uv 映射的问题,而是 vtk 如何在两个节点之间插值颜色的问题。出于某种原因,它在两个节点之间添加了一块蓝色,其中一个是红色,一个是绿色。
这在使用真实纹理进行可视化时会导致严重问题
有没有办法强制vtk选择一个或另一个相邻节点的颜色作为它们之间的颜色?我尝试打开 "edge-clamping",但这并没有达到任何效果。
我使用的代码如下,您可以从这里访问相关文件 https://www.dropbox.com/sh/ipel0avsdiokr10/AADmUn1-qmsB3vX7BZObrASPa?dl=0
但我希望这是一个简单的解决方案。
from numpy import *
from mayavi import mlab
from tvtk.api import tvtk
import os
from vtk.util import numpy_support
def obj2array(f):
"""function for reading a Wavefront obj"""
if type(f)==str:
if os.path.isfile(f)==False:
raise ValueError('obj2array: unable to locate file ' + str(f))
f =open(f)
vertices = list()
connectivity = list()
uv = list()
vt = list()
fcount = 0
for l in f:
line = l.rstrip('\n')
data = line.split()
if len(data)==0:
pass
else:
if data[0] == 'v':
vertices.append(atleast_2d(array([float(item) for item in data[1:4]])))
elif data[0]=='vt':
uv.append(atleast_2d(array([float(item) for item in data[1:3]])))
elif data[0]=='f':
nverts = len(data)-1 # number of vertices comprising each face
if fcount == 0: #on first face establish face format
fcount = fcount + 1
if data[1].find('/')==-1: #Case 1
case = 1
elif data[1].find('//')==True:
case = 4
elif len(data[1].split('/'))==2:
case = 2
elif len(data[1].split('/'))==3:
case = 3
if case == 1:
f = atleast_2d([int(item) for item in data[1:len(data)]])
connectivity.append(f)
if case == 2:
splitdata = [item.split('/') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
if case == 3:
splitdata = [item.split('/') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
if case == 4:
splitdata = [item.split('//') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
vertices = concatenate(vertices, axis = 0)
if len(uv)==0:
uv=None
else:
uv = concatenate(uv, axis = 0)
if len(connectivity) !=0:
try:
conarray = concatenate(connectivity, axis=0)
except ValueError:
if triangulate==True:
conarray=triangulate_mesh(connectivity,vertices)
else:
raise ValueError('obj2array: not all faces triangles?')
if conarray.shape[1]==4:
if triangulate==True:
conarray=triangulate_mesh(connectivity,vertices)
return vertices, conarray,uv
# load texture image
texture_img = tvtk.Texture(interpolate = 1,edge_clamp=1)
texture_img.input = tvtk.BMPReader(file_name='HM_1_repose.bmp').output
#load obj
verts, triangles, uv = obj2array('HM_1_repose.obj')
# make 0-indexed
triangles = triangles-1
surf = mlab.triangular_mesh(verts[:,0],verts[:,1],verts[:,2],triangles)
tc=numpy_support.numpy_to_vtk(uv)
pd = surf.mlab_source.dataset._vtk_obj.GetPointData()
pd.SetTCoords(tc)
surf.actor.actor.mapper.scalar_visibility=False
surf.actor.enable_texture = True
surf.actor.actor.texture = texture_img
mlab.show(stop=True)
您可以关闭所有插值(在您的示例中将 interpolate = 1
更改为 interpolate = 0
),但是没有办法仅在它会跨子插值的地方关闭插值纹理图像——至少在没有编写自己的片段着色器的情况下。这可能看起来很粗糙。
另一种解决方案是在不属于演员面部的每个位置创建 3 个具有透明纹素的纹理图像。然后每次渲染具有相同纹理坐标但不同图像的相同几何体(即,有 3 个演员,每个角色具有相同的多数据但不同的纹理图像)。
我也 运行 研究了这个确切的问题,发现发生这种情况的原因是因为 VTK 假设在渲染演员和关联的 vtkTexture。然而,在我的例子和 OP 的例子中,有相邻的三角形被映射到图像的不同部分,所以它们有非常不同的 uv 坐标。共享这些相邻面的点只能有一个与之关联的 uv 坐标(或 Tcoord),但它们实际上需要 2 个(或更多,具体取决于您的情况)。
我的解决方案是遍历并复制位于 seams/borders 上的这些点,并创建一个新的 vtkCellArray,其三角形具有这些重复的 pointId。然后我简单地用新的三角形替换了 vtkPolyData Polys() 列表。复制点并为每个需要它的三角形更新现有的 pointId 会容易得多,但我找不到正确更新单元格的方法。
您好,我正在尝试使用 Mayavi 和 vtk 的 Python 绑定将纹理映射到 3d 网格。我正在可视化 .obj 波前。这个对象是一张脸的 3D 照片。纹理图像是由三张二维照片合成的。
网格中的每个节点在图像中都有一个 (uv) 坐标,它定义了它的颜色。网格的不同区域从图像的不同部分绘制颜色。为了说明这一点,我用这个替换了实际的纹理图像:
并将其映射到网格。
我遇到的问题是鼻子周围的问题。在红色和绿色的边界处有一个蓝色的轮廓。在线框模式下对该区域的仔细检查表明,这不是 uv 映射的问题,而是 vtk 如何在两个节点之间插值颜色的问题。出于某种原因,它在两个节点之间添加了一块蓝色,其中一个是红色,一个是绿色。
这在使用真实纹理进行可视化时会导致严重问题
有没有办法强制vtk选择一个或另一个相邻节点的颜色作为它们之间的颜色?我尝试打开 "edge-clamping",但这并没有达到任何效果。
我使用的代码如下,您可以从这里访问相关文件 https://www.dropbox.com/sh/ipel0avsdiokr10/AADmUn1-qmsB3vX7BZObrASPa?dl=0 但我希望这是一个简单的解决方案。
from numpy import *
from mayavi import mlab
from tvtk.api import tvtk
import os
from vtk.util import numpy_support
def obj2array(f):
"""function for reading a Wavefront obj"""
if type(f)==str:
if os.path.isfile(f)==False:
raise ValueError('obj2array: unable to locate file ' + str(f))
f =open(f)
vertices = list()
connectivity = list()
uv = list()
vt = list()
fcount = 0
for l in f:
line = l.rstrip('\n')
data = line.split()
if len(data)==0:
pass
else:
if data[0] == 'v':
vertices.append(atleast_2d(array([float(item) for item in data[1:4]])))
elif data[0]=='vt':
uv.append(atleast_2d(array([float(item) for item in data[1:3]])))
elif data[0]=='f':
nverts = len(data)-1 # number of vertices comprising each face
if fcount == 0: #on first face establish face format
fcount = fcount + 1
if data[1].find('/')==-1: #Case 1
case = 1
elif data[1].find('//')==True:
case = 4
elif len(data[1].split('/'))==2:
case = 2
elif len(data[1].split('/'))==3:
case = 3
if case == 1:
f = atleast_2d([int(item) for item in data[1:len(data)]])
connectivity.append(f)
if case == 2:
splitdata = [item.split('/') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
if case == 3:
splitdata = [item.split('/') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
if case == 4:
splitdata = [item.split('//') for item in data[1:len(data)]]
f = atleast_2d([int(item[0]) for item in splitdata])
connectivity.append(f)
vertices = concatenate(vertices, axis = 0)
if len(uv)==0:
uv=None
else:
uv = concatenate(uv, axis = 0)
if len(connectivity) !=0:
try:
conarray = concatenate(connectivity, axis=0)
except ValueError:
if triangulate==True:
conarray=triangulate_mesh(connectivity,vertices)
else:
raise ValueError('obj2array: not all faces triangles?')
if conarray.shape[1]==4:
if triangulate==True:
conarray=triangulate_mesh(connectivity,vertices)
return vertices, conarray,uv
# load texture image
texture_img = tvtk.Texture(interpolate = 1,edge_clamp=1)
texture_img.input = tvtk.BMPReader(file_name='HM_1_repose.bmp').output
#load obj
verts, triangles, uv = obj2array('HM_1_repose.obj')
# make 0-indexed
triangles = triangles-1
surf = mlab.triangular_mesh(verts[:,0],verts[:,1],verts[:,2],triangles)
tc=numpy_support.numpy_to_vtk(uv)
pd = surf.mlab_source.dataset._vtk_obj.GetPointData()
pd.SetTCoords(tc)
surf.actor.actor.mapper.scalar_visibility=False
surf.actor.enable_texture = True
surf.actor.actor.texture = texture_img
mlab.show(stop=True)
您可以关闭所有插值(在您的示例中将 interpolate = 1
更改为 interpolate = 0
),但是没有办法仅在它会跨子插值的地方关闭插值纹理图像——至少在没有编写自己的片段着色器的情况下。这可能看起来很粗糙。
另一种解决方案是在不属于演员面部的每个位置创建 3 个具有透明纹素的纹理图像。然后每次渲染具有相同纹理坐标但不同图像的相同几何体(即,有 3 个演员,每个角色具有相同的多数据但不同的纹理图像)。
我也 运行 研究了这个确切的问题,发现发生这种情况的原因是因为 VTK 假设在渲染演员和关联的 vtkTexture。然而,在我的例子和 OP 的例子中,有相邻的三角形被映射到图像的不同部分,所以它们有非常不同的 uv 坐标。共享这些相邻面的点只能有一个与之关联的 uv 坐标(或 Tcoord),但它们实际上需要 2 个(或更多,具体取决于您的情况)。
我的解决方案是遍历并复制位于 seams/borders 上的这些点,并创建一个新的 vtkCellArray,其三角形具有这些重复的 pointId。然后我简单地用新的三角形替换了 vtkPolyData Polys() 列表。复制点并为每个需要它的三角形更新现有的 pointId 会容易得多,但我找不到正确更新单元格的方法。