点云处理入门--PointNetPointNet++论文与代码详解

基础知识

点云数据:
点云是一种通过三维扫描设备或计算机图形学技术获取的三维空间数据,通常由一系列点组成,每个点包含其在三维空间中的坐标(如 x,y,z),有时还可能包含颜色、强度等附加信息。
介绍几种常见的点云存储格式:
1、XYZ
最简单的点云文件格式,通常是一个纯文本文件,每行表示一个点,包含三个数值(x,y,z坐标)
2、PLY
文件可以是纯文本或二进制格式,支持存储点的坐标、颜色、法线等附加信息,支持点云和网格(mesh)数据。
3、OBJ
主要用于存储三维模型(网格),但也可用于点云数据。文件格式为纯文本,支持顶点坐标、纹理坐标、法线等信息,不支持颜色。
Mesh和点云的转换:
Mesh(网格)是一种由顶点、边和面组成的三维模型,而点云是离散的点集合。虽然它们在形式上有所不同,但可以通过一些算法相互转换:

  • Mesh 转点云:可以通过泊松采样(Poisson Sampling)、均匀采样(Uniform Sampling)等方法从网格表面生成点云。
  • 点云转 Mesh:可以通过泊松重建(Poisson Surface Reconstruction)、Marching Cubes 等算法从点云生成网格。

PointNet论文讲解

论文地址:https://arxiv.org/abs/1612.00593
code:https://github.com/charlesq34/pointnet
pytorch实现:https://github.com/fxia22/pointnet.pytorch

简单来说,点云数据就是一堆坐标点。

如何处理点云数据?

体素化方法:将点云数据转换为规则的三维网格(体素),每个体素包含一定范围内的点云数据,对每个体素内的点云进行特征提取(平均值、最大值等),然后利用3D卷积神经网络(CNN)进行处理。
多视图方法:将点云从多个视角投影到二维平面上,生成多个二维图像,然后使用传统的2D卷积网络进行处理。
这两种方法都没有直接利用点云原始数据,而是经过转换后输入网络。

PointNet直接使用原始点云数据,近年来越来越多的方法这样做。
直接对点云特征进行学习,就需要解决两个问题:点云数据的无序性、旋转的不变性。

1、数据的无序性

点云中的点的排列顺序是随机的,不具有固定的结构。点云中的点可以任意交换位置,而不会影响其对物体或场景的描述。这点和图像数据是不同的,而传统的深度学习方法(如卷积神经网络)通常依赖于输入数据的固定结构(如图像的像素排列)。
这就要求模型能够不受数据排列顺序的影响,提取到准确的特征。
如何能够不受排列顺序影响呢?我们可以使用一些函数来处理,比如max,min,sum,avg等,这些函数的处理结果不受序列顺序影响。但是还有一个问题,如果说一个点云nx3,我们对每一个维度采取max处理,得到1x3的特征(这反映了这个点云在三个维度上的最大值),这样做损失的信息太多了,输出的特征仅仅继承了三个坐标轴上的最大特征。
所以我们需要更多的信息,我们首先将特征映射到高维空间,简单来说将点云数据通过MLP扩充维度,如nx1024,然后 取点云序列在每一个维度的最大值1x1024,组成的向量代表整个点云序列的特征,这样一来,避免了序列顺序的影响,同时也增加了更多的特征信息。
在这里插入图片描述

2、旋转不变性

点云在经过旋转变换后,其特征表示应保持不变。
换句话说,如果我们在空间内旋转一个物体,比如一把伞,那么他的点云数据(坐标)都已经被改变,我们需要模型能够包容这种旋转操作,依然识别出这是一把伞。
PointNet保证旋转不变性的做法是–设计了T-Net来学习点云的旋转。
T-Net是一个小型的神经网络。主要目的是通过学习一个仿射变换矩阵,对输入点云或特征空间进行对齐,从而减少姿态变化对模型性能的影响。
具体做法:输入点云为nx3,先扩充维度(三次卷积操作,卷积核为1x1,通道数分别是64,128,1024)得到nx1024向量,然后对其进行最大池化(确保模型对点云无序性具有不变性),得到特征1x1024,然后先通过两层全连接层,分别映射到 512 维和 256 维,最后把256维的特征映射到kxk的矩阵(在网络架构中有两处,一处是3x3,一处是64x64)。这个映射也很简单,就是再过一个全连接,维度变成 k 2 k^2 k2,然后reshape成kxk的矩阵就行。最后这个矩阵就是学习了点云旋转的特征,将其乘以输出的点云即可。

