三维点云网络PointNet——模型及代码分析

PointNet架构

PointNet主要架构如下图所示:
pointNet
主要包含了点云对齐/转换、mlp学习、最大池化得到全局特征三个主要的部分。

-T-Net用于将不同旋转平移的原始点云和点云特征进行规范化;

  • mlp是多层感知机,n个共享的mlp用于处理n个点/特征
  • max pooling 用于融合多个特征并得到全局的1024维的特征;
  • 最后根据任务的不同,利用一个MLP实现分类;结合局部信息利用多个mlp实现分割
    haha|left
1.T-Net

首先我们来看T-Net模型的代码,它的主要作用是学习出变化矩阵来对输入的点云或特征进行规范化处理。其中包含两个函数,分别是
学习点云变换矩阵的:input_transform_net(point_cloud, is_training, bn_decay=None, K=3)
学习特征变换矩阵的:feature_transform_net(inputs, is_training, bn_decay=None, K=64)

def input_transform_net(point_cloud, is_training, bn_decay=None, K=3):
    """ Input (XYZ) Transform Net, input is BxNx3 gray image
        Return:
            Transformation matrix of size 3xK """
    batch_size = point_cloud.get_shape()[0].value
    num_point = point_cloud.get_shape()[1].value

    input_image = tf.expand_dims(point_cloud, -1)		#转为4D张量
    #构建T-Net模型,64--128--1024
    net = tf_util.conv2d(input_image, 64, [1,3],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv1', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv2', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 1024, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv3', bn_decay=bn_decay)
    net = tf_util.max_pool2d(net, [num_point,1],
                             padding='VALID', scope='tmaxpool')

	#利用1024维特征生成256维度的特征
    net = tf.reshape(net, [batch_size, -1])
    net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,
                                  scope='tfc1', bn_decay=bn_decay)
    net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,
                                  scope='tfc2', bn_decay=bn_decay)

mlp网络的定义如上,其输入为点云数据,每一个点云作为一个batch。

  • 首先将三通道的点云拓展为4-D的张量,tf.expend_dims(),ref,将得到batchn3*1的数据作为网络的输入;
  • 随后构建网络,利用1*1的卷积来实现全连接。每一层单元依次为64-128-1024-512-256的网络结构;
    在这里插入图片描述
    接下来需要将mlp得到的256维度特征进行处理,以输出3*3的旋转矩阵:
	#生成点云旋转矩阵 T=3*3
    with tf.variable_scope('transform_XYZ') as sc:
        assert(K==3)
        weights = tf.get_variable('weights', [256, 3*K],
                                  initializer=tf.constant_initializer(0.0),
                                  dtype=tf.float32)
        biases = tf.get_variable('biases', [3*K],
                                 initializer=tf.constant_initializer(0.0),
                                 dtype=tf.float32)
        biases += tf.constant([1,0,0,0,1,0,0,0,1], dtype=tf.float32)
        transform = tf.matmul(net, weights)
        transform = tf.nn.bias_add(transform, biases)

    transform = tf.reshape(transform, [batch_size, 3, K])
    return transform

通过定义权重[W(256,3*K), bais(3*K)],将上面的256维特征转变为3*3旋转矩阵输出。
haha|left
同样对于特征层的规范化处理,其输入为n*64的特征输出为64*64的旋转矩阵,网络结构与上面完全相同,只是在输入输出的维度需要变化:

