Pointnet网络结构与代码解读

article/2025/9/14 4:18:25

前言

Pointnet开创性地将深度学习直接用于三维点云任务。由于点云数据的无序性,无法直接对原始点云使用卷积等操作。Pointnet提出对称函数来解决点的无序性问题,设计了能够进行分类和分割任务的网络结构,本文结合源码与个人的理解对于T-net网络和对称函数进行分析。

点的无序性

针对点的无序性问题实际上是文章提出了三个方案:

  • 对于无序点集进行排序(Pointcnn)。
  • 把点集当做序列进行处理,但是这种方法需要对输入点集做所有的排列变换进行数据增强。
  • 使用对称函数,Pointnet使用的就是这种方法。

Pointnet网络主要使用对称函数解决点的无序性问题,对称函数就是指对输入顺序不敏感的函数。如加法、点乘、max pooling等操作。假设输入特征为NxD, N N N表示点数, D D D表示维度数,在max pooling作用下,取出每个维度上最大值的1xD的向量,每一维特征都与其顺序无关,这样便保证了对于点云输入顺序的鲁棒性。

点云的旋转不变性

Pointnet的解决方法是学习一个变换矩阵 T T T,即 T − N e t T-Net TNet结构。由于loss的约束,使得 T T T矩阵训练会学习到最有利于最终分类的变换,如把点云旋转到正面。论文的架构中,分别在输入数据后和第一层特征中使用了 T T T矩阵,大小为3x3和64x64。其中第二个T矩阵由于参数过多,考虑添加正则项,使其接近于正交矩阵,减少点云的信息丢失。

1. T-Net网络结构

将输入的点云数据作为nx3x1单通道图像,接三次卷积和一次池化后,再reshape为1024个节点,然后接两层全连接,网络除最后一层外都使用了ReLU激活函数和批标准化(batch normalization)。

论文中的T-net网络的实际结构并不复杂,我根据个人理解画出T-net的结构。
在这里插入图片描述
实际训练过程中,T矩阵的参数初始化使用单位矩阵(np.eye(K)), 参数会随着整个网络的训练进行更新,并不是提前单独训练的。很多文章提到T-Net对特征进行对齐,保证了模型的对特定空间转换的不变性,我其实不太理解这种说法。

实际上通过网络结构看出T-net结构是一个mini的Pointnet做特征提取,是个弱监督学习设计,我理解为需要训练一个矩阵对输入点(或者深层特征)进行坐标变换,个人认为这样的设计实际上是可以保留原始点云的部分特征,为后面的concat操作提供更多特征。源码中在点云分类部分使用到了 T − n e t T-net Tnet,点云分割部分可以不用,对结果并没有太大的提升,原因在于pointnet结构自身不能学到点云点的局部联系,因此即使加入类似结构的T-net也是一样。

  • models/transform_nets.py中的网络实现
def input_transform_net(point_cloud, is_training, bn_decay=None, K=3):""" Input: BxNx3 B=batch size;N=number of pointcloudOutput: 3x3 matrix"""batch_size = point_cloud.get_shape()[0].valuenum_point = point_cloud.get_shape()[1].valueinput_image = tf.expand_dims(point_cloud, -1) # 扩展一维表示通道C,BxNx3x1# 输入BxNx3x1# 64个1x3卷积核 (参数[1,3]定义)# 移动步长 1x1 (stride=[1,1]定义)# 输出 BxNx1x64net = 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)# 输入 BxNx1x64# 128个 1x1 卷积核# 步长 1x1# 输出 BxNx1x128net = tf_util.conv2d(net, 128, [1,1],padding='VALID', stride=[1,1],bn=True, is_training=is_training,scope='tconv2', bn_decay=bn_decay)# 输入 BxNx1x128# 1024个 1x1卷积核# 输出 BxNx1x1024net = tf_util.conv2d(net, 1024, [1,1],padding='VALID', stride=[1,1],bn=True, is_training=is_training,scope='tconv3', bn_decay=bn_decay)# 池化操作# 输入 BxNx1x1024# 输出 Bx1x1x1024net = tf_util.max_pool2d(net, [num_point,1],padding='VALID', scope='tmaxpool')# 输出 Bx1024net = tf.reshape(net, [batch_size, -1])# 全连接层# 输入 Bx1024# 权重矩阵 1024x512# 偏置 512x1# 输出 Bx512net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,scope='tfc1', bn_decay=bn_decay)# 全连接# 输出 Bx256net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,scope='tfc2', bn_decay=bn_decay)# 再次使用全连接,不加ReLU和BN# 输出 Bx9with 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)#初始化为3x3单位矩阵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)# reshape# 输出 Bx3x3transform = tf.reshape(transform, [batch_size, 3, K])return transform

