PolyGen生成三維模型【深度學習】

本文將介紹如何利用深度學習技術生成3D模型,使用了PyTorch和PolyGen。 在這裏插入圖片描述

1、概述

有一個新興的深度學習研究領域專注於將 DL 技術應用於 3D 幾何和計算機圖形應用程序,這一長期研究的集合證明了這一點。對於希望自己嘗試一些 3D 深度學習的 PyTorch 用戶,Kaolin 庫值得研究。對於 TensorFlow 用戶,還有TensorFlow Graphics。一個特別熱門的子領域是 3D 模型的生成。創造性地組合 3D 模型、從圖像快速生成 3D 模型以及爲其他機器學習應用程序和模擬創建合成數據只是 3D 模型生成的無數用例中的一小部分。

然而,在 3D 深度學習研究領域,爲你的數據選擇合適的表示是成功的一半。在計算機視覺中,數據的結構非常簡單:圖像由密集的像素組成,這些像素整齊均勻地排列成精確的網格。3D 數據的世界沒有這種一致性。3D 模型可以表示爲體素、點雲、網格、多視圖圖像集等。這些輸入表示也都有自己的一組缺點。例如,體素儘管計算成本很高,但輸出分辨率很低。點雲不編碼表面或其法線的概念,因此不能僅從點雲唯一地推斷出拓撲。網格也不對拓撲進行唯一編碼,因爲任何網格都可以細分以產生相似的表面。PolyGen,一種用於網格的神經生成模型,它聯合估計模型的面和頂點以直接生成網格。DeepMind GitHub 上提供了官方實現。

2、研究

現在經典的PointNet論文爲點雲數據建模提供了藍圖,例如 3D 模型的頂點。它是一種通用算法,不對 3D 模型的面或佔用進行建模,因此無法單獨使用 PointNet 生成獨特的防水網格。3D-R2N2採用的體素方法將我們都熟悉的 2D 卷積擴展到 3D,並自然地從 RGB 圖像生成防水網格。然而,在更高的空間分辨率下,體素表示的計算成本很高,有效地限制了它可以產生的網格的大小。

Pixel2Mesh可以通過變形模板網格(通常是橢圓體)從單個圖像預測 3D 模型的頂點和麪。目標模型必須與模板網格同胚,因此使用凸模板網格(例如橢圓體)會在椅子和燈等高度非凸的物體上引入許多假面。拓撲修改網絡(TMN) 通過引入兩個新階段在 Pixel2Mesh 上進行迭代:拓撲修改階段用於修剪會增加模型重建誤差的錯誤面,以及邊界細化階段以平滑由面修剪引入的鋸齒狀邊界。如果你有興趣,我強烈建議同時查看AtlasNet和Hierarchical Surface Prediction。

在這裏插入圖片描述

雖然變形和細化模板網格的常用方法表現良好,但它始於對模型拓撲的主要假設。就其核心而言,3D 模型只是 3D 空間中的一組頂點,通過各個面進行分組和連接在一起。是否可以避開中間表示並直接預測這些頂點和麪?

3、PolyGen

在這裏插入圖片描述

PolyGen 通過將 3D 模型表示爲頂點和麪的嚴格排序序列,而不是圖像、體素或點雲,對模型生成任務採取了一種相當獨特的方法。這種嚴格的排序使他們能夠應用基於注意力的序列建模方法來生成 3D 網格,就像 BERT 或 GPT 模型對文本所做的那樣。

PolyGen 的總體目標有兩個:首先爲 3D 模型生成一組合理的頂點(可能以圖像、體素或類標籤爲條件),然後生成一系列面,一個接一個,連接頂點在一起,併爲此模型提供一個合理的表面。組合模型將網格上的分佈p(M)表示爲兩個模型之間的聯合分佈:頂點模型p(V)表示頂點,面模型p(F|V)表示以頂點爲條件的面。 在這裏插入圖片描述