def feature_transform_net(inputs, is_training, bn_decay=None, K=64):
    """ Feature Transform Net, input is BxNx1xK
        Return:
            Transformation matrix of size KxK """
    batch_size = inputs.get_shape()[0].value
    num_point = inputs.get_shape()[1].value
	
	#构建T-Net模型,64--128--1024
    net = tf_util.conv2d(inputs, 64, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv1', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv2', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 1024, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='tconv3', bn_decay=bn_decay)
    net = tf_util.max_pool2d(net, [num_point,1],
                             padding='VALID', scope='tmaxpool')

    net = tf.reshape(net, [batch_size, -1])
    net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,
                                  scope='tfc1', bn_decay=bn_decay)
    net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,
                                  scope='tfc2', bn_decay=bn_decay)
	
	#生成特征旋转矩阵 T=64*64
    with tf.variable_scope('transform_feat') as sc:
        weights = tf.get_variable('weights', [256, K*K],
                                  initializer=tf.constant_initializer(0.0),
                                  dtype=tf.float32)
        biases = tf.get_variable('biases', [K*K],
                                 initializer=tf.constant_initializer(0.0),
                                 dtype=tf.float32)
        biases += tf.constant(np.eye(K).flatten(), dtype=tf.float32)
        transform = tf.matmul(net, weights)		
        transform = tf.nn.bias_add(transform, biases)

    transform = tf.reshape(transform, [batch_size, K, K])
    return transform

在这里插入图片描述
mlp网络定义每一层的神经元数量为64--128--512--256。同样在得到256维的特征后利用weight(256*K*K), bais(K*K)来计算出K*K特征旋转矩阵,其中K为64,为默认输出特征数量。
代码链接

2.MLP处理点云

在得到点云的规范化选择矩阵后,将原始输入进行处理。旋转后的点云point_cloud_transformed作为MLP的输入抽取特征。
在这里插入图片描述
此时输入是旋转后的点云,并通过一个两层的mlp(64--64)得到了64维的特征;

    transform = input_transform_net(point_cloud, is_training, bn_decay, K=3)    # 预测出旋转矩阵T
    point_cloud_transformed = tf.matmul(point_cloud, transform)		  #原始点云乘以旋转矩阵,得到矫正后点云
    input_image = tf.expand_dims(point_cloud_transformed, -1)

	#构建感知机  两层64--64
    net = tf_util.conv2d(input_image, 64, [1,3],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv1', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 64, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv2', bn_decay=bn_decay)

随后利用特征旋转矩阵transform对特征进行规范化处理,得到校正后的特征。此时的新特征net_transformed将输入到下一个MLP中进行处理

    with tf.variable_scope('transform_net2') as sc:
        transform = feature_transform_net(net, is_training, bn_decay, K=64)    #预测出旋转矩阵T
    end_points['transform'] = transform
    net_transformed = tf.matmul(tf.squeeze(net, axis=[2]), transform)    #特征乘以旋转矩阵,得到矫正后特征
    net_transformed = tf.expand_dims(net_transformed, [2])
3.MLP处理特征

对于处理点云后的特征需要利用另一个多层感知机进行处理,将表示每个点的输入64维的向量表示为1024维输出:
在这里插入图片描述

	#构建mlp 64-128-1024
    net = tf_util.conv2d(net_transformed, 64, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv3', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv4', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 1024, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv5', bn_decay=bn_decay)

这部分包含三层mlp(64--128--1024),最终输出n*1024维度的特征矩阵.
haha|left

4.Max Pooling(对称函数)得到全局特征

此时每个输入点从三维变成了1024维的表示,此时需要对n个点所描述的点云进行融合处理以得到全局特征,源码中使用了最大池化层来实现这一功能:
在这里插入图片描述

    # Symmetric function: max pooling
    # 最大池化,二维的池化函数对点云中点的数目这个维度进行池化,n-->1
    net = tf_util.max_pool2d(net, [num_point,1],
                             padding='VALID', scope='maxpool')

输出为全局特征[num_point,1]表示将每一个点云的n个点最大池化为1个特征,这个特征的长度为1024。此时通过了两次mlp的处理将一个点云的特征逐点进行描述,并合并到了1024维的全局特征上来。
haha|left

5.分类

利用上面的1024维特征,就可以基于这一特征对点云的特性进行学习实现分类任务,PointNet利用了一个三层感知机MLP(512--256--40)来对特征进行学习,最终实现了对于40类的分类.
在这里插入图片描述

    net = tf.reshape(net, [batch_size, -1])

    # 定义分类的mlp512-256-k, k为分类类别数目
    net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,
                                  scope='fc1', bn_decay=bn_decay)
    net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training,
                          scope='dp1')
    net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,
                                  scope='fc2', bn_decay=bn_decay)
    net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training,
                          scope='dp2')
    net = tf_util.fully_connected(net, 40, activation_fn=None, scope='fc3')

