Python调用vtk库和numpy绘制自定义曲面,并进行纹理映射

一、前言

        vtk库是一个开源的三维计算机图形学、图像处理和可视化库,可以用来执行三维重建、纹理映射等任务。由于组里的项目的三维模型需要,开始慢慢摸索这个库怎么用(PS:学图像处理的从来没搞过3D任务,难顶)...vtk的轮子(whl文件)可以在这个链接处获得:Archived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu)https://www.lfd.uci.edu/~gohlke/pythonlibs/#vtk

        发现网上的vtk教程基本都来源于张晓东老师《VTK图形图像开发进阶》这本书,内容很全面,但总有想找却找不到的知识点。本次任务是把胶带纸筒内壁重建出来,图如下:

         这个纸筒的曲面信息是已知的,直径为76mm。这里我会把相机镜头放在其圆心处拍一张照片,图像尺寸是2592*1944(500w像素),比例4:3,得到的图片如下(texture.jpg):

        现在的任务就是,把相机视野对应的曲面用vtk库重建出来,并且将上面这幅图像作为纹理映射到重建出的模型上,该怎么做呢? 

二、numpy和vtk库绘制自定义曲面

        查阅资料,发现vtk提供了一个重要的函数:vtk.vtkSurfaceReconstructionFilter(),这个函数可以帮助我们将vtk.vtkPolyData()格式的顶点数据进行隐式平面重建,而vtk.vtkPolyData()的顶点数据又可以用numpy来进行构建。这样一来思路就有了:

1.numpy构建自定义曲面采样点(使用linspace等)

2.vtk库调用vtkSurfaceReconstructionFilter进行曲面重建和绘制

3.使用vtk库进行纹理映射

2.1 numpy输入顶点坐标

        经过计算,拍摄的texture,jpg的视野对应的是圆柱面的一部分,暂且称为圆弧面吧。这个圆弧面的角度约为90°半径76mm高度80mm,这里用1个单位长度作为1mm进行绘图,构建顶点坐标代码如下(使用了numpy和vtk.vtkPoints()):

import vtk
import numpy as np

# 半径
r = 76
# 极坐标系下构建顶点数据
theta = np.linspace(0, np.pi / 2, 61)
x = r * np.cos(theta)
y = r * np.sin(theta)

# vtkPoints格式的点
points = vtk.vtkPoints()
vertices = vtk.vtkCellArray()

i = 0
for z in range(80):
    for xi, yi in zip(x, y):
        points.InsertPoint(i, xi, yi, z)
        # vertices.InsertNextCell(1)
        # vertices.InsertCellPoint(i)
        i += 1

        通过这些类似采样点的数据可以绘制出顶点阵列的三维图像(代码未给出,只作为示例):

 

        有了这些顶点数据,下一步就可以调用vtkSurfaceReconstructionFilter进行我们自定义曲面的重建工作了。

2.2 vtkSurfaceReconstructionFilter曲面重建

        有了2.1中的numpy.ndarray格式的顶点数据,这里先将其转换为vtk.vtkPolyData()格式的vtk数据格式,主要使用SetPoints函数来实现:

polyData = vtk.vtkPolyData()
polyData.SetPoints(points)

        接下来到了vtkSurfaceReconstructionFilter和vtkContourFilter登场的时候了,只需将上面转换得到的vtkPolyData作为输入,便可以得到重建的曲面对象:

polyData = vtk.vtkPolyData()
polyData.SetPoints(points)
# polyData.SetVerts(vertices)

surf = vtk.vtkSurfaceReconstructionFilter()
# 输入polyData数据
surf.SetInputData(polyData)
surf.SetNeighborhoodSize(20)
# 此处SetSampleSpacing数值小精度高但运行时间长,数值大则反之
surf.SetSampleSpacing(1.0)
surf.Update()

# 轮廓信息
contour = vtk.vtkContourFilter()
contour.SetInputConnection(surf.GetOutputPort())
contour.SetValue(0, 0.0)
contour.Update()

         进行到这一步,我们实际上已经构建出了一个自定义曲面,并加载到了vtk.vtkContourFilter()格式的contour变量中。此时我们可以利用vtk库的映射器(mapper)和演员(actor)绘制出contour的模样,这一部分网上教程就很多了,大家可以自行查缺补漏:

texturemap = vtk.vtkTextureMapToPlane()
texturemap.SetInputData(contour.GetOutput())

# 设置纹理映射原点
texturemap.SetOrigin(76, 0, 0)
# 设置两个坐标轴的方向(根据自定义曲面的信息来输入)
texturemap.SetPoint1(0, 76, 0)
texturemap.SetPoint2(76, 0, 80)

# 映射器
mapper = vtk.vtkPolyDataMapper()
# 映射器输入自定义曲面模型信息
mapper.SetInputConnection(texturemap.GetOutputPort())
# 这一句十分关键,不然后面的纹理映射可能会失败
mapper.ScalarVisibilityOff()

# 演员
actor = vtk.vtkActor()
# 演员添加映射器
actor.SetMapper(mapper)