頂點模型是一個解碼器,它試圖預測以先前標記爲條件的序列中的下一個標記(並且可選地以圖像、體素字段或類標籤爲條件)。人臉模型由一個編碼器和一個解碼器指針網絡組成,該網絡表示頂點序列上的分佈。該指針網絡一次有效地“選擇”一個頂點,以添加到當前面序列並構建模型的面。該模型以先前的人臉序列和整個頂點序列爲條件。由於 PolyGen 架構相當複雜並且依賴於各種概念,因此本文將僅限於頂點模型。

4、預處理頂點

流行的ShapeNetCore數據集中的每個模型都可以表示爲頂點和麪的集合。每個頂點由一個 (x, y, z) 座標組成,該座標描述了 3D 網格中的一個點。每個面都是一個索引列表,指向構成該面角的頂點。對於三角形面,此列表長 3 個索引。對於 n 邊形面,此列表是可變長度的。原始數據集非常大,因此爲了節省時間,我在此處提供了一個更輕量級的預處理數據集子集供你進行實驗。該子集僅包含來自 5 個形狀類別的模型,並且在轉換爲 n 邊形後少於 800 個頂點(如下所述)。

爲了使序列建模方法發揮作用,數據必須以一種受約束的、確定性的方式表示,以儘可能多地消除可變性。出於這個原因,作者對數據集進行了一些簡化。首先,他們將所有輸入模型從三角形(連接 3 個頂點的面)轉換爲 n 邊形(連接 n 個頂點的面),使用Blender 的平面抽取修改器合併面。這爲相同的拓撲提供了更緊湊的表示,並減少了三角剖分中的歧義,因爲大型網格並不總是具有唯一的三角剖分。爲了篇幅的緣故,我不會在這篇文章中討論 Blender 腳本,但有很多資源,包括官方文檔和GitHub 上的這套優秀示例,很好地涵蓋了這個主題。我提供的數據集已經預先抽取。

在這裏插入圖片描述

要繼續進行,請下載此示例 cube.obj 文件。這個模型是一個基本的立方體,有 8 個頂點和 6 個面。以下簡單代碼片段從單個 .obj 文件中讀取所有頂點。

def load_obj(filename):
  """Load vertices from .obj wavefront format file."""
  vertices = []
  with open(filename, 'r') as mesh:
    for line in mesh:
      data = line.split()
      if len(data) > 0 and data[0] == 'v':
        vertices.append(data[1:])
  return np.array(vertices, dtype=np.float32)
verts = load_obj(cube_path)
print('Cube Vertices')
print(verts)

其次,頂點首先從它們的 z 軸(在這種情況下爲垂直軸)按升序排序,然後是 y 軸,最後是 x 軸。這樣,模型頂點是自下而上表示的。在 vanilla PolyGen 模型中,然後將頂點連接成一維序列向量,對於較大的模型,該向量最終會得到一個非常長的序列向量。作者在論文的附錄 E 中描述了一些減輕這種負擔的修改。

要對一系列頂點進行排序,我們可以使用字典排序。這與對字典中的單詞進行排序時採用的方法相同。要對兩個單詞進行排序,您將查看第一個字母,然後如果有平局,則查看第二個字母,依此類推。對於“aardvark”和“apple”這兩個詞,第一個字母是“a”和“a”,所以我們移動到第二個字母“a”和“p”來告訴我“aardvark”在“apple”之前。在這種情況下,我們的“字母”是按順序排列的 z、y 和 x 座標。

verts_keys = [verts[..., i] for i in range(verts.shape[-1])] 
sort_idxs = np.lexsort(verts_keys) 
verts_sorted = verts[sort_idxs]

最後,頂點座標被歸一化,然後被量化以將它們轉換爲離散的 8 位值。這種方法已在像素遞歸神經網絡和WaveNet中用於對音頻信號進行建模,使它們能夠對頂點值施加分類分佈。在最初的WaveNet論文中,作者評論說“分類分佈更靈活,並且可以更容易地對任意分佈進行建模,因爲它不對它們的形狀做任何假設。” 這種質量對於建模複雜的依賴關係很重要,例如 3D 模型中頂點之間的對稱性。