PointNet代码

PointNet分为两个版本,点云分类和点云分割
首先看分类,分类的网络结构图应该如下图所示,红色箭头代表跳过中间的feature_transform
在这里插入图片描述
输入为一个 N × 3 N \times 3 N×3的点云,代码里实际的输入格式为Bx3xN
先过一个inout_transform,也就是论文中说的T-Net(这部分怎么处理的,可以看下面的代码注释)
然后得到特征为Bx3xN,然后过一个卷积,变为Bx64xN,也就是图中的mlp(64,64),两个64分别代表之前的通道数和之后的通道数,
然后按照我画的红色箭头,再过一个mlp(64,128,1024),也就是三个卷积,最后输出Bx1024xN,然后做一个max pool变为Bx1024
然后用全卷积神经网络nn.Linear,将1024–>512–>256–>k,也就是图中的mlp(512,256,k)
k就是分类的类别数量,最后再过一个softmax得到分类结果。
比如分两类,最后输出的Bx2也就是每个点云2个概率值,分别代表该点云是这两类的概率。
这就是分类架构。
分割与分类不同,分类预测整个点云的概率,如果一共有五类,那么就输出五个数,代表该点云是这五类的概率。
而分割需要预测每一个点的类别,要输出Nx5个数字,代表n个点是这个五类的概率。
分割的网络结构如下图所示。
在这里插入图片描述

与分类相比有几个地方不一样。
首先就是第一次mlp(64,64)之后,需要再过一个feature_transformer,这次仿射变换矩阵是64x64的,过了之后保持大小形状不变,然后把这个Bx64xN保存下来,后面继续正常往下走,直到走到最后max pool之后输出一个全局特征Bx1024,这个时候我们使用复制操作,将其复制为Bx1024xN,也就是复制n个,然后把这个特征与前面保存下来的那个Bx64xN,做一个拼接,得到Bx1088xN(64+1024),拿这个特征去做一维卷积,将通道数由1088一直降到128,也就是得到Bx128xN,最后再来一个一维卷积,直接维度降到k,BxkxN,k代表分类的类别。然后做一个softmax,得到每个点的k个类别概率(softmax之前要转换一下,可以参考代码操作)。

代码以及注释(pytorch版本)

