三角網格去噪-DNF_Net論文解讀

三角網格去噪-DNF_Net論文解讀

用三維掃描儀或者深度相機獲取的原始三維模型,都不可避免的含有噪聲。這些含有噪聲的三維模型需要做去噪處理,以便後續應用。三維模型常見的表示形式是點雲或者三角網格,對應的就是點雲去噪、三角網格去噪。

本文講解一篇用深度學習方法做三角網格去噪的方法。

三角網格去噪方法可以分成兩類:第一類是直接移動三角網格中的頂點位置,達到去噪的目的。第二類是先對三角網格中每個三角面片的法向進行調整(也可以稱之爲法向濾波或者法向域去噪),然後根據根據調整後的法向,移動頂點的位置,達到去噪的目的。

從目前來看,第二類方法佔據了主流地位,很多論文也對其爲啥佔主流地位做了分析。

言歸正傳,本次分享的論文DNF_Net也是用深度學習方法在法向域裏面進行濾波,然後根據濾波後的法向,移動頂點的位置,達到去噪的目的。

1 Training patch preparation(訓練樣本準備)

給定一對三角網格(mesh pair),即帶噪聲的網格 M M M和其對應的乾淨無噪聲的 M G M^G MG,通過三步來對數據進行預處理。

  1. 隨機選取三角網格 M M M上的一些面作爲種子面集合,同時上 M G M^G MG也選取相同的種子集合,即網格 M M M與網格 M G M^G MG的種子面集合是相同的。

  2. 對於每個種子面 F i F_i Fi,根據這個面找到離其測地線距離(geodesic distance)最近的 N − 1 N-1 N1個面。將包括種子面在內的 N N N個面的法向量構成一個可訓練的數據塊 N i ∈ R N × 3 N_i \in \mathbb{R}^{N\times3} NiRN×3。(請注意,實際代碼中,是從種子面的n環鄰面中以此遍歷到的N個面,可參考代碼getNeighborFacets函數)
    N i = [ n i 1 n i 2 ⋮ n i N ] N_i = \left[\begin{matrix} n_{i1}\\ n_{i2}\\ \vdots\\ n_{iN} \end{matrix} \right] Ni=ni1ni2niN

  3. 爲了利用塊 N i N_i Ni中每個面的局部信息(或者可以稱爲local structure),對於 N i N_i Ni中的每個面 F i j F_{ij} Fij,記 I i j I_{ij} Iij F i j F_{ij} Fij K K K個最鄰面的索引, I i ∈ R N × K I_i \in \mathbb{R}^{N\times K} IiRN×K。(不像二維規則的圖像,三維曲面是不規則的,在三維空間中進行學習,一個重要的問題就是解決無序性,這裏利用 I i j I_{ij} Iij是一個解決方案,可以借鑑。)

2 Network architecture

框架圖

在這裏插入圖片描述

輸入:採集到的數據對,即 ( ( N i , I i ) ( N i G , I i ) ) ((N_i,I_i)(N_i^G,I_i)) ((Ni,Ii)(NiG,Ii)), 即學習$N_i 到 到 N_i^G$的映射,達到Ni中法向去噪的目的。

  • N i N_i Ni:有噪聲網格 M M M i i i片區域內所有三角面片的法向量,每一行代表一個片的法向量。

  • I i I_i Ii:第 i i i片區域內所有面的 k k k個最鄰近面。

  • N i G N_i^G NiG:無噪聲 M G M^G MG i i i片區域內所有三角面片的法向量,每一行代表一個面片的法向量。

2.1 Multi-scale feature embedding unit

目的:提取局部特徵,以用於編碼局部上下文。

建立一個三級框架從 N i N_i Ni I i I_i Ii中提取法向特徵圖 F i ∈ R N × C \mathcal{F}_i \in \mathbb{R}^{N \times C} FiRN×C。這三級框架中第一級爲法向聚合層(Normal grouping layer)+特性提取層(Feature extraction layer),後兩級均爲特徵聚合層(Feature grouping layer)+特徵提取層。

2.1.1 Normal grouping layer

目的:提取輸入的每個面的局部空間特徵。

爲了更好地說明這一層是如何設計的,結合下圖進行分析。下面標號分別對應與圖中的標號。

在這裏插入圖片描述

  1. N i N_i Ni複製 k 1 k_1 k1份。
  2. 因爲 I i I_i Ii記錄了 N i N_i Ni中每個面的 K K K個最鄰近面的索引,所以可以根據這些索引獲得每個面最鄰近的 k 1 k_1 k1個面的法向量。通俗的描述,如上圖2右邊 k 1 k_1 k1個塊所示,第一個塊就是 N i N_i Ni,第二個塊就是由與 N i N_i Ni中每個面的最鄰近的面的法向量構成的,第三個塊就是由與 N i N_i Ni中每個面的第二鄰近的面的法向量構成的,……,第 k 1 k_1 k1個塊就是由與 N i N_i Ni中每個面的第 k 1 − 1 k_1-1 k11鄰近的面的法向量構成的。
  3. 將1,2對應塊拼接起來。