这一感知机由全连接层组成,其中包含了两个dropout=0.7防止过拟合。最终就可以根据输出K个分类值分数的大小来确定输入点云的分类了。
源码链接
haha|left

6.分割

对于分割任务,需要加入局域信息来进行学习。所以分类任务的输入包括了1024维的全局信息还包括了n*64的从点云直接学习出的局部信息。PointNet的做法是将全局信息附在每一个局部点描述的后面,形成了1024+64=1088维的向量,而后通过两个感知机来进行分割:
在这里插入图片描述

   global_feat_expand = tf.tile(global_feat, [1, num_point, 1, 1])
    concat_feat = tf.concat(3, [point_feat, global_feat_expand])

新得到的特征concat_feat输入两个连续的感知机mlp(512,256),mlp(128,m)都通过1*1卷积实现:

    # 定义分割的mlp512-256-128  128-m, m为点所属的类别数目
    net = tf_util.conv2d(concat_feat, 512, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv6', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 256, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv7', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv8', bn_decay=bn_decay)
    net = tf_util.conv2d(net, 128, [1,1],
                         padding='VALID', stride=[1,1],
                         bn=True, is_training=is_training,
                         scope='conv9', bn_decay=bn_decay)

    net = tf_util.conv2d(net, 50, [1,1],
                         padding='VALID', stride=[1,1], activation_fn=None,
                         scope='conv10')
    net = tf.squeeze(net, [2]) # BxNxC

由于点云的分割问题可以看做是对于每一个点的分类问题,需要对每一个点的分类进行预测。在通过对全局+局部特征学习后,最后将每一个点分类到50类中,并输出n*50的输出。
代码链接
haha|left

论文中最后的效果如下:
在这里插入图片描述

Full code from: https://github.com/charlesq34/pointnet
插图来自pics.sc.chinaz.com

### PointNet 点云重建实现与应用 #### 1. PointNet概述及其适用场景 PointNet是一种用于直接处理原始点云数据的深度学习模型,能够有效捕捉三维空间中的几何特征。该网络利用多层感知器(MLP)来提取单个点的局部特征,并采用最大池化操作确保整个系统的置换不变性[^3]。 #### 2. T-Net辅助下的姿态矫正机制 为了克服传统PointNet面对旋转变化敏感的问题,在实际应用中通常会加入一个小规模子网——T-Net。这个组件专门负责估计并纠正输入点集的整体方位偏差,使得后续主干部分(PointNet vanilla)可以在标准化坐标系下更稳定地执行分类或分割任务[^5]。 #### 3. 基于PointNet点云重建流程说明 虽然PointNet本身主要用于分类和分段任务,但其核心理念同样适用于构建更加复杂的点云重建框架: - **预处理阶段**:读取目标对象的离散采样点作为初始输入;必要时可通过体素网格滤波等方式减少冗余样本数量。 - **特征提取模块**:运用标准配置下的PointNet结构获取全局描述符以及各节点对应的局部嵌入向量。 - **上采样/插值单元**:针对稀疏区域补充额外细节信息,形成稠密化的中间表达形式。 - **最终输出层**:将上述得到的结果映射回欧几里得空间位置参数,从而完成高质量表面恢复过程。 ```python import torch.nn as nn from pointnet_util import STNkd, feature_transform_reguliarzer class PointReconstruction(nn.Module): def __init__(self, k=3): super().__init__() self.stn = STNkd(k=k) self.conv1 = nn.Conv1d(k, 64, kernel_size=1) ... # 定义更多卷积层和其他必要的组成部分 def forward(self, x): trans = self.stn(x) x = x.transpose(2, 1).contiguous() x = F.relu(self.conv1(x)) ... # 继续定义前馈路径直至返回重构后的点云张量 return reconstructed_points ``` 此代码片段展示了如何创建一个简单的基于PointNet架构的类来进行点云重建工作。需要注意的是这只是一个简化版本的实际系统可能会涉及更多的优化措施和技术细节调整以适应特定应用场景的需求。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值