亲宝软件园·资讯

展开

Python处理3D数据

一大口奶酪 人气:0

今天我们将介绍处理大量数据时非常方便的工具。我不会只告诉您可能在手册中找到的一般信息,而是分享一些我发现的小技巧,例如tqdm与 multiprocessing​imap​​一起使用、并行处理档案、绘制和处理 3D 数据以及如何搜索如果您有点云,则用于对象网格中的类似对象。​

那么我们为什么要求助于并行计算呢?如今,如果您处理任何类型的数据,您可能会面临与“大数据”相关的问题。每次我们有不适合 RAM 的数据时,我们都需要一块一块地处理它。幸运的是,现代编程语言允许我们生成在多核处理器上完美运行的多个进程(甚至线程)。(注意:这并不意味着单核处理器不能处理多处理。  这是关于该主题的堆栈溢出线程。)

今天,我们将尝试处理经常发生的计算网格和点云之间距离的 3D 计算机视觉任务。例如,当您需要在所有可用网格中找到定义与给定点云相同的 3D 对象的网格时,您可能会遇到此问题。

我们的数据由​​.obj​​​存储在​​.7z​​存档中的文件组成,这在存储效率方面非常出色。但是当我们需要访问它的确切部分时,我们应该努力。在这里,我定义了包装 7-zip 存档并提供底层数据接口的类。

类 Archive7z(基础):
def __init__ ( self , file , password = None ):
# ...
自我。文件={}
# ...
对于信息的文件。文件:
#创建一个知道磁盘位置的ArchiveFile实例
file = ArchiveFile ( info , pos , src_pos , folder , self , maxsize = maxsize )
# ...
自我。文件。追加(文件)
# ...
自我。文件映射。更新([(X。文件名,X)为X的自我。文件])
#从files_map字典返回ArchiveFile的方法
def getmember ( self , name ):
if isinstance ( name , ( int , long )):
尝试:
回归自我。文件[名称]
除 了 IndexError:
返回无
回归自我。文件映射。获取(名称,无)
类 Archive7(基础):
定义读取(自我):
# ...
对于水平,编码器在枚举(自我。_folder。编码器):
# ...
#获取解码器并解码底层数据
data = getattr ( self , decoder ) ( coder , data , level , num_coders )
返回数据

这个类几乎不依赖​​py7zlib​​​包,它允许我们在每次调用get方法时解压缩数据并为我们提供存档中的文件数。我们还定义了​​__iter__​​这将帮助我们map像在可迭代对象上一样在该对象上启动多处理。

您可能知道,可以创建一个 Python 类,从中可以实例化可迭代对象。该类应满足以下条件:覆盖​​__getitem__​​​返回​​self​​​和​​__next__​​​返回后续元素。我们绝对遵守这条规则。

上面的定义为我们提供了遍历存档的可能性,但 它是否允许我们 并行随机访问内容,这是一个有趣的问题,我在网上没有找到答案,但我们可以研究源代码​​py7zlib​​并尝试自己回答。

在这里,我提供了来自pylzma的代码片段:

类 Archive7z(基础):
def __init__ ( self , file , password = None ):
# ...
自我。文件={}
# ...
对于信息的文件。文件:
#创建一个知道磁盘位置的ArchiveFile实例
file = ArchiveFile ( info , pos , src_pos , folder , self , maxsize = maxsize )
# ...
自我。文件。追加(文件)
# ...
自我。文件映射。更新([(X。文件名,X)为X的自我。文件])
#从files_map字典返回ArchiveFile的方法
def getmember ( self , name ):
if isinstance ( name , ( int , long )):
尝试:
回归自我。文件[名称]
除 了 IndexError:
返回无
回归自我。文件映射。获取(名称,无)
类 Archive7z(基础):
定义读取(自我):
# ...
对于水平,编码器在枚举(自我。_folder。编码器):
# ...
#获取解码器并解码底层数据
data = getattr ( self , decoder ) ( coder , data , level , num_coders )
返回数据

在代码中,您可以看到在从存档中读取下一个对象期间调用的方法。我相信从上面可以清楚地看出,只要同时多次读取存档,就没有理由阻止存档。