针对64x64的网络设计与3x3的一样,只是改变了K值,对于文章提到让特征转化矩阵接近正交化,这样特征损失更小,这部分的实现是在分类任务对损失函数加入正则项,及添加权重reg_weight=0.001,下文分析中会标出。

2. 点云分类部分

这部分主要分析训练代码和点云分类模型的设计

  • train.py
# 参数输入处理
parser = argparse.ArgumentParser()
parser.add_argument('--gpu', type=int, default=0,help='GPU to use [default: GPU 0]')
parser.add_argument('--model', default='pointnet_cls',help='Model name: pointnet_cls or pointnet_cls_basic [default: pointnet_cls]')
parser.add_argument('--log_dir', default='log', help='Log dir [default: log]')
parser.add_argument('--num_point', type=int, default=1024,help='Point Number [256/512/1024/2048] [default: 1024]')
parser.add_argument('--max_epoch', type=int, default=250,help='Epoch to run [default: 250]')
parser.add_argument('--batch_size', type=int, default=32,help='Batch Size during training [default: 32]')
parser.add_argument('--learning_rate', type=float, default=0.001,help='Initial learning rate [default: 0.001]')
parser.add_argument('--momentum', type=float, default=0.9,help='Initial learning rate [default: 0.9]')
parser.add_argument('--optimizer', default='adam',help='adam or momentum [default: adam]')
parser.add_argument('--decay_step', type=int, default=200000,help='Decay step for lr decay [default: 200000]')
parser.add_argument('--decay_rate', type=float, default=0.7,help='Decay rate for lr decay [default: 0.8]')
FLAGS = parser.parse_args()
BATCH_SIZE = FLAGS.batch_size # 训练批次大小
NUM_POINT = FLAGS.num_point # 训练点云点个数
MAX_EPOCH = FLAGS.max_epoch # 最大训练次数
BASE_LEARNING_RATE = FLAGS.learning_rate # 初始学习率
GPU_INDEX = FLAGS.gpu # 默认GPU使用数量
MOMENTUM = FLAGS.momentum # 初始学习率
OPTIMIZER = FLAGS.optimizer # 优化器
DECAY_STEP = FLAGS.decay_step # 衰变步长
DECAY_RATE = FLAGS.decay_rate # 衰变率# some code ...
# 获取模型
pred, end_points = MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay)

原始点云nx3与T-Net训练后得到的3x3旋转矩阵相乘后,可以理解为变换为一组新的坐标下的点云数据。

  • models/pointnet_cls.py中的代码
