[PointNet代码详解]PointNet各模块代码实现超详细注释

pointnet.py pointnet模型各个模块的实现

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 F
​
# STN3d: T-Net 3*3 transform
# 类似一个mini-PointNet
class STN3d(nn.Module):
    def __init__(self, channel):
        super(STN3d, self).__init__()
        # torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
        self.conv1 = torch.nn.Conv1d(channel, 64, 1) 
        self.conv2 = torch.nn.Conv1d(64, 128, 1)#channel为输入他通道数, 为3指的是输入点云特征的三个通道(X,Y,Z),channel=6表示6个通道(X.Y.Z.和X,Y,Z方向的法向量)
        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) # 9=3*3
        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):#前向传播函数
        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)))
        # Symmetric function: max pooling
        x = torch.max(x, 2, keepdim=True)[0]#最大池化 这里得到了一个全局的特征
        # x参数展平(拉直)
        x = x.view(-1, 1024) #将全局特征展成一个1024列的特征,其中-1代表相应的行
​
        x = F.relu(self.bn4(self.fc1(x)))#1024降维到512后对512维的特征做相应的bn4(512)归一化之后进行relu的激活函数进行非线性。
        x = F.relu(self.bn5(self.fc2(x)))
        x = self.fc3(x)#9个元素
​
        # 展平的对角矩阵:np.array([1, 0, 0, 0, 1, 0, 0, 0, 1])
        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 # affine transformation,仿射变换,简单来说,“仿射变换”就是:“线性变换”+“平移”。
        # 用view,转换成batchsize*3*3的数组
        x = x.view(-1, 3, 3)
        return x
​
​
# STNkd: T-Net 64*64 transform,k默认是64
class STNkd(nn.Module):
    def __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 = k
​
    def 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)))
        # Symmetric function: max pooling
        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 + iden # affine transformation
        x = x.view(-1, self.k, self.k)
        return x
​
# PointNet编码器
class PointNetEncoder(nn.Module):
    def __init__(self, global_feat=True, feature_transform=False, channel=3):
        super(PointNetEncoder, self).__init__()