接下来,我们快速介绍一下什么是网格和点云。

首先,网格是顶点、边和面的集合。顶点由空间中的(x,y,z) 坐标定义并分配有唯一编号。边和面是相应的点对和三元组的组,并用提到的唯一点 id 定义。通常,当我们谈论“网格”时,我们指的是“三角形网格”,即由三角形组成的表面。使用​​trimesh​​库在 Python 中使用网格要容易得多。例如,它提供了一个接口来加载​​.obj​​内存中的文件。要在​​jupyter notebook​​一个3D 对象中显示和交互,可以使用​​k3d​​库。

所以,用下面的代码片段我回答这个问题:“你怎么绘制​​trimesh​​​的对象​​jupyter​​​有​​k3d​​?”

进口饰面
导入k3d
使用 open ( w. /data/meshes/stanford-bunny, obj")作为 f : bunny_mesh =网眼。力口载(f , 'obj')
情节=k3d。情节()
网格= k3d。网格 (bunny_mesh . vertices> bunny_mesh . faces) 绘图上网格
情节。显示。

其次,点云是表示空间中对象的 3D 点数组。许多 3D 扫描仪生成点云作为扫描对象的表示。出于演示目的,我们可以读取相同的网格并将其顶点显示为点云。

​进口饰面
导入k3d
使用 open ( w. /data/meshes/stanford-bunny, obj")作为 f :
   bunny_mesh =网眼。力口载(f , 'obj')
情节=k3d。情节()
云=k3d。点(bunny_mesh . vertices , point_size = 0. 0001 , shader = "flat")
情节+=云
情节。显示。
​

k3d绘制的点云

如上所述,3D 扫描仪为我们提供了一个点云。假设我们有一个网格数据库,我们想在我们的数据库中找到一个与扫描对象对齐的网格,也就是点云。为了解决这个问题,我们可以提出一种简单的方法。我们将从我们的档案中搜索给定点云的点与每个网格之间的最大距离。如果​​1e-4​​某些网格的距离更小,我们将认为该网格与点云对齐。

​最后,我们来到了多处理部分。请记住,我们的存档中有大量文件可能无法放在一起放在内存中,因为我们更喜欢并行处理它们。为了实现这一点,我们将使用 ​​multiprocessing Pool​​​,它使用​​map​​​或​​imap/imap_unordered​​​方法处理用户定义函数的多次调用。​​map​​​和​​imap​​​影响我们的区别在于,​​map​​​在将其发送到工作进程之前将可迭代对象转换为列表。如果存档太大而无法写入 ​​RAM​​​,则不应将其解压缩到 ​​Python ​​列表中。换句话说,两者的执行速度是相似的。

[加载网格:pool.map w/o manager] 4 个进程的池经过时间:37.213207403818764 秒
[加载网格:pool.imap_unordered w/o manager] 4 个进程的池经过时间:37.219303369522095 秒

上面您可以看到从适合内存的网格档案中简单读取的结果。

更进一步​​imap​​​:让我们讨论如何实现我们的目标,即找到靠近点云的网格。这是数据。我们有 5 种来自斯坦福模型的不同网格。我们将通过向斯坦福兔子网格的顶点添加噪声来模拟 3D 扫描。

将numpy导入为np
A numpy。随机 导入 defaulting
def normalize_pc (点):
   点额=点额-点额。平均值(轴=0)[无,:]
   分布=np。linalg<>范数(点,轴=1) scaled_points =点 / dists中。最大值。 返回 scaled_points
def load_bunny_pc ( bunny_path ):
   标准差=lₑ-3
   使用 open ( bunny_path )作为 f : bunny_mesh = load_mesh ( f )
   #标准化后云
   scaled_bunny = normalize_pc ( bunny_mesh . vertices )
   #向点云添加一些噪声 rng = defaulting ()
   噪音=rng。正常(0. 0 , STD , scaled_bunny . shape ) 畸变兔子=缩放兔子+噪声
    返回 di st ort ed_bunny

当然,我们之前在下面将点云和网格顶点归一化,以在 3D 立方体中缩放它们。

要计算点云和网格之间的距离,我们将使用​​igl​​。为了完成,我们需要编写一个函数来调用每个进程及其依赖项。让我们用下面的代码片段来总结一下。

导入迭代工具
导入时间
将 numpy 导入为 np
  nwnpyo 随机导入 default rng
面以1 口口如 进进从
A多处理导入池
de£ load_mesh ( obj_file ):
   目二 trimesh。力口载(obj_file , ' obj')
   返回网格
def get_max__dist ( basjmesh , point_cloud ):
   distance_sq , mesh_face__indexes , _ = igl。point_mesh_squared_distance (
       点云,
       basjmesho 顶点,
       basjmesho 面孔
   )
   返回distancjsq。最大值0
def 1 oad_mesh__get_di stance ( args ):
   obj_file , point__cloud = args [ 0 ]/ args [ 1 ]
   网格二 load_mesh ( obj_file )
   网。顶点=RormaliNe_pc (网格。顶点)
   max_dist = get_max_dist (网格,点云)
   返回 max__dist
de£ read_meshes__get__di stances_pool__imap ( archive_path , point_cloud , nwn_proc , nwn_i terations ):
   #在疝中进行向格“理
   elapsed__time =[]
   为一在 范围(nujn-i terati ons ):
      归档二 MeshesArchive (ARCHIVE-PATH)
      池二池(nwn_proc )
      开始=时间。时间0
      导致=名单(tqdm(池。IMAP (
         1 o ad_m e sh__ge t_di s t anc e ,
         zip (存档,itertoolso 重复(point_cloud)),
      ),总计=len辱档)))
      池。关闭0
      池。加入o
      结束=时间。时间0
      elapsed time o追加(结束一开始)
   print ( F [Process meshes: pool, imap] {num_proc}个进程的池经过的时间:{np. array (elapsed_time). mean()} sec )
   对于 name , di st in zip ( archive . namesjist , result ): 打印(r{name} {dist}")
   返回结果
如果 _name_ ==
   bunny_path =  /data/meshes/stanford-bunny, obj"
   archive_path = /data/meshes. 7zff
   nwn_proc = 4
   num_iterations = 3
   point__cloud - load__bunny_pc ( bunny_path )
     read_meshes__get__di stances_pool_no_manager__imap ( archive_path ,point_cloud , nwn_proc , num.
iterations )

这​​read_meshes_get_distances_pool_imap是一个中心函数,其中完成以下操作:​​

请注意我们如何传递参数以​​imap​​​从​​archive​​​和​​point_cloud​​​使用​​zip(archive, itertools.repeat(point_cloud))​​​. 这允许我们将点云数组粘贴到存档的每个条目上,避免转换​​archive​​为列表。

执行结果如下:

100%|########################################### #####################| 5/5 [00:00<00:00, 5.14it/s]

100%|########################################### #####################| 5/5 [00:00<00:00, 5.08it/s]

100%|########################################### #####################| 5/5 [00:00<00:00, 5.18it/s]

[进程网格:pool.imap w/o manager] 4 个进程的池经过时间:1.0080536206563313 秒

犰狳.obj 0.16176825266293382

野兽.obj 0.28608649819198073

牛.obj 0.41653845909820164

现货.obj 0.22739556571296735

stanford-bunny.obj 2.3699851136074263e-05

我们可以注意到斯坦福兔子是最接近给定点云的网格。还可以看出,我们没有使用大量数据,但我们已经证明,即使我们在存档中有大量网格,该解决方案也能奏效。

多处理使数据科学家不仅在 3D 计算机视觉中而且在机器学习的其他领域中都取得了出色的表现。理解并行执行比在循环内执行快得多是非常重要的。差异变得显着,尤其是在正确编写算法时。大量数据揭示了如果没有关于如何使用有限资源的创造性方法就无法解决的问题。幸运的是,Python 语言及其广泛的库集帮助我们数据科学家解决了这些问题。

加载全部内容

相关教程
猜你喜欢
用户评论