草庐IT

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

Billy_zz 2023-04-10 原文

一、前言

        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方面的知识,包括纹理映射和坐标关系等,简单做一个分享,方法并不完美,欢迎各位一起讨论学习

有关Python调用vtk库和numpy绘制自定义曲面,并进行纹理映射的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  4. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  5. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  6. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  7. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  8. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  9. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  10. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

随机推荐