from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
from torch.autograd import Variable
import numpy as np
import torch.nn.functional as Fclass STN3d(nn.Module): #空间变换网络(Spatial Transformer Network)def __init__(self):super(STN3d, self).__init__()self.conv1 = torch.nn.Conv1d(3, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, 9)self.relu = nn.ReLU()self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.bn4 = nn.BatchNorm1d(512)self.bn5 = nn.BatchNorm1d(256)def forward(self, x): # x:BxDxN  eg:(32, 3, 2500) 32个点云,每个点云2500个点,每个点三个坐标xyzbatchsize = x.size()[0] # 输入点云数据 BxDxN  D=3x = F.relu(self.bn1(self.conv1(x))) # 一维卷积 通道数3-->64x = F.relu(self.bn2(self.conv2(x))) # 一维卷积 通道数64-->128x = F.relu(self.bn3(self.conv3(x))) # 一维卷积 通道数128-->1024x = torch.max(x, 2, keepdim=True)[0] # Bx1024xN 在维度2上取最大值 Bx1024x1 即在每个维度上对N个点取最大值x = x.view(-1, 1024) # 去除最后一列 Bx1024x = F.relu(self.bn4(self.fc1(x))) # 1024-->512x = F.relu(self.bn5(self.fc2(x))) # 512-->256x = self.fc3(x) # 256-->9# 为每一个batch生成一个单位矩阵iden = Variable(torch.from_numpy(np.array([1,0,0,0,1,0,0,0,1]).astype(np.float32))).view(1,9).repeat(batchsize,1)if x.is_cuda:iden = iden.cuda()x = x + iden # 变换矩阵与单位矩阵相加 这个操作可以理解为:防止网络乱学 引导其接近单位矩阵x = x.view(-1, 3, 3) # 调整为Bx3x3return xclass STNkd(nn.Module):#这个与上面的STN是一样的 这个输出64x64  上面输出3x3def __init__(self, k=64):super(STNkd, self).__init__()self.conv1 = torch.nn.Conv1d(k, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, k*k)self.relu = nn.ReLU()self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.bn4 = nn.BatchNorm1d(512)self.bn5 = nn.BatchNorm1d(256)self.k = kdef forward(self, x):batchsize = x.size()[0]x = F.relu(self.bn1(self.conv1(x)))x = F.relu(self.bn2(self.conv2(x)))x = F.relu(self.bn3(self.conv3(x)))x = torch.max(x, 2, keepdim=True)[0]x = x.view(-1, 1024)x = F.relu(self.bn4(self.fc1(x)))x = F.relu(self.bn5(self.fc2(x)))x = self.fc3(x)iden = Variable(torch.from_numpy(np.eye(self.k).flatten().astype(np.float32))).view(1,self.k*self.k).repeat(batchsize,1)if x.is_cuda:iden = iden.cuda()x = x + idenx = x.view(-1, self.k, self.k)return xclass PointNetfeat(nn.Module):def __init__(self, global_feat = True, feature_transform = False):super(PointNetfeat, self).__init__()self.stn = STN3d()self.conv1 = torch.nn.Conv1d(3, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.global_feat = global_featself.feature_transform = feature_transformif self.feature_transform:self.fstn = STNkd(k=64)def forward(self, x): # x:BxDxN D就是通道数 输入的时候D=3 代表xyz坐标n_pts = x.size()[2]trans = self.stn(x) # 做一个仿射变换  trans:Bx3x3x = x.transpose(2, 1) # BxDxN --> BxNx3x = torch.bmm(x, trans) # 与仿射变换的结果相乘 结果大小为BxNx3x = x.transpose(2, 1) # x:Bx3xNx = F.relu(self.bn1(self.conv1(x))) # 3-->64  Bx64xNif self.feature_transform:trans_feat = self.fstn(x) # 做一个仿射变换  trans_feat: 64x64x = x.transpose(2,1)x = torch.bmm(x, trans_feat) # 与仿射变换的结果相乘 x = x.transpose(2,1) # 结果大小为BxNx64else:trans_feat = Nonepointfeat = x # feature_transform=false : pointfeat Bx64xNx = F.relu(self.bn2(self.conv2(x))) # 64-->128  Bx128xNx = self.bn3(self.conv3(x)) # 128-->1024  Bx1024xNx = torch.max(x, 2, keepdim=True)[0] # 最大池化 Bx1024x1x = x.view(-1, 1024) # Bx1024if self.global_feat:return x, trans, trans_feat # x是编码后的特征 teans是仿射变换矩阵 trans_feat是else:x = x.view(-1, 1024, 1).repeat(1, 1, n_pts) # 使用复制 将x:Bx1024x1-->Bx1024xN# x(Bx1024xN)pointfeat(Bx64xN)在维度1上拼接 结果大小为Bx1088xNreturn torch.cat([x, pointfeat], 1), trans, trans_featclass PointNetCls(nn.Module): # 分类def __init__(self, k=2, feature_transform=False):# 分类时 feature_transform=Falsesuper(PointNetCls, self).__init__()self.feature_transform = feature_transformself.feat = PointNetfeat(global_feat=True, feature_transform=feature_transform) self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, k)self.dropout = nn.Dropout(p=0.3) # 每个神经元有 30% 的概率被置零 最后一层使用self.bn1 = nn.BatchNorm1d(512)self.bn2 = nn.BatchNorm1d(256)self.relu = nn.ReLU()def forward(self, x): # BxDxNx, trans, trans_feat = self.feat(x) # 提取特征 x:Bx1024 trans:Bx3x3 trans_feat=Nonex = F.relu(self.bn1(self.fc1(x))) # Bx1024-->Bx512x = F.relu(self.bn2(self.dropout(self.fc2(x))))  # Bx512-->Bx256 x = self.fc3(x) # Bx256 --> k  要分几类 默认是2return F.log_softmax(x, dim=1), trans, trans_feat # 过一个log_softmax输出结果class PointNetDenseCls(nn.Module): # 分割def __init__(self, k = 2, feature_transform=False):super(PointNetDenseCls, self).__init__()self.k = kself.feature_transform=feature_transformself.feat = PointNetfeat(global_feat=False, feature_transform=feature_transform)self.conv1 = torch.nn.Conv1d(1088, 512, 1)self.conv2 = torch.nn.Conv1d(512, 256, 1)self.conv3 = torch.nn.Conv1d(256, 128, 1)self.conv4 = torch.nn.Conv1d(128, self.k, 1)self.bn1 = nn.BatchNorm1d(512)self.bn2 = nn.BatchNorm1d(256)self.bn3 = nn.BatchNorm1d(128)def forward(self, x):batchsize = x.size()[0]n_pts = x.size()[2]x, trans, trans_feat = self.feat(x) # 提取特征 x:Bx1088xN trans:Bx3x3 trans_feat=Bx64x64x = F.relu(self.bn1(self.conv1(x))) # Bx1088xN --> Bx512xNx = F.relu(self.bn2(self.conv2(x))) # Bx512xN --> Bx256xNx = F.relu(self.bn3(self.conv3(x))) # Bx256xN --> Bx128xNx = self.conv4(x) # Bx128xN --> BxkxNx = x.transpose(2,1).contiguous() # BxNxKx = F.log_softmax(x.view(-1,self.k), dim=-1) # BxNxK-->B*NxK 然后过一个log_softmaxx = x.view(batchsize, n_pts, self.k) # BxNxKreturn x, trans, trans_feat # x:BxNxK, trans:Bx3x3, trans_feat:Bx64x64def feature_transform_regularizer(trans):d = trans.size()[1]batchsize = trans.size()[0]I = torch.eye(d)[None, :, :]if trans.is_cuda:I = I.cuda()loss = torch.mean(torch.norm(torch.bmm(trans, trans.transpose(2,1)) - I, dim=(1,2)))return lossif __name__ == '__main__':sim_data = Variable(torch.rand(32,3,2500))trans = STN3d()out = trans(sim_data)print('stn', out.size())print('loss', feature_transform_regularizer(out))sim_data_64d = Variable(torch.rand(32, 64, 2500))trans = STNkd(k=64)out = trans(sim_data_64d)print('stn64d', out.size())print('loss', feature_transform_regularizer(out))pointfeat = PointNetfeat(global_feat=True)out, _, _ = pointfeat(sim_data)print('global feat', out.size())pointfeat = PointNetfeat(global_feat=False)out, _, _ = pointfeat(sim_data)print('point feat', out.size())cls = PointNetCls(k = 5)out, _, _ = cls(sim_data)print('class', out.size())seg = PointNetDenseCls(k = 3)out, _, _ = seg(sim_data)print('seg', out.size())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25158.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat介绍

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,因为Tomcat 技术先进、性能稳定&…