# 绘制
ren = vtk.vtkRenderer()
# 添加演员
ren.AddActor(actor)

#绘制窗口
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
renWin.SetSize(800, 800)

# 交互窗口
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
iren.Initialize()
renWin.Render()
iren.Start()

没有纹理的自定义曲面三维图像,大概长这个样子:

         我们已经成功一大半了!接下来只需要像在曲面屏上贴膜一样,将纹理映射到这个纯白色的平面上~

三、 纹理映射

        在自定义曲面上进行纹理映射并不困难,这里使用vtk.vtkTextureMapToPlane()进行模型构建,也就是将纹理图片texture.jpg沿某个平面平铺在在自定义曲面上。其中最主要的是设置三个坐标SetOrigin、SetPoint1和SetPoint2,分别代表了映射原点和两个坐标轴方向。原点位置和轴朝向不同,映射得到的纹理有可能方向也会倒置或者镜像,三维坐标规律这里手绘一下,意会即可:

        注意原点位置往往不是原坐标系中的 (0,0,0)!,需要根据自己图形的坐标来确定。接下来直接进行纹理映射即可,texture.jpg所示的图案便会按照上图的平面方向和尺寸往自定义的圆弧面上映射,如前文所说,像在曲面屏上贴保护膜一样...代码如下,之后绘制步骤同上,不赘述

reader = vtk.vtkJPEGReader()
reader.SetFileName(r'texture.jpg')

texture = vtk.vtkTexture()  # 定义一个纹理类
texture.SetInputConnection(reader.GetOutputPort())
texture.InterpolateOn()

texturemap = vtk.vtkTextureMapToPlane()
texturemap.SetInputData(contour.GetOutput())
texturemap.SetOrigin(76, 0, 0)
texturemap.SetPoint1(0, 76, 0)
texturemap.SetPoint2(76, 0, 80)

# 映射器
mapper = vtk.vtkPolyDataMapper()
# 映射器输入自定义曲面模型信息
mapper.SetInputConnection(texturemap.GetOutputPort())
# 这一句十分关键,不然后面的纹理映射可能会失败
mapper.ScalarVisibilityOff()

# 演员
actor = vtk.vtkActor()
# 演员添加映射器
actor.SetMapper(mapper)
#演员添加纹理
actor.SetTexture(texture)

        至此大功告成,三维效果如下:

纹理映射三维效果

四、 总结

        先放上整个过程的代码供大家参考:

import vtk
import numpy as np

# 半径
r = 76
# 极坐标系下构建顶点数据
theta = np.linspace(0, np.pi / 2, 61)
x = r * np.cos(theta)
y = r * np.sin(theta)
# vtkPoints格式的点
points = vtk.vtkPoints()
vertices = vtk.vtkCellArray()
i = 0
for z in range(80):
    for xi, yi in zip(x, y):
        points.InsertPoint(i, xi, yi, z)
        # vertices.InsertNextCell(1)
        # vertices.InsertCellPoint(i)
        i += 1

polyData = vtk.vtkPolyData()
polyData.SetPoints(points)
# polyData.SetVerts(vertices)
surf = vtk.vtkSurfaceReconstructionFilter()
# 输入polyData数据
surf.SetInputData(polyData)
surf.SetNeighborhoodSize(20)
# 此处SetSampleSpacing数值小精度高但运行时间长,数值大则反之
surf.SetSampleSpacing(1.0)
surf.Update()
# 轮廓信息
contour = vtk.vtkContourFilter()
contour.SetInputConnection(surf.GetOutputPort())
contour.SetValue(0, 0.0)
contour.Update()

reader = vtk.vtkJPEGReader()
reader.SetFileName(r'texture.jpg')
texture = vtk.vtkTexture()  # 定义一个纹理类
texture.SetInputConnection(reader.GetOutputPort())
texture.InterpolateOn()
texturemap = vtk.vtkTextureMapToPlane()
texturemap.SetInputData(contour.GetOutput())
texturemap.SetOrigin(76, 0, 0)
texturemap.SetPoint1(0, 76, 0)
texturemap.SetPoint2(76, 0, 80)

# 映射器
mapper = vtk.vtkPolyDataMapper()
# 映射器输入自定义曲面模型信息
mapper.SetInputConnection(texturemap.GetOutputPort())
# 这一句十分关键,不然后面的纹理映射可能会失败
mapper.ScalarVisibilityOff()

# 演员
actor = vtk.vtkActor()
# 演员添加映射器
actor.SetMapper(mapper)
actor.SetTexture(texture)

# 绘制
ren = vtk.vtkRenderer()
# 添加演员
ren.AddActor(actor)

#绘制窗口
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
renWin.SetSize(800, 800)

# 交互窗口
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
iren.Initialize()
renWin.Render()
iren.Start()

        这里的自定义平面大家可以任意修改为自己的模型,比如地形图等数据。本人刚刚接触3D方面的知识,包括纹理映射和坐标关系等,简单做一个分享,方法并不完美,欢迎各位一起讨论学习

猜你喜欢

转载自blog.csdn.net/m0_57315535/article/details/128155049