代碼如下:

def normal_grouping_embedding(input, mlp, is_training, bn_decay, scope, local_size, bn=True, pooling='max'):
    '''
    :param input: [B, N, K, 3]
    :param is_training:
    :param bn_decay:
    :param scope:
    :param bn:
    :param pooling:
    :return: output: [B, N, 1, C']
    '''

    K = input.get_shape()[2].value
    center = input[:, :, 0, :]
    center = tf.expand_dims(center, axis=2)  # [B, N, 1, C]
    center_tile = tf.tile(center, [1, 1, K, 1])  # [B, N, K, C]
    input_concat = tf.concat([center_tile, input], axis=-1)  # [B, N, K, 2C]

    input_local = input_concat[:, :, 0:local_size, :]
    with tf.variable_scope(scope) as sc:
        for i, num_out_channel in enumerate(mlp):
            input_local = tf_util.conv2d(input_local, num_out_channel, [1,1],
                                          padding='VALID', stride=[1,1],
                                          bn=bn, is_training=is_training,
                                          scope='conv%d' % (i), bn_decay=bn_decay)
        if pooling=='max':
            output = tf.reduce_max(input_local, axis=2, keep_dims=True, name='maxpool')  # [B, N, 1, C']
        elif pooling=='avg':
            output = tf.reduce_mean(input_local, axis=2, keep_dims=True, name='avgpool')

        output = cbam_module2(output, is_training=is_training, bn_decay=bn_decay, name="cbam")

        return output
2.1.2 Feature grouping layer

在這裏插入圖片描述

  1. ε i 1 \varepsilon^1_i εi1 ε i 2 \varepsilon^2_i εi2複製 k 2 k_2 k2 k 3 k_3 k3份。
  2. 與 Normal grouping layer 中的2類似。
  3. 將1,2對應塊相減。
  4. 將1,3對應塊拼接起來。

代碼如下:

def feature_grouping_embedding(input, idx, mlp, is_training, bn_decay, scope, local_size, bn=True, pooling='max',size=50):
    '''
    :param input: [B, N, 1, C]
    :param idx: [B, N, 50]
    :param is_training:
    :param bn_decay:
    :param scope:
    :param bn:
    :param pooling:
    :return: output: [B, N, 1, C']
    '''

    input_tile = tf.tile(input, [1, 1, size, 1])  # [B, N, 50, C]

    # find local feature map according to topology indices
    input = tf.squeeze(input, axis=2)
    input_group = group_point(input, idx)  # [B, N, 50, C]
    input_concat = tf.concat([input_tile, input_group-input_tile], axis=-1)  # [B, N, 50, 2C]
    input_local = input_concat[:, :, 0:local_size, :]

    with tf.variable_scope(scope) as sc:
        for i, num_out_channel in enumerate(mlp):
            input_local = tf_util.conv2d(input_local, num_out_channel, [1,1],
                                          padding='VALID', stride=[1,1],
                                          bn=bn, is_training=is_training,
                                          scope='conv%d' % (i), bn_decay=bn_decay)

        if pooling=='max':
            output = tf.reduce_max(input_local, axis=2, keep_dims=True, name='maxpool')  # [B, N, 1, C']
        elif pooling=='avg':
            output = tf.reduce_mean(input_local, axis=2, keep_dims=True, name='avgpool')

        output = cbam_module2(output, is_training=is_training, bn_decay=bn_decay, name="cbam")

        return output

2.1.3 Feature extraction layer

提取額外特徵的方法有很多,比如PointNet++PCN、圖卷積神經網絡等,本文作者採用CBAM

之所以採用CBAM,是因爲可以學習每個通道的權重,然後用這些權重來調整每個通道的重要程度。

2.2 Residual learning unit

提取噪聲特徵 Δ F i \Delta\mathcal{F}_i ΔFi,以便獲得去噪後的特徵圖 F i ~ = F i − Δ F \tilde{\mathcal{F}_i}=\mathcal{F}_i- \Delta\mathcal{F} Fi~=FiΔF

爲了更好地提取用於去噪的特徵,需要對局部特徵進行編碼。

框架如圖所示:

在這裏插入圖片描述

