当前位置:网站首页>三角网格去噪-DNF_Net论文解读

三角网格去噪-DNF_Net论文解读

2020-12-06 08:36:36 osc_veyfyz58

三角网格去噪-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 缺点

产生的结果对数据的依赖性较强

版权声明
本文为[osc_veyfyz58]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4416364/blog/4776550