# 1.原始点云与3x3的T变换矩阵
with tf.variable_scope('transform_net1') as sc:transform = input_transform_net(point_cloud, is_training, bn_decay, K=3)
point_cloud_transformed = tf.matmul(point_cloud, transform)
input_image = tf.expand_dims(point_cloud_transformed, -1)# 2.mlp(64,64):使用2次卷积
# 输入 Bxnx3x1
# 64个1x3卷积核
# 输出 Bxnx1x64
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)
# 输入 Bxnx1x64
# 输出 Bxnx1x64
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)# 3.接64x64特征转换矩阵
with tf.variable_scope('transform_net2') as sc:transform = feature_transform_net(net, is_training, bn_decay, K=64)
end_points['transform'] = transform
# 将上一步的net Bxnx1x64压缩为 Bxnx64 和 T-Net的Bx64x64 相乘
net_transformed = tf.matmul(tf.squeeze(net, axis=[2]), transform)# 4.接3次卷积和1次池化,对应图中的mlp(64,128,1024)+maxpool
pointnet_cls.py
# Bxnx64 扩展为 Bxnx1x64
net_transformed = tf.expand_dims(net_transformed, [2])# 输入 Bxnx1x64
# 使用64个1x1卷积核
# 输出 Bxnx1x64
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)
# 输入 Bxnx1x64
# 使用128个1x1卷积核
# 输出 Bxnx1x128
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)
# 输入 Bxnx1x128
# 使用1024个1x1卷积核
# 输出 Bxnx1x1024
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)# Symmetric function: max pooling
# 输入 Bxnx1x1024
# 输出 Bx1x1x1024
net = tf_util.max_pool2d(net, [num_point,1],padding='VALID', scope='maxpool')
# 输出 Bx1024
net = tf.reshape(net, [batch_size, -1]) # 5.全连接1 + dropout
# 输出 batch_size x 512
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')
# 全连接2 + dropout
# 输出 batch_size x 256
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')
# 全连接3
# 输出 batch_size x 40,因为ModelNet数据集有40个类别的模型
net = tf_util.fully_connected(net, 40, activation_fn=None, scope='fc3')
return net, end_points# 6.使用交叉熵损失函数计算loss,对网络结构进行训练。
def get_loss(pred, label, end_points, reg_weight=0.001):""" 预测值pred: B*NUM_CLASSES,标签值label: B, """loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=pred, labels=label)classify_loss = tf.reduce_mean(loss)tf.summary.scalar('classify loss', classify_loss)# Enforce the transformation as orthogonal matrixtransform = end_points['transform'] # BxKxKK = transform.get_shape()[1].valuemat_diff = tf.matmul(transform, tf.transpose(transform, perm=[0,2,1]))mat_diff -= tf.constant(np.eye(K), dtype=tf.float32)mat_diff_loss = tf.nn.l2_loss(mat_diff) tf.summary.scalar('mat loss', mat_diff_loss)#这里加入了reg_weight正则项,使得T-net部分获得的特征转移矩阵更接近与正交化return classify_loss + mat_diff_loss * reg_weight#	模型优化器选择:adam
#	学习率初始值:0.001
# 	动量=0.9
# 	batch_size =32
# 	学习率每训练20个epochs后减半
#	GTX1080 ModelNet 需要3-6小时

对于pointnet_cls_basic.py没有使用T-net的点云分类,网络结构更容易理解,比加入T-net的结构性能略低。

3. 点云分割部分

  • 点之间的相关性问题

针对与分割物体上的问题与分类任务不同,分类任务中特征经过max pooling得到一维特征向量,它包含了全局信息,再经过全连接网络,得到1*K的k个类别预测得分即为分类结果。而分割任务中,需要对每一个点输出所属类别,使用类似二维图像分割的上采样过程(跳步连接skip-links)。Pointnet针对分割任务也使用了类似图像分割任务的,高层全局信息与底层局部特征结合的思想。

针对Pointnet论文作者提供的版本(Tensorflow)的源码如下:https://github.com/charlesq34/pointnet

对于pointnet源码其余部分的介绍不详细展开,根据个人理解将源码的结构与功能设计展示如下:
在这里插入图片描述
分割部分的代码实现主要在part_seg/(部件分割)和sem_seg/(场景分割)下。其中part_seg中底层局部特征与高层全局特征的连接**(concat)使用到了各层特征**。
在这里插入图片描述
sem_seg/model.py场景分割中需要注意论文使用的S3DIS数据维度不再是3维而是更高的9维度(XYZ+RGB+相对于房间的标准化后的位置信息),针对特征连接部分使用高层全局特征(B*1024)接全连接降维到128,然后与高维特征自身做concat,不是采用论文中提到的方式。

而论文中提到的分割结构实际是在models/pointnet_seg.py中实现,即max pooling后的1D特征向量,使用tf.tile()复制n份(n个特征点),与之前网络得到的 n * 64特征矩阵分别concat。得到一个n(64+D)的特征矩阵,再经一系列的特征变换操作,得到每个点的分类结果。

结语

