利用Python多處理庫處理3D數據詳解

今天我們將介紹處理大量數據時非常方便的工具。我不會隻告訴您可能在手冊中找到的一般信息,而是分享一些我發現的小技巧,例如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是一個中心函數,其中完成以下操作:​​

  • MeshesArchive​​並​​multiprocessing.Pool​​初始化
  • ​​tqdm​​ 用於觀察池進度,並手動完成整個池的分析
  • 執行結果的輸出

請註意我們如何傳遞參數以​​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 語言及其廣泛的庫集幫助我們數據科學傢解決瞭這些問題。

到此這篇關於利用Python多處理庫處理3D數據詳解的文章就介紹到這瞭,更多相關Python處理3D數據內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: