PointNet网络详解+分类解读

article/2025/9/14 3:23:49

PointNet设想的由来

说到如何设计PointNet网络的,那我们首先就要从输入数据的特性说起。点云数据是一种不规则的数据,在空间上和数量上可以任意分布,由于其特性而不能直接适用于传统CNN。以往的研究者想出了很多种处理点云方式:

  • 将点云数据转化为规则的数据,转化为栅格使其均匀分布,再用3DCNN来处理栅格数据
    缺点:3D cnn 复杂度相当的高,三次方的增长,所以分辨率不高303030 相比图像是很低的,带来了量化的噪声错误,限制识别的错误。

  • 有的学者将点云数据从3D投影到2D平面,再进行2DCNN,这样会损失3D空间的信息。

吸取前者的经验,根据点云数据的特性着手设计PointNet网络

点云数据的三个特性

  • 点云具有无序性

点云数据是一种对顺序很不敏感的数据,点云是无序的,点与点之间的顺序可以任意变换,但其代表的还是同一个物体。故此在设计网络的时候,就需要使网络能够针对不同顺序下的点云数据都能够提取到同一种空间特征,具有置换不变性。
数学上提供了对称函数的理论指出:输出结果与输入顺序无关的函数
而在网络上能实现对称函数功能的是最大池化层:Maxpooling
在这里插入图片描述三维点云数据不管以什么顺序输入,经过maxpool后输出的总是最大的特征,这也有效的解决了点云无序性的问题。
如想具体了解可点击点云深度学习的3D场景理解

  • 点与点之间的存在的信息连接

这点我认为在pointnet中没有很好的体现出来,在进行零件细分和语义分割的过程中,只是将局部特征和全局特征进行了串联拼接,该架构不能获取点附近的底层局部结构,这也是PointNet最主要的缺点,而在Pointnet++中改善此缺点。

  • 点云的旋转不变性

点云具有旋转不变性,平移不变性,缩放不变性。针对该问题,网络增加了一个基于数据本身的变换函数模块T-Net,我在看别的博客,大多都是解释,生成点云旋转矩阵与输入点云数据相乘,使得与特征空间对齐来保证不变性。
对于特征空间对齐这个概念我一直不是很理解,在观看其他博客的时候,我得到了一种说法,实质上T-net得作用就是保留原始点云的部分特征,为后面的concat操作提供更多特征。在原文中,作者也对T-Net做了实验,发现在分类过程中,加入T-Net网络,使用输入变换可以提高0.8%的性能。但是T-Net没法获取点附近之间的局部特征,故此对于分割而言是没有帮助的,在语义分割中也弃用了T-Net。

最终得到PointNet框架如下:在这里插入图片描述

分类代码详解

在这里插入图片描述先贴个图,看分类代码的分支,用到的其实就是transform_nets.py,pointnet_cls,py,外加训练测试加预测了

T-Net

def input_transform_net(point_cloud, is_training, bn_decay=None,K=3):"""输入(XYZ)转换网络输入:B x N x 3灰度图像返回:转换矩阵尺寸 3 x K"""batch_size = point_cloud.get_shape()[0].value  #批次大小为B的值  32num_point = point_cloud.get_shape()[1].value   #每个批次点云数据量N大小  2048input_image = tf.expand_dims(point_cloud,-1) #扩展数组的维度[B,N,3,1] (32,2048,3,1)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)#将(32,2048,3,1)-->(32,2048,1,1)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)#(32,2048,1,1)-->(32,2048,1,128)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)#(32,2048,1,128)-->(32,2048,1,1024)                     net = tf_util.max_pool2d(net, [num_point,1],padding='VALID', scope='tmaxpool')#maxpooling  (32,2048,1,1024)-->(32,1,1,1024)  #利用1024维特征生成256维度的特征#(32,1,1,1024)-->(32,1024)net = tf.reshape(net,[batch_size,-1])#(32,1024)-->(32.512)net = tf_util.fully_connected(net,512,bn=True,is_training=is_training,scope='tfc1',bn_decay=bn_decay)#(32,512)-->(32,256)                              net = tf_util.fully_connected(net,256,bn=True, is_training=is_training,scope='tfc2', bn_decay=bn_decay)#生成点云旋转矩阵 T=3*3with 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)"""tf.nn.bias_add(value,bias,name=None)一个叫bias的向量加到value的矩阵,是向量与矩阵的每一行相加,得到的结果和value矩阵大小相同将偏差项bias加到value上,加到每一个数上"""transform = tf.reshape(transform,[batch_size,3,K])return transform

KK的旋转矩阵和33的旋转矩阵代码基本一样就不细说了

train.py代码

import argparse
"""argpare是python自带的命令行参数解析包,可以方便读取命令行参数"""
import math
import h5py
import numpy as np
import tensorflow as tf
import socket
import importlib
import os
import sysBASE_dir = os.path.dirname(os.path.abspath(__file__))#获取当前文件的绝对路径
sys.path.append(BASE_dir)
sys.path.append(os.path.join(BASE_dir,'models'))
sys.path.append(os.path.join(BASE_dir,'utils'))
import preproesess
import tf_utilparser = 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=50,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.7]')
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
MOMENTUM = FLAGS.momentum
OPTIMIZER = FLAGS.optimizer
DECAY_STEP = FLAGS.decay_step
DECAY_RATE = FLAGS.decay_rateMODEL = importlib.import_module(FLAGS.model) #导入网络模块
"""importlib:
python提供importlib包作为标准库的一部分,提供python中import语句的实现
importlib允许创建自定义的对象,用于引入过程"""
MODEL_FILE = os.path.join(BASE_dir,'models',FLAGS.model+'py')
LOG_DIR = FLAGS.log_dir
if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR)
os.system('cp %s %s' % (MODEL_FILE,LOG_DIR))#cp为copy,复制文件和目录
os.system('cp train.py %s' % (LOG_DIR))
LOG_FOUT = open(os.path.join(LOG_DIR,'log_train.txt'),'w')
LOG_FOUT.write(str(FLAGS)+'\n')MAX_NUM_POINT = 2048
NUM_CLASSES = 40"""????BN 代表什么"""
BN_INIT_DECAY = 0.5
BN_DECAY_DECAY_RATE = 0.5
BN_DECAY_DECAY_STEP = float(DECAY_STEP)
BN_DECAY_CLIP = 0.99HOSTNAME = socket.gethostname()
"""socket插口socket模块提供函数用于使用主机名和地址来工作socket.gethostname()返回运行程序所在的计算机的主机名"""#ModelNet40 official train/test
TRAIN_FILE =preproesess.getDataFiles(os.path.join(BASE_dir,'data/modelnet40_ply_hdf5_2048/train_files.txt'))
TEST_FILE =preproesess.getDataFiles(os.path.join(BASE_dir,'data/modelnet40_ply_hdf5_2048/test_files.txt'))def log_string(out_str):   #写日志LOG_FOUT.write(out_str+'\n')LOG_FOUT.flush()print(out_str)
"""write()方法用于向文件中写入指定字符串1.fileObject.write([str]) str:要写入文件的字符串2.flush()方法是用来把文件从内存buffer(缓冲区)中强制刷新到硬盘中,同时清空缓冲区,一般情况下文件关闭后会自动刷新到硬盘中,但有时需要在关闭前刷新到硬盘中,可使用flush()fileObject.flush() //刷新缓冲区在使用流时,都会有一个缓冲区,把要发的数据先放到缓冲区,缓冲区放满以后再一次性发过去,而不是分开一次一次发flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满"""def get_learning_rate(batch):   #获取学习率learning_rate = tf.train.exponential_decay(BASE_LEARNING_RATE, #事先设定的初始学习率batch * BATCH_SIZE, #当前的数据集索引(总的迭代次数)DECAY_STEP,      #衰减速度(在迭代到该次数时学习率衰减为 lr*decay_rate)DECAY_RATE,      #衰减系数,通常介于0-1之间staircase=True)  #通过staircase选择不同的衰减方式,若为True(global_step/decay_steps)则被转化为整数learning_rate = tf.maximum(learning_rate,0.00001)return learning_rate
"""tf.train.exponential_decay()函数实现指数衰减学习率1.首先使用较大学习率(目的:为快速得到一个比较优的解)2.然后通过迭代逐步减小学习率(目的:为使模型再训练后期更加稳定)如果staircase为True,则global_step / decay_steps始终取整数,也就是说衰减是突变的,每decay_steps次变化一次,变化曲线是阶梯状。decayed_learning_rate = learning_rate * decay_rate^(global_step/decay_step)"""def get_bn_decay(batch):bn_momentum = tf.train.exponential_decay(BN_INIT_DECAY,   #初始衰减batch*BATCH_SIZE, #总的迭代次数BN_DECAY_DECAY_STEP, #衰减步数BN_DECAY_DECAY_RATE, #衰减率staircase=True)bn_decay = tf.minimum(BN_DECAY_CLIP,1-bn_momentum)return bn_decay
"""衰减也是不断衰减的tf.minimum(a,b):返回的是a,b之间的最小值_tf.maximun(a,b):返回的是a,b之间的最大值"""def train():with tf.Graph().as_default():with tf.device('/gpu:'+str(GPU_INDEX)):pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE,NUM_POINT)is_training_pl = tf.placeholder(tf.bool,shape=())print(is_training_pl)#注意将global_step = batch参数最小化#告诉优化器在每次训练时都为您增加batch参数batch = tf.Variable(0)bn_decay = get_bn_decay(batch)tf.summary.scalar('bn_decay',bn_decay)# 获取model和losspred, end_points = MODEL.get_model(pointclouds_pl,is_training_pl,bn_decay=bn_decay)loss = MODEL.get_loss(pred,labels_pl,end_points)tf.summary.scalar('loss',loss)correct = tf.equal(tf.argmax(pred,1),tf.to_int64(labels_pl))"""1.tf.argmax(input,axis):根据axis取值的不同返回每行或每列最大值的索引tf.argmax(array,0) tf.argmax(array,1)axis = 0:比较每一列的元素,将每一列最大元素所在的索引记录下来,输出最大元素所在的索引数组axis = 1:将每一行最大元素的索引记录下来2.tf.to_int64:张量变换,将张量转换为int64类型,返回一个类型为int64的Tensor或者SparseTensor,与x具有相同的形状3.tf.equal(x,y,name=None)对比两个矩阵或者向量的相等的元素,若相等那就返回True,反则False,返回的值的矩阵维度和x,y一样。由于是逐个元素对比,所以x,y的维度也要相同"""accuary = tf.reduce_sum(tf.cast(correct,tf.float32)) / float(BATCH_SIZE)tf.summary.scalar('accuary',accuary)"""tf.reduce_sum(input_tensor,axis=None,keepdims=None)计算张量tensor沿着某一维度的和,可在求和后降维input_tensor:待求和的tensoraxis:指定的维度,不指定则计算所有元素的总和keepdims:是否保持原有张量维度,True保持,False降维,默认降维"""#get training operatorlearning_rate = get_learning_rate(batch)tf.summary.scalar('learning_rate',learning_rate)if OPTIMIZER =='momentum':optimizer = tf.train.MomentumOptimizer(learning_rate,momentum=MOMENTUM)elif OPTIMIZER == 'adam':optimizer = tf.train.AdamOptimizer(learning_rate)train_op = optimizer.minimize(loss,global_step=batch)"""tf.train.Optimizer.minimize(loss,global_step=None,var_list=None,gate_gradients=1,aggregation_method=None)添加操作通过更新var_list来最大程度减少loss是将compute_gradients()和apply_gradients()组合在一起变量loss:包含最小化值的张量global_step:可选变量,再变量更新后增加一var_list:可选的可变对象列表,可进行更新以最大程度减少损失gate_gradients:如何控制梯度的计算"""# 添加操作以保存和还原所有变量。saver = tf.train.Saver()# Create a sessionconfig = tf.ConfigProto()config.gpu_options.allow_growth = Trueconfig.allow_soft_placement = Trueconfig.log_device_placement = Falsesess = tf.Session(config= config)"""tf.ConfigProto()函数在创建session时候,对session进行参数配置1.tf.ConfigProto(log_device_placement = Ture)记录设备指派情况,获取Tensor指派到哪个设备(几号CPU几号GPU),会在终端打印出各项操作在哪个设备上运行2.tf.ConfigProto(allow_soft_placement=True)在tf中,通过命令"with tf.device('/cpu:0')",允许手动设置操作运行的设备设置该函数允许tf自动选择一个存在并且可用的设备运行。3.动态申请显存 tf.ConfigProto(gpu_options.allow_growth=True)Session是为了控制,输出文件的执行语句"""# Add summary writersmerged = tf.summary.merge_all()train_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'train'),sess.graph)test_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'test'))# Init variablesinit = tf.global_variables_initializer()sess.run(init, {is_training_pl: True})ops = {'pointclouds_pl': pointclouds_pl,'labels_pl': labels_pl,'is_training_pl': is_training_pl,'pred': pred,'loss': loss,'train_op': train_op,'merged': merged,'step': batch}for epoch in range(MAX_EPOCH):log_string('**** EPOCH %03d ****' % (epoch))sys.stdout.flush()"""sys.stdout是print的一种默认输出格式print默认调用了sys.stdout.write()方法"""train_one_peoch(sess,ops,train_writer)eval_one_epoch(sess,ops,test_writer)#保存变量到磁盘if epoch % 10 == 0:save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt"))log_string("Model saved in file: %s" % save_path)

博主也是入门,初看到代码的时候头晕眼花,故此通过搜索将很多自己没有用过的函数都进行了注释,希望能帮助到像博主一样刚入门基础不是很好的初学者。

代码分类结果

由于电脑配置低,所以只进行了50epoch的训练,跑了差不多十多个小时。
在这里插入图片描述
在这里插入图片描述 原文分类正确率

预测错误图片

在这里插入图片描述

PointNet部分分割点此处


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

相关文章

PointNet论文及代码详细解析

基本都是搬运的知乎刘昕宸大佬的文章,自己也是想记录学习一下 原文链接: https://zhuanlan.zhihu.com/p/264627148 这篇论文的题目是:PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation 首先回答三个问题作为引…

3D点云(3D point cloud)及PointNet、PointNet++

文章目录 一、什么是3D点云二、基于3D点云的一些任务三、如何提取3D点云数据的特征:PointNet(1)在PointNet之前也有工作在做点云上的深度学习(2)PointNet(1)置换不变性(Permutation …

PointNet++:Deep Hierarchical Feature Learning on Point Sets in a Metric Space

在上一篇文章中,提及了3D点云分类与分割的开山鼻祖——PointNet:https://blog.csdn.net/Alkaid2000/article/details/127253473,但是这篇PointNet是存在有很多不足之处的,在文章的末尾也提及了,它没有能力捕获局部结构…

Pointnet系列(二)Pointnet++

简介 作者在先前的研究中提出了Pointnet,此论文是Pointnet的改进版Pointnet。提出改进的理由是因为Pointnet无法很好地捕捉由度量空间引起的局部结构问题,由此限制了网络对精细场景的识别以及对复杂场景的泛化能力。 Pointnet的基本思想是对输入点云中…

PointNet++详解与代码

在之前的一篇文章《PointNet:3D点集分类与分割深度学习模型》中分析了PointNet网络是如何进行3D点云数据分类与分割的。但是PointNet存在的一个缺点是无法获得局部特征,这使得它很难对复杂场景进行分析。在PointNet中,作者通过两个主要的方法…

【3D计算机视觉】从PointNet到PointNet++理论及pytorch代码

从PointNet到PointNet理论及代码详解 1. 点云是什么1.1 三维数据的表现形式1.2 为什么使用点云1.3 点云上以往的相关工作 2. PointNet2.1 基于点云的置换不变性2.1.1 由对称函数到PointNet(vanilla)2.1.2 理论证明 2.2 基于点云的旋转不变性2.3 网络总体结构2.4 实验结果和网络…

Pointnet网络结构与代码解读

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

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