本文主要结合代码层面总结了pointnet网络的分类和分割任务的实现。主要是理解pointnet是如何做到直接从原始点云数据中提取高维特征,并且解决好点云的特性。实际上基于pointnet结构可以进行很多任务,比如点云配准,物体检测,3D重建,法向量估计等,只需要根据具体任务合理修改网络后几层的结构,利用好网络提取的高维特征。

针对pointnet存在的点与点之间相关性的缺失,在pointnet++中使用局部采样+分组+pointnet的结构进行解决,并考虑到了点云的稀疏性解决方案,之后很多深度学习的研究在此基础上展开,习惯上称为pointnet家族(point-wise MLP),比如Frustum,flowNet 3D,LSAnet,PAT等等。个人认为更高的准确度需要点云等3D数据与图像结合进行深度学习训练,将图像的高分辨率优势借鉴进来会有更好的效果。

源码地址:
1.原论文实现代码
https://github.com/charlesq34/pointnet
2.基于pytorch实现:
https://github.com/fxia22/pointnet.pytorch
https://github.com/yanx27/Pointnet_Pointnet2_pytorch

放上自己在谷歌的Colab上的gpu实现:在Colab上实现分类和Part_seg,选择GPU版本的Notebook,
挂载好自己的谷歌云盘(方便保存和加载训练数据),batch_size设置为32,数据集使用别人共享的Shapenet的数据集。
对于语义分割部分colab上的免费gpu满足不了,需要购买更高版本的配置。


http://chatgpt.dhexx.cn/article/u6LM61f6.shtml

相关文章

PointNet++

[NIPS 2017]PointNet: Deep Hierarchical Feature Learning onPoint Sets in a Metric Space 语雀(原文内容多一点,CSDN导不进来) [论文地址][项目页面][GitHub] 在之前的文章中分析了PointNet网络是如何进行3D点云数据分类与分割的。但是P…

一文搞懂PointNet全家桶——强势的点云处理神经网络

作者:黎国溥,3D视觉开发者社区签约作者,CSDN博客专家,华为云-云享专家 首发:公众号【3D视觉开发者社区】 前言 PointNet是由斯坦福大学的Charles R. Qi等人在《PointNet:Deep Learning on Point Sets for 3D Classifi…

PointNet理解(PointNet实现第4步)

PointNet第4步——PointNet理解 前面,我们讲到了点云的挑战,针对点云的挑战,PointNet论文提出了下面的解决方案。 下面用到的PPT来源于PointNet作者本人,不得不说大佬还是大佬,介绍也十分清晰,下面附上祁芮…

Pointnet/Pointnet++学习

一、点云的应用 二、点云的表述 三、Pointnet 四、Pointnet Pointnet概述 虽然这篇文章叫PointNet,但和PointNet相比还是有很大的改进。文章非常核心的一点就是提出了多层次特征提取结构。具体来说就是先在输…

PointNet介绍

论文:PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation 代码:https://github.com/charlesq34/pointnet 0 引言 PointNet是处理点云数据的深度学习模型,其地位堪比2D图像处理中的CNN网络, 后续的诸…

三维深度学习之pointnet系列详解(一)

目前二维深度学习取得了很大的进步并且应用范围越来越广,随着三维设备的发展,三维深度学习得到了很大的关注。 最近接触了三维深度学习方面的研究,从pointnet入手,对此有了一点点了解希望记录下来并分享,若有误希望指…

综述|PointNet、PointNet++、 F-PointNet基于深度学习的3D点云分类和分割

点击下方卡片,关注计算机视觉工坊公众号 干货第一时间送达 作者:黎国溥,3D视觉开发者社区签约作者,CSDN博客专家,华为云-云享专家。 编辑:3D视觉开发者社区 前言 PointNet是由斯坦福大学的Charles R. …

PointNet++分类与分割详解

前言 PointNet是一个用于对不规则形状的点云数据进行分类和分割任务的深度神经网络。相对于传统的基于网格的3D数据表示方法,点云数据更易于获取和处理。PointNet的另一个优势是它引入了多尺度层次结构,可以处理更为复杂的点云数据。相比于第一版的Point…

【点云分类和分割】简述PointNet和PointNet++的理解