# normalize vertices to range [0.0, 1.0]
lims = [-1.0, 1.0]
norm_verts = (verts - lims[0]) / (lims[1] - lims[0])
# quantize vertices to integers in range [0, 255]
n_vals = 2 ** 8
delta = 1. / n_vals
quant_verts = np.maximum(np.minimum((norm_verts // delta), n_vals - 1), 0).astype(np.int32)

5、頂點模型

頂點模型由一個解碼器網絡組成,它具有變壓器模型的所有標準特徵:輸入嵌入、18 個變壓器解碼器層的堆棧、層歸一化,最後是在所有可能的序列標記上表示的 softmax 分佈。給定一個長度爲N的扁平頂點序列Vseq,其目標是在給定模型參數的情況下最大化數據序列的對數似然: 在這裏插入圖片描述

與 LSTM 不同的是,transformer 模型能夠以並行方式處理順序輸入,同時仍使來自序列一部分的信息能夠爲另一部分提供上下文。這一切都歸功於他們的注意力模塊。3D 模型的頂點包含各種對稱性和遠點之間的複雜依賴關係。例如,考慮一個典型的桌子,其中模型對角的腿是彼此的鏡像版本。注意力模塊允許對這些類型的模式進行建模。

6、輸入嵌入

嵌入層是序列建模中用於將有限數量的標記轉換爲特徵集的常用技術。在語言模型中,“國家”和“民族”這兩個詞的含義可能非常相似,但與“蘋果”這個詞卻相距甚遠。當單詞用唯一的標記表示時,就沒有相似性或差異性的固有概念。嵌入層將這些標記轉換爲矢量表示,可以對有意義的距離感進行建模。

PolyGen 將同樣的原理應用於頂點。該模型使用三種類型的嵌入層:座標表示輸入標記是 x、y 還是 z 座標,值表示標記的值,以及位置編碼頂點的順序。每個都向模型傳達有關令牌的一條信息。由於我們的頂點一次在一個軸上輸入,座標嵌入爲模型提供了基本的座標信息,讓它知道給定值對應的座標類型。

coord_tokens = np.concatenate(([0], np.arange(len(quant_verts)) % 3 + 1, (n_padding + 1) * [0]))

值嵌入對我們之前創建的量化頂點值進行編碼。我們還需要一些序列控制點:額外的開始和停止標記分別標記序列的開始和結束,並將標記填充到最大序列長度。

TOKENS = {
  '<pad>': 0,
  '<sos>': 1,
  '<eos>': 2
}
max_verts = 12 # set low for prototyping
max_seq_len = 3 * max_verts + 2 # num coords + start & stop tokens
n_tokens = len(TOKENS)
seq_len = len(quant_verts) + 2
n_padding = max_seq_len - seq_len
val_tokens = np.concatenate((
  [TOKENS['<sos>']],
  quant_verts + n_tokens,
  [TOKENS['<eos>']],
  n_padding * [TOKENS['<pad>']]
))

由於並行化而丟失的給定序列位置n的位置信息通過位置嵌入來恢復。也可以使用位置編碼,一種不需要學習的封閉形式的表達。在經典的 Transformer 論文“ Attention Is All You Need ”中,作者定義了一種由不同頻率的正弦和餘弦函數組成的位置編碼。他們通過實驗確定位置嵌入的性能與位置編碼一樣好,但編碼的優勢在於比訓練中遇到的序列更長。有關位置編碼的出色視覺解釋,請查看此博客文章

pos_tokens = np.arange(len(quant_tokens), dtype=np.int32)

生成所有這些標記序列後,最後要做的是創建一些嵌入層並將它們組合起來。每個嵌入層都需要知道期望的輸入字典的大小和輸出的嵌入維度。每層的嵌入維數爲 256,這意味着我們可以將它們與加法相結合。字典大小取決於輸入可以具有的唯一值的數量。對於值嵌入,它是量化值的數量加上控制標記的數量。對於座標嵌入,對於每個座標 x、y 和 z,它是一個,對於上述任何一個(控制標記)都不是一個。最後,位置嵌入對於每個可能的位置或最大序列長度都需要一個。

n_embedding_channels = 256
# initialize value embedding layer
n_embeddings_value = 2 ** n_bits + n_tokens
value_embedding = torch.nn.Embedding(n_embeddings_value,
  n_embedding_channels, padding_idx=TOKENS['<pad>'])
# initialize coordinate embedding layer
n_embeddings_coord = 4
coord_embedding = torch.nn.Embedding(n_embeddings_coord,
  n_embedding_channels)
# initialize position embedding layer
n_embeddings_pos = max_seq_len
pos_embedding = torch.nn.Embedding(n_embeddings_pos,
  n_embedding_channels)
# pass through layers
value_embed = self.value_embedding(val_tokens)
coord_embed = self.coord_embedding(coord_tokens)
pos_embed = self.pos_embedding(pos_tokens)
# merge
x = value_embed + coord_embed + pos_embed

7、序列掩蔽

Transformer模型如此並行化的另一個後果是什麼?對於時間n的給定輸入標記,模型實際上可以“看到”序列後面的目標值,當你嘗試僅根據先前的序列值調整模型時,這會成爲一個問題。爲了防止模型關注無效的未來目標值,可以-Inf在 self-attention 層中的 softmax 步驟之前屏蔽未來位置。

n_seq = len(val_tokens) 
mask_dims = (n_seq, n_seq) 
target_mask = torch.from_numpy( 
  (val_tokens != TOKENS['<pad>'])[..., np.newaxis] \ 
  & (np.triu(np.個(mask_dims),k=1).astype('uint8')== 0))

PolyGen 還廣泛使用無效預測掩碼來確保其生成的頂點和麪部序列編碼有效的 3D 模型。例如,必須強制執行諸如“z 座標不遞減”和“停止標記只能出現在完整頂點(z、y 和 x 標記的三元組)之後”之類的規則,以防止模型產生無效的網格. 作者在論文的附錄 F 中提供了他們使用的掩蔽的廣泛列表。這些約束僅在預測時強制執行,因爲它們實際上會損害訓練性能。

8、核取樣

與許多序列預測模型一樣,該模型是自迴歸的,這意味着給定時間步的輸出是下一個時間步的可能值的分佈。整個序列一次預測一個標記,模型在每一步都會查看先前時間步驟中的所有標記以選擇其下一個標記。解碼策略決定了它如何從這個分佈中選擇下一個Token。

如果使用次優解碼策略,生成模型有時會陷入重複循環或產生質量較差的序列。我們都看到生成的文本看起來像是胡說八道。PolyGen 採用稱爲核採樣的解碼策略來生成高質量序列。原始論文在文本生成上下文中應用了這種方法,但它也可以應用於頂點。前提很簡單:僅從 softmax 分佈中共享 top-p 概率質量的標記中隨機抽取下一個標記。這在推理時應用以生成網格,同時避免序列退化。有關核採樣的 PyTorch 實現,請參閱此要點

9、條件輸入

除了無條件生成模型外,PolyGen 還支持使用類標籤、圖像和體素進行輸入調節。這些可以指導生成具有特定類型、外觀或形狀的網格。類標籤通過嵌入投影,然後添加到每個注意力塊中的自注意力層之後。對於圖像和體素,編碼器創建一組嵌入,然後用於與轉換器解碼器的交叉注意。

10、結論

PolyGen 模型描述了一個強大、高效和靈活的框架,用於有條件地生成 3D 網格。序列生成可以在各種條件和輸入類型下完成,從圖像到體素到簡單的類標籤,甚至只是一個起始標記。表示網格頂點分佈的頂點模型只是聯合分佈難題的一部分。我打算在以後的文章中介紹面部模型。同時,我鼓勵你查看DeepMind 的 TensorFlow 實現,並嘗試生成條件模型!


原文鏈接:深度學習生成3D模型 — BimAnt

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章