PointNet论文解读和代码解析

目录

一、论文动机

现有的问题:

作者的思路及面临的问题:

二、论文方法

如何解决点云无序性问题?作者提出了三种想法。

针对点云的刚体运动不变性

三、网络结构

四、代码阅读

五、Reference(两篇都是对原论文的翻译)


论文地址:https://arxiv.org/pdf/1612.00593.pdf

代码地址:https://github.com/yanx27/Pointnet_Pointnet2_pytorch

一、论文动机

现有的问题:

因为卷积神经网络一般都要求高度规则的输入数据格式,如图像和3D体素,所以现有的方法一般是把点云转换成体素格式或者投影到二维图中。但是由于算力的限制,体素的分辨率最多只能达到32*32*32,而且体素是立方体,对于点云表面的特征都没有表达出来。

作者的思路及面临的问题:

我们能否设计一个神经网络直接对点云的特征进行学习,而不进行转换?

面临两个问题:1.如何解决点云的无序性问题

                          2.如何保证点云的刚体运动不变性

二、论文方法

如何解决点云无序性问题?作者提出了三种想法。

1.对点云排序之后送入——高维空间不存在稳定的排序,因为有点的扰动。

2.将点云作为一个序列来用RNN处理——RNN对于长度较小的序列具有很好的鲁棒性,但点云序列太长了

3.使用对称函数来解决无序性问题,如均值池化,最大池化。实验证明最大池化效果最好。

针对点云的刚体运动不变性

引入两个对齐输入点和点特征的联合对齐网络,在点云进行特征提取之前将所有的输入对齐到规范空间。通过T-Net网络预测一个仿射变换矩阵,与点云进行相乘。

为什么联合对齐网络可以引入刚体运动不变性?

三、网络结构

 原理就是对输入的点云先进行对齐,引入刚体运动不变性,然后通过MLP对特征升维,再对特征进行对齐,然后再MLP升维特征,这时进行最大池化,解决无序性问题,得到1024维的全局特征,如果是分类,则直接对全局特征进行MLP操作,输出k维结果就是分类结果。如果是分割的话,则把全局特征与每个点的特征进行拼接,然后两次MLP得到每个点的分割结果。

四、代码阅读

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

#nn.relu模块调用一般用在init里,而F.relu是函数调用一般用在forward里
#Conv1d和Linear的区别,当你必须保留语义分割中的空间信息时,使用卷积 Conv1d() 。当你不需要做任何与空间信息相关的事情时,比如在基本分类(mnist、猫狗分类器)中,使用线性层 Linear() 
class STN3d(nn.Module):  #第一个T-Net
    def __init__(self, channel):
        super(STN3d, self).__init__()
        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.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):
        batchsize = x.size()[0] #x-(B,C,N)N表示n个点,C表示特征数
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x))) #(B,1024,N)
        x = torch.max(x, 2, keepdim=True)[0] #只返回最大值的每个数(B,1024,1)
        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)  #(B,9)

        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)  #tensor不能反向传播,variable可以反向传播。同时在单位矩阵的基础上进行调整
        if x.is_cuda:
            iden = iden.cuda()
        x = x + iden
        x = x.view(-1, 3, 3)
        return x


class STNkd(nn.Module):   #特征T-Net
    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)))
        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
        x = x.view(-1, self.k, self.k)
        return x


class PointNetEncoder(nn.Module):
    def __init__(self, global_feat=True, feature_transform=False, channel=3):
        super(PointNetEncoder, self).__init__()
        self.stn = STN3d(channel)
        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)

    def forward(self, x):
        B, D, N = x.size()
        trans = self.stn(x)
        x = x.transpose(2, 1) #交换矩阵的两个维度,也可以torch.transpose
        if D > 3:
            feature = x[:, :, 3:]
            x = x[:, :, :3]
        x = torch.bmm(x, trans) #计算两个tensor的矩阵乘法,torch.bmm(a,b),tensor a 的size为(b,h,w),tensor b的size为(b,w,m) 也就是说两个tensor的第一维是相等的,然后第一个数组的第三维和第二个数组的第二维度要求一样,对于剩下的则不做要求,输出维度 (b,h,m)
        if D > 3:
            x = torch.cat([x, feature], dim=2)
        x = x.transpose(2, 1)
        x = F.relu(self.bn1(self.conv1(x)))  #(B,64,N)

        if self.feature_transform:
            trans_feat = self.fstn(x)
            x = x.transpose(2, 1)
            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))) #(B,128,N)
        x = self.bn3(self.conv3(x))   #(B,1024,N)
        x = torch.max(x, 2, keepdim=True)[0]
        x = x.view(-1, 1024)   #(B,1024)
        if self.global_feat:
            return x, trans, trans_feat
        else:
            x = x.view(-1, 1024, 1).repeat(1, 1, N)   #(B,1024,N)
            return torch.cat([x, pointfeat], 1), trans, trans_feat  #如果有分割,则返回合并后的特征

##对齐特征的时候,由于特征空间维度更高,优化难度大,所以加了一项正则项,让求解出来的仿射变换矩阵接近于正交,这里返回的在后面损失函数会用到
#扩充维度可以先view扩充一维,再repeat
def feature_transform_reguliarzer(trans):
    d = trans.size()[1]
    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))) #torch.norm默认求所有元素的平方和,dim指定维度,是在一二维求,得到(B,loss)再求平均 
    return loss

#损失函数nn.CrossEntropyLoss()=F.log_softmax() + F.nll_loss()

五、Reference(两篇都是对原论文的翻译)

(3条消息) 【论文翻译】从零开始PointNet论文分析与代码复现_花花大魔王的博客-CSDN博客

PointNet 文献阅读及拓展阅读_万俟淋曦的博客-CSDN博客

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CVplayer111

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值