Hello大家好,最近阅读了PointNet和PointNet两篇论文,本人觉得这是点云方向入手的比较简单的入门论文,下面阐述一下自己对这两篇论文的理解 一、首先点云是非常重要的三维数据结构,但是其有着非常特殊的性质,不规则性和…

PointNet解读

PointNet解决的问题: 如上图所示: 1.点云图像的分类(整片点云是什么物体) 2.点云图像的部件分割(整片点云所代表的物体能拆分的结构) 3.点云图像的语义分割(将三维点云环境中不同的物体用不同…

基于深度学习方法的点云算法3——PointNet++(点云分类分割)

基于深度学习方法的点云算法3——PointNet(点云分类分割) 请点点赞,会持续更新!!! 基于深度学习方法的点云算法1——PointNetLK(点云配准) 基于深度学习方法的点云算法2——PointNet…

论文解读PointNet(用于点云处理的深度学习框架)

随着最近几年神经网络在CV、NPL等领域取得重大的成果,因此就有学者希望将神经网络应用于3D任务中。在这篇文章(PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation)出现之前,一般在3D任务中用的最多的…

最全PointNet和PointNet++要点梳理总结

一、基本简介 本篇博文主要是对 PointNet,PointNet 论文的要点进行梳理和总结。认真阅读本博文后,不仅能够深刻理解论文的核心算法思想,而且对模型训练数据、模型的训练流程也能了然于胸。如果想阅读原论文以及翻译,参考下面的链接…

苹果将强制开发人员启用双因素认证提高安全

苹果的开发人员在近期应该都收到了公司的电子邮件通知,知会他们在今年2月27日之后,都必须启用双因素认证才能登入开发人员账号。苹果在邮件中指出,为了让开发人员的账号更为安全,从2月27日起,不管是要登入苹果的开发人…

关闭appleid双重认证_Apple ID 被停用如何解决?

苹果帐户被停用一般是因为输错密码次数过多,或者登录的设备数过多。账户被停用后,iPhone 就会弹出“您的帐户已在 App Store 和 iTunes 中被禁用”等提示。 如果看到一条内容为 " 您的帐户已在 App Store 和 iTunes 中被禁用 " 的信息&#xf…

苹果规定开发人员得使用双认证登入 以确保帐号安全

苹果的开发人员在近期应该都收到了公司的电子邮件通知,知会他们在今年2月27日之后,都必须启用双因素认证才能登入开发人员账号。 苹果在邮件中指出,为了让开发人员的账号更为安全,从2月27日起,不管是要登入苹果的开发人…

苹果怎么解ID锁?苹果ID锁解锁办法汇总

苹果ID锁很多人了解都不是很清楚,而且很多人认为用爱思助手刷机可以刷掉ID锁,如果你的手机出现ID锁后,那你就必须输入Apple ID账号密码才能激活后,无论之后你再怎么刷机都是需要账号密码才能激活的。 苹果解ID锁的办法在网上也是五…

苹果手机账号验证失败连接不上服务器,苹果手机让检查Apple ID 电话号码点击后验证失败,连接服务器失败出错...

这个问题应该是信号或者网络连接的问题,也可能是该时段服务器连接量过大。 建议换个时间段尝试,或者关掉wifi使用数据连接尝试验证。 双重认证是一种相对较新的安全保护机制,直接内建于 iOS、macOS、Apple tvOS、watchOS 和 Apple 网站中。它…

苹果开发者账号:忘记AppleID的安全提示问题怎么办?

方法一:通过iPhone重设安全问题 1、使用账号密码在设置里登录任意一台iOS 9及以上系统的手机 2、登录一段时间(大概一个月)之后,点击开启双重认证 3、 刚登陆的时候开启双重认证的话还需要回答安全提示问题。所以等一段时间再开启…

最近发现有很多人一直在问苹果ID双重认证怎么关闭。

最近发现有很多人一直在问苹果ID双重认证怎么关闭? 其实我想说大家都粗心了,双重认证是和ios版本没有关系的,无论什么IOS版本开通的双重认证都是可以关闭的。https://support.apple.com/zh-cn/HT204915 最后一段有说明哦。 其实苹果在官网上已…