框架圖基本上 Feature grouping layer 層類似,不同的是:

  1. 在特徵空間中,不再使用 I i I_i Ii,而是使用KNN(文中 k k k取20)。(實際代碼中使用的是DGCNN
  2. 在 concat 後使用了兩次MLP,然後使用 max pooling 獲得殘差特徵圖 Δ F i ∈ R N × C \Delta\mathcal{F}_i\in\mathbb{R}^{N\times C} ΔFiRN×C

部分代碼如下:

########### denoise level 1 ###############
res_coarse = model_util.dgcnn(net_noisy, [128, 128], is_training, bn_decay, 'res_coarse', bn=True)  # [B, N, 1, 128]

net_clean_coarse = tf.subtract(net_noisy, res_coarse)  # [B, N, 1, 128]

## regress to three normal vectors
normal_coarse = tf_util.conv2d(net_clean_coarse, 128, [1, 1], padding='VALID', stride=[1, 1], bn=True,
                                is_training=is_training, bn_decay=bn_decay, scope='regress_coarse_1')
normal_coarse = tf_util.conv2d(normal_coarse, 64, [1, 1], padding='VALID', stride=[1, 1], bn=True,
                                is_training=is_training, bn_decay=bn_decay, scope='regress_coarse_2')
normal_coarse = tf_util.conv2d(normal_coarse, 3, [1, 1], padding='VALID', stride=[1, 1],
                                activation_fn=None, scope='regress_coarse_3')
normal_coarse = tf.squeeze(normal_coarse, axis=2)  # [B, N, 3]

## magnitude of the normal vector should be 1
normal_coarse = tf.nn.l2_normalize(normal_coarse, dim=2)
############################################

########### denoise level 2 ###############
res_fine = model_util.dgcnn(net_clean_coarse, [128, 128], is_training, bn_decay, 'res_fine',
                            bn=True)  # [B, N, 1, 128]

net_clean_fine = tf.subtract(net_clean_coarse, res_fine)  # [B, N, 1, 128]

net_clean = tf.add(net_clean_coarse, net_clean_fine)

## regress to three normal vectors
normal_fine = tf_util.conv2d(net_clean, 128, [1, 1], padding='VALID', stride=[1, 1], bn=True,
                                is_training=is_training, bn_decay=bn_decay, scope='regress_fine_1')
normal_fine = tf_util.conv2d(normal_fine, 64, [1, 1], padding='VALID', stride=[1, 1], bn=True,
                                is_training=is_training, bn_decay=bn_decay, scope='regress_fine_2')
normal_fine = tf_util.conv2d(normal_fine, 3, [1, 1], padding='VALID', stride=[1, 1],
                                activation_fn=None, scope='regress_fine_3')
normal_fine = tf.squeeze(normal_fine, axis=2)  # [B, N, 3]

## magnitude of the normal vector should be 1
normal_fine = tf.nn.l2_normalize(normal_fine, dim=2)

3 Deeply-supervised end-to-end training

在這裏插入圖片描述

3.1 Deeply-supervised normal recovery loss

使用中間輸出 N ~ i 1 \tilde{N}^1_i N~i1和最終輸出 N ~ i \tilde{N}_i N~i來進行雙重監督。
L d e e p = 1 N p ∑ i = 1 N P ( ∥ N i G − N i ~ ∥ 2 + ∥ N i G − N i 1 ~ ∥ 2 ) L_{deep} = \frac{1}{N_p}\sum^{N_P}_{i=1}(\parallel\mathcal{N}^G_i-\tilde{\mathcal{N}_i}\parallel^2+ \parallel\mathcal{N}^G_i-\tilde{\mathcal{N}^1_i} \parallel^2) Ldeep=Np1i=1NP(NiGNi~2+NiGNi1~2)
其中, N p N_p Np是訓練的塊數。

3.2Residual regularization loss

從理論上說, Δ F i 1 \Delta\mathcal{F}^1_i ΔFi1 Δ F i 2 \Delta\mathcal{F}^2_i ΔFi2 F i \mathcal{F}_i Fi的小部分,所以這兩個值不應該太大。
L r e s i f u a l = 1 N p ∑ i = 1 N p ( ∥ Δ F i 1 ∥ 2 + ∥ Δ F i 2 ∥ 2 ) L_{resifual} =\frac{1}{N_p}\sum^{N_p}_{i=1}(\parallel\Delta\mathcal{F}^1_i\parallel^2+\parallel\Delta\mathcal{F}^2_i\parallel^2) Lresifual=Np1i=1Np(ΔFi12+ΔFi22)

3.3 Joint loss

聯合損失函數:
L = L d e e p + α L r e s i d u a l L=L_{deep}+\alpha L_{residual} L=Ldeep+αLresidual
文中 α \alpha α取0.5。

4 優缺點

4.1 優點

  1. 將三角網格分成多個面數相等的塊,能夠直接輸入網絡中進行訓練。
  2. 提出了一個DNF_Net,可以訓練三角網格數據。

4.2 缺點

產生的結果對數據的依賴性較強

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