DeepSeek-R1-Zero:基于基础模型的强化学习

注:此文章内容均节选自充电了么创始人,CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》(人工智能科学与技术丛书)【陈敬雷编著】【清华大学出版社】 文章目录 DeepSeek大模型技术系列四DeepSeek大模型技术系列四》DeepSeek-…

Redis初识

Redis是什么 Redis是一个在内存中存储数据的中间件,它可以用于作为数据库,用于作为数据的缓存,市面上作为数据缓存的不止Redis一家,但为啥我们要学习Redis呢?因为Redis有一些特性和优点,让Reids在市面上脱…

DeepSeek今日连开3源!针对优化的并行策略,梁文锋本人参与开发

DeepSeek开源周第四天,直接痛快「1日3连发」,且全都围绕一个主题: 优化并行策略。 DualPipe:一种创新的双向流水线并行算法,能够完全重叠前向和后向计算-通信阶段,并减少“流水线气泡”。它通过对称的微批…

打印九九乘法表

打印九九乘法表 package struct; ​ public class ForDemo04 {public static void main(String[] args) { ​for (int i 1; i < 9; i) {//System.out.println(1"*"i""(1*i));for (int j 1; j < i; j) {System.out.print(i"*"j"&qu…

机器学习的起点:线性回归Linear Regression

机器学习的起点&#xff1a;线性回归Linear Regression 作为机器学习的起点&#xff0c;线性回归是理解算法逻辑的绝佳入口。我们从定义、评估方法、应用场景到局限性&#xff0c;用生活化的案例和数学直觉为你构建知识框架。 回归算法 一、线性回归的定义与核心原理 定义&a…

DeepSeek 提示词:常见指令类型

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

查询NFT图片地址

前言 有人给我发了nft&#xff0c;但是没有图片&#xff0c;我就很纳闷为什么&#xff0c;所以想一探究竟 解决思路 先说下环境吧 Sepolia 测试网 metamask钱包 需要获取nft的合约地址和token id 钱包内 nft可以查得到 思路&#xff1a; 我的理解就是ERC721有标准的…

一个滑块可变色的Seekbar

因项目需要&#xff0c;做一个如下图的滑动条&#xff0c;要求如下&#xff1a; 1、滑块跟着进度条改变颜色 2、滑块有白色边和内部颜色组成 大体思路&#xff0c;就是背景需要UI按照需求提供&#xff0c;然后变色时&#xff0c;根据滑动回调动态设置对应的颜色。 直接上代码…

重大更新!锂电池剩余寿命预测新增 CALCE 数据集

往期精彩内容&#xff1a; 单步预测-风速预测模型代码全家桶-CSDN博客 半天入门&#xff01;锂电池剩余寿命预测&#xff08;Python&#xff09;-CSDN博客 超强预测模型&#xff1a;二次分解-组合预测-CSDN博客 VMD CEEMDAN 二次分解&#xff0c;BiLSTM-Attention预测模型…

实时时钟(RTC)/日历芯片PCF8563的I2C读写驱动(2):功能介绍

0 参考资料 PCF8563数据手册&#xff08;第 11 版——2015 年 10 月 26 日&#xff09;.pdf 1 功能介绍 1.1 实时时钟&#xff08;RTC&#xff09;/日历 &#xff08;1&#xff09;PCF8563支持实时时钟&#xff08;RTC&#xff09;&#xff0c;提供时、分、秒信息。对应寄存器…

Xcode如何高效的一键重命名某个关键字

1.选中某个需要修改的关键字&#xff1b; 2.右击&#xff0c;选择Refactor->Rename… 然后就会出现如下界面&#xff1a; 此时就可以一键重命名了。 还可以设置快捷键。 1.打开Settings 2.找到Key Bindings 3.搜索rename 4.出现三个&#xff0c;点击一个地方设置后其…

Grok 3 的崛起:AI 的新时代

AI 领域再次震动&#xff0c;一款全新的深度思考大型语言模型正式亮相。它不仅碾压了现有的各项基准测试&#xff0c;还成功登顶 LM Marina 排行榜&#xff0c;夺得第一名。这款 AI 不是别人&#xff0c;正是埃隆马斯克那款“基于事实、敢言无忌”的 Grok 3——一个号称既极为聪…

ros安装rqt_joint_trajectory_controller

有时候&#xff0c;我们可以看到别人的代码里面有这个&#xff0c;但是这个是需要安装的。 <node name"gui_controller" pkg"rqt_joint_trajectory_controller" type"rqt_joint_trajectory_controller" />sudo apt-get install ros-noeti…

ARM Linux LCD上实时预览摄像头画面

文章目录 1、前言2、环境介绍3、步骤4、应用程序编写4.1、lcd初始化4.2、摄像头初始化4.3、jpeg解码4.4、开启摄像头4.5、完整的程序如下 5、测试5.1、编译应用程序5.2、运行应用程序 6、总结 1、前言 本次应用程序主要针对支持MJPEG格式输出的UVC摄像头。 2、环境介绍 rk35…

是德科技keysight N5173B信号发生器,是一款经济高效的仪器

是德科技keysight N5173B信号发生器安捷伦N5173B信号源 是德N5173B微波模拟信号发生器&#xff0c;拥有 9 kHz 至 40 GHz 的频率覆盖范围&#xff0c;N5173B为宽带滤波器、放大器、接收机等器件的参数测试提供了必要的信号&#xff0c;是一款经济高效的仪器。 N5173B特点&…

【Redis】在Java中以及Spring环境下操作Redis

Java环境下&#xff1a; 1.创建maven 项目 2.导入依赖 <!-- redis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.2</version></dependency> 此处使用的是Jedis&…

registry 容器镜像测试

registry 封装容器部署环境测试 封装打包镜像 dockerfile # 阶段 1&#xff1a;构建阶段&#xff08;使用多阶段构建以减少最终镜像大小&#xff09; FROM golang:1.22-alpine AS builder # 安装构建所需工具 RUN #apk add --no-cache git # 设置工作目录 WORKDIR /app # 将…

Python视频网站(Django框架)

有需要请加文章底部Q哦 可远程调试 Python视频网站(Django框架) 一 介绍 此Python视频网站基于Django框架开发&#xff0c;数据库mysql&#xff0c;前端jquery.js。系统角色分为用户和管理员。 技术栈:Python3(Django框架)MySQLjquery.jsPyCharmnavicat 二 功能 用户 1 注册…

多元数据直观表示(R语言)

一、实验目的&#xff1a; 通过上机试验&#xff0c;掌握R语言实施数据预处理及简单统计分析中的一些基本运算技巧与分析方法&#xff0c;进一步加深对R语言简单统计分析与图形展示的理解。 二、实验内容&#xff1a; bank.csv文件中数据来自1969-1971年美国一家银行的474名职…