​
        self.stn = STN3d(channel) # STN3d: T-Net 3*3 transform
        self.conv1 = torch.nn.Conv1d(channel, 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_feat  #是否需要全局的特征
        self.feature_transform = feature_transform#是否需要特征变换
        if self.feature_transform:
            self.fstn = STNkd(k=64) # STNkd: T-Net 64*64 transform
​
    def forward(self, x):
        B, D, N = x.size() # batchsize,3(xyz坐标)或6(xyz坐标+法向量),1024(一个物体所取的点的数目)
        trans = self.stn(x) # STN3d T-Net
        x = x.transpose(2, 1) # 交换一个tensor的两个维度
        if D >3 :
            x, feature = x.split(3,dim=2)
        # 对输入的点云进行输入转换(input transform)    
        # input transform: 计算两个tensor的矩阵乘法
        # bmm是两个三维张量相乘, 两个输入tensor维度是(b×n×m)和(b×m×p), 
        # 第一维b代表batch size,输出为(b×n×p)
        x = torch.bmm(x, trans)
        if D > 3:
            x = torch.cat([x,feature],dim=2) #矩阵的拼接,0代表竖着拼接,1代表横着拼接
        x = x.transpose(2, 1)
        x = F.relu(self.bn1(self.conv1(x))) # MLP
​
        if self.feature_transform:
            trans_feat = self.fstn(x) # STNkd T-Net
            x = x.transpose(2, 1)
            # 对输入的点云进行特征转换(feature transform)
            # feature transform: 计算两个tensor的矩阵乘法
            x = torch.bmm(x, trans_feat)
            x = x.transpose(2, 1)
        else:
            trans_feat = None
​
        pointfeat = x # 局部特征
        x = F.relu(self.bn2(self.conv2(x))) # MLP
        x = self.bn3(self.conv3(x)) # MLP
        x = torch.max(x, 2, keepdim=True)[0] # 最大池化得到全局特征
        x = x.view(-1, 1024) # 展平
        if self.global_feat: # 需要返回的是否是全局特征?
            return x, trans, trans_feat # 返回全局特征
        else:
            x = x.view(-1, 1024, 1).repeat(1, 1, N)
            # 返回局部特征与全局特征的拼接
            return torch.cat([x, pointfeat], 1), trans, trans_feat
​
# 对特征转换矩阵做正则化:
# constrain the feature transformation matrix to be close to orthogonal matrix
def feature_transform_reguliarzer(trans): #让feature transform接近于一个正交的矩阵
    d = trans.size()[1]
    I = torch.eye(d)[None, :, :] # torch.eye(n, m=None, out=None) 返回一个2维张量,对角线位置全1,其它位置全0。单位阵
    if trans.is_cuda:
        I = I.cuda()
        
    # 正则化损失函数
    loss = torch.mean(torch.norm(torch.bmm(trans, trans.transpose(2, 1) - I), dim=(1, 2))) #a*a的转置-单位阵后求范数后再平均=正则化的loss
    return loss#用来将特征转换矩阵做正则化的约束
无序的。输入是欧几里德空间中点的子集。它有三个主要的属性:与图像中的像素阵列或体素网格中的体素阵列不同,点云是一组没有特定顺序的点。

点之间的交互作用。这些点来自一个有距离度量的空间。它意味着点不是孤立的,相邻点形成一个有意义的子集。因此,该模型需要能够从附近的点捕获局部结构,以及局部结构之间的组合相互作用。

变换不变性。作为一个几何对象,学习到的点集表示应该对某些变换是不变的。例如,同时旋转和平移点不应该改变全局点云的类别,也不应该改变点的分割。

它采用了两次STN(Spatial Transformer Networks),第一次input transform是对空间中点云进行调整,直观上理解是旋转出一个更有利于分类或分割的角度,比如把物体转到正面;第二次feature transform是对提取出的64维特征进行对齐,即在特征层面对点云进行变换。

网络分成了分类网络和分割网络两个部分,大体思路类似,都是设计表征的过程分类网络设计global feature,分割网络设计point-wise feature两者都是为了让表征尽可能discriminative,也就是同类的能分到一类,不同类的距离能拉开。

conclusion

第一篇直接用于点云数据处理的三为点云分割网络,结构简单,逻辑清晰,秒杀之前多视图,体素等方法。不用过多的去进行数据预处理。大大降低了点云模型的复杂度。影响力颇深。

2023.3.27 记录James.King缺席13场比赛后替补复出。出战29’31‘’拿下19分8篮板3助攻惜败公牛

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
PointNet是一种用于处理三维点云数据的深度学习模型。它是由Charles R. Qi等人在2017年提出的。PointNet的目标是对不同形状的物体进行分类、分割和识别等任务。 PointNet的网络结构包括两个主要部分:特征提取网络和全局特征编码器。特征提取网络将输入的点云数据转化为局部特征,而全局特征编码器则将局部特征集成为全局特征。具体而言,特征提取网络包含几个全连接神经网络和最大池化层,用于提取每个点的局部特征。全局特征编码器则使用一个全连接神经网络,将所有点的局部特征整合为全局特征。最后,全连接神经网络将全局特征映射到具体的任务空间中,例如物体分类、物体分割等。 PointNet的特点是可以对点云数据进行任意排序和排列,从而能够处理不同形状和大小的物体。此外,PointNet还可以处理不完整和噪声干扰的点云数据,具有较强的鲁棒性。 关于PointNet的代码详解,可以参考以下资源: - PointNet的原始论文提供了详细的网络结构和算法描述,可以通过阅读该论文来深入了解代码实现细节。 - 在GitHub上可以找到PointNet的开源代码,可以通过下载和阅读代码来了解其实现方式和具体细节。 - 在相关的博客和教程中,也有人对PointNet的代码进行了解析和解释,可以通过阅读这些博客和教程来获取更多的代码解释和示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值