首页 > 其他分享 >PointNet++笔记

PointNet++笔记

时间:2024-08-11 17:49:59浏览次数:12  
标签:++ xyz PointNet 笔记 grouped points new self channel

pointnet++论文题目为:PointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space
在这篇文章中,作者对pointnet进行了一些改进,因为原始的pointnet对于规模较大的点云时,性能就显得不够了。在论文的摘要开头也指出了这一点:“However, by design PointNet does not capture local structures induced by the metric space points live in, limiting its ability to recognize fine-grained patterns and generalizability to complex scenes.”。因此,本文提出了一种层次的网络结构,在输入点集的部分循环地应用了pointnet(这也算是借鉴了resnet的一种思想吧),并且在摘要中作者也指出,这种方法可以捕获细粒度的局部结构。不仅如此,作者也指出:由于点集以不同密度进行采样,这会导致在均匀密度上训练的网络性能大大降低,所以,作者提出了集合学习层来自适应地组合多个尺度的特征。
根据这个摘要,我们可以大致的得出:pointnet++这个方法主要是提升了对点的集合的局部细节捕捉的一个能力,下面会详细分析论文中的具体细节和代码。

Introduction部分

这一部分主要是对网络结构进行了一个大体的介绍,以及需要解决的问题,文中指出:“The design of PointNet++ has to address two issues: how to generate the partitioning of the point set, and how to abstract sets of points or local features through a local feature learner.” 翻译过来就是pointnet++需要解决如何生成点集的划分,以及如何通过局部特征学习器抽象点集或局部的特征。

对于后者,作者使用了pointnet作为局部特征学习器。对于前者,如何生成点集的划分呢?事实上,作者将每个分区定义为底层欧几里得空间中的一个邻域球,通过最远点采样(FPS)方法在输入的点集中选择质心。这种方法比volumetric CNNs更加有效。

接下来我们看一下FPS的代码:

def farthest_point_sample(xyz, npoint):
    """
    Input:
        xyz: point cloud data, [B, N, 3]
        npoint: number of samples
    Return:
        centroids: sampled point cloud index, [B, npoint]
    """
    device = xyz.device
    B, N, C = xyz.shape
    # 先初始化一个全为0的矩阵
    centroids = torch.zeros(B, npoint, dtype=torch.long).to(device)
    # 距离初始化为一个非常大的值
    distance = torch.ones(B, N).to(device) * 1e10
    # 生成0-N范围内的数,(B,)是形状,一维张量
    farthest = torch.randint(0, N, (B,), dtype=torch.long).to(device)
    batch_indices = torch.arange(B, dtype=torch.long).to(device)
    # 循环选取各个点
    for i in range(npoint):
        # 初始化每列, 先随机选取一个值作为初始中心点
        centroids[:, i] = farthest
        # 选取中心点
        centroid = xyz[batch_indices, farthest, :].view(B, 1, 3)
        # 我们计算其与中心点之间的欧氏距离的平方
        dist = torch.sum((xyz - centroid) ** 2, -1)
        mask = dist < distance
        distance[mask] = dist[mask]
        # 选择距离最远的点作为下一个中心点,torch.max 返回最大值及其索引,这里选择索引部分。
        farthest = torch.max(distance, -1)[1]
    return centroids

首先我们需要明确FPS的目的是什么,就是要找到一组点,以这些点作为质心,再通过一个半径范围进行后续处理。那么FPS函数返回的就是一组质心点。在代码中,npoints代表我每个batch需要选取的点的个数。

Method部分

由于这个方法中也运用了pointnet的部分,所以不做过多赘述。下面来看一下网络的整体架构:
image
从图中不难看出,层次结构由许多抽象级别集合组成,在每个级别,都会对一组点进行处理和抽象,生成一个包含更少元素的新集合,每个级别由三个关键层组成:采样层、分组层和PointNet层。采样层从输入点中选择一组点,这定义了局部区域的质心。然后,分组层通过查找质心周围的“相邻”点来构造局部区域集。 PointNet 层使用迷你 PointNet 将局部区域模式编码为特征向量。这个层我们把它简称为SGP层。

Sampling layer

给定点集 {x1, x2, ..., xn},使用最远点采样(FPS)方法,选择点的子集 {xi1 , xi2 , ..., xim },使得xij是最远点,其中xi为随机挑选的一个点,这部分后续会在代码里讲到。

Grouping layer

该层的输入是大小为** N × (d + C)** 的点集和大小为 N ' × d 的质心集的坐标。输出是大小为 N ′× K × (d + C) 的点集组,其中每个组对应于一个局部区域,K 是质心点邻域中的点数。

PointNet layer

在该层中,输入是数据大小为 N '× K ×(d+C) 的点的 N ' 局部区域。输出中的每个局部区域都是由其质心和编码质心邻域的局部特征抽象的。输出数据大小为 N ′ × (d + C′)
另外,在这一层中,原文中还有这样一段话:
image
也就是说,我们首先要将局部区域中的点的坐标转换为相对于质心点的局部坐标系。

Robust Feature Learning under Non-Uniform Sampling Density 部分

这部分是非均匀采样密度下的鲁棒特征学习

点云数据大多数都是不均匀的,有稀疏也有密集的地方,在密集数据中学习的特征可能无法推广到稀疏采样区域。因此,针对稀疏点云训练的模型可能无法识别细粒度的局部结构。
针对这一问题,作者提出了密度自适应 PointNet 层,当输入采样密度发生变化时,该层学习组合不同尺度区域的特征。将具有密度自适应 PointNet 层的分层网络称为 PointNet++。在PointNet++中,每个抽象级别都会提取多个尺度的局部模式,并根据局部点密度将它们智能地组合起来。在对局部区域进行分组并结合不同尺度的特征方面,作者提出了两种类型的密度自适应层,如下所示。
image接下来我们对这两种方法进行分析。

感觉MSG和MRG还是主要看代码,下面就给出原文的翻译。

Multi-scale grouping (MSG) 多尺度分组.

如图 3(a)所示,捕获多尺度模式的一种简单但有效的方法是应用不同尺度的分组层,然后根据 PointNet 提取每个尺度的特征。不同尺度的特征连接起来形成多尺度特征。

我们训练网络学习结合多尺度特征的优化策略。这是通过对每个实例以随机概率随机丢弃输入点来完成的,我们称之为随机输入丢弃。具体来说,对于每个训练点集,我们选择从 [0, p] 中均匀采样的丢弃率 θ,其中 p ≤ 1。对于每个点,我们以概率 θ 随机丢弃一个点。在实践中,我们设置 p = 0.95 以避免生成空点集。在此过程中,我们向网络提供了各种稀疏性(由 θ 引起)和不同均匀性(由 dropout 的随机性引起)的训练集。在测试过程中,我们保留所有可用点。

Multi-resolution grouping (MRG) 多分辨率分组.

这种方法是对应MSG的一种改进,下面给出原文翻译:

上面的 MSG 方法的计算成本很高,因为它在每个质心点的大规模邻域中运行本地 PointNet。特别是,由于在最低级别质心点的数量通常相当大,因此时间成本非常高。

在这里,我们提出了一种替代方法,可以避免如此昂贵的计算,但仍然保留根据点的分布属性自适应聚合信息的能力。在图 3 (b) 中,某个级别 Li 的区域特征是两个向量的串联。通过使用设定的抽象级别从较低级别 Li−1 总结每个子区域的特征,获得一个向量(图中左侧)。另一个向量(右)是使用单个PointNet直接处理局部区域中的所有原始点获得的特征。

当局部区域的密度较低时,第一向量可能不如第二向量可靠,因为计算第一向量的子区域包含更稀疏的点并且更容易遭受采样不足。在这种情况下,第二个向量的权重应该更高。另一方面,当局部区域的密度较高时,第一向量提供更精细细节的信息,因为它具有在较低级别递归地以更高分辨率进行检查的能力。

与 MSG 相比,该方法在计算上更加高效,因为我们避免了在最低级别的大规模邻域中进行特征提取。

感觉读起来很抽象是不是,反正我自己是这么感觉的,后面还是看看代码吧。

Point Feature Propagation for Set Segmentation 用于集合分割的点特征分割

这段讲的是分割的部分,作者采用基于距离的插值和跨级跳跃链接的分层传播策略,这里原文的翻译是:在特征传播级别中,我们将点特征从 Nl × (d + C) 点传播到 Nl−1 点,其中 Nl−1 和 Nl(其中 Nl ≤ Nl−1)是集合抽象级别的输入和输出的点集大小湖我们通过在 Nl−1 点的坐标处插值 Nl 点的特征值 f 来实现特征传播。在插值的众多选择中,我们使用基于 k 个最近邻的反距离加权平均值(如方程 2 中,默认情况下我们使用 p = 2,k = 3)。然后,将 Nl−1 点上的插值特征与来自设置抽象级别的跳跃链接点特征连接起来。然后连接的特征通过“单位点网”,这类似于 CNN 中的一对一卷积。应用一些共享的全连接层和 ReLU 层来更新每个点的特征向量。重复该过程,直到我们将特征传播到原始点集。

后续我们看代码进一步理解

对于代码部分,我们拿一个训练的过程来分析,在文件结构中,可以看到有train_partseg.py和train_semseg.py,前者是对shapenet进行训练的代码,后者是针对于S3DIS这种场景分割的,我们拿后者来看。

首先,在train_semseg.py的大约111行处,注释着MODEL LOADING,我们就在这里开始看,前面的部分就是一些参数的设置和数据集加载部分,不做过多的解释,之后会新开一篇专题来讲解S3DIS这种数据集加载的代码部分。

下面是pointnet2.utils.py部分的代码注释,后面那个类也就是上面图中公式部分。

class PointNetSetAbstractionMsg(nn.Module):
    def __init__(self, npoint, radius_list, nsample_list, in_channel, mlp_list):
        super(PointNetSetAbstractionMsg, self).__init__()
        self.npoint = npoint # 要抽取的关键点数目
        self.radius_list = radius_list # 一个列表,表示不同尺度下的球形查询半径
        self.nsample_list = nsample_list # 表示每个半径内要采样的点数目。
        self.conv_blocks = nn.ModuleList()
        self.bn_blocks = nn.ModuleList()
        for i in range(len(mlp_list)):
            convs = nn.ModuleList() # 用于存储当前尺度下的所有卷积层。
            bns = nn.ModuleList() # 用于存储当前尺度下的所有批量归一化层。
            last_channel = in_channel + 3 # 三维坐标被视为附加通道。
            for out_channel in mlp_list[i]:
                # 在当前尺度下的卷积层列表中添加一个二维卷积层(Conv2d)。该卷积层的输入通道数为 last_channel,输出通道数为
                # out_channel,卷积核大小为 1×1。
                convs.append(nn.Conv2d(last_channel, out_channel, 1))
                bns.append(nn.BatchNorm2d(out_channel))
                last_channel = out_channel
            self.conv_blocks.append(convs) # 在最外层循环的每次迭代中,将当前尺度的卷积层列表 convs 添加到 conv_blocks 中。
            self.bn_blocks.append(bns)

    def forward(self, xyz, points):
        """
        Input:
            xyz: input points position data, [B, C(通常仅仅是xyz), N]
            points: input points data, [B, D(包括xyz), N]
        Return:
            new_xyz: sampled points position data, [B, C(3), S]
            new_points_concat: sample points feature data, [B, D'(新的通道数), S]
        """

        # 维度交换,通常通道数在最后
        xyz = xyz.permute(0, 2, 1)
        if points is not None:
            points = points.permute(0, 2, 1)
        B, N, C = xyz.shape
        S = self.npoint

        # 这里 S 是即将采样的点的个数,返回一组最新的点云数据。
        new_xyz = index_points(xyz, farthest_point_sample(xyz, S))
        new_points_list = []

        # 这个循环处理多尺度特征抽象(MSG),每个半径对应不同的尺度。
        for i, radius in enumerate(self.radius_list):
            K = self.nsample_list[i] # 获取当前尺度下要取样的点数量。

            # 以当前的 radius 和 K 为参数,查找在原始点云 xyz 中距离 new_xyz 中每个点不超过 radius 的 K 个邻居点的索引。
            # group_idx 是形状为 [B, S, K] 的张量,表示在每个批次中每个新点对应的 K 个邻居点的索引。
            group_idx = query_ball_point(radius, K, xyz, new_xyz)

            # 使用 index_points 函数,根据 group_idx 索引从 xyz 中提取邻居点的坐标,得到 grouped_xyz。
            # grouped_xyz 的形状为 [B, S, K, C],表示在每个批次中,S 个新点的 K 个邻居点的坐标。
            grouped_xyz = index_points(xyz, group_idx)

            # 对邻居点的坐标进行归一化处理,减去新点的坐标,使得邻居点的坐标相对新点的坐标。
            grouped_xyz -= new_xyz.view(B, S, 1, C)

            # 检查是否有额外的点特征数据 points(除了 xyz 之外的特征)。
            if points is not None:
                grouped_points = index_points(points, group_idx)

                # 将邻居点的特征 grouped_points 与相对坐标 grouped_xyz 连接在一起,形成新的特征张量。
                # 新的 grouped_points 的形状为 [B, S, K, D+C]。
                grouped_points = torch.cat([grouped_points, grouped_xyz], dim=-1)
            else:

                # 在这种情况下,只使用相对坐标 grouped_xyz 作为特征。
                grouped_points = grouped_xyz

            grouped_points = grouped_points.permute(0, 3, 2, 1)  # [B, D, K, S]

            # 卷积操作 这里相当于sgp的pointnet部分。
            for j in range(len(self.conv_blocks[i])):
                conv = self.conv_blocks[i][j]
                bn = self.bn_blocks[i][j]
                grouped_points = F.relu(bn(conv(grouped_points)))

            # 对第 2 维(即邻居点的维度 K)进行最大池化操作,提取局部区域内的最强特征,
            # 得到新的特征张量 new_points,其形状为 [B, D', S],D' 是卷积后输出的特征维度。
            new_points = torch.max(grouped_points, 2)[0]  # [B, D', S]
            new_points_list.append(new_points)

        new_xyz = new_xyz.permute(0, 2, 1)
        new_points_concat = torch.cat(new_points_list, dim=1)
        # 返回所有点集和对应特征
        return new_xyz, new_points_concat


# 特征传播部分,也就是上采样模块。
class PointNetFeaturePropagation(nn.Module):
    def __init__(self, in_channel, mlp):
        super(PointNetFeaturePropagation, self).__init__()
        self.mlp_convs = nn.ModuleList()
        self.mlp_bns = nn.ModuleList()
        last_channel = in_channel
        for out_channel in mlp:
            self.mlp_convs.append(nn.Conv1d(last_channel, out_channel, 1))
            self.mlp_bns.append(nn.BatchNorm1d(out_channel))
            last_channel = out_channel

    def forward(self, xyz1, xyz2, points1, points2):
        """
        Input:
            xyz1: input points position data, [B, C, N]
            xyz2: sampled input points position data, [B, C, S]
            points1: input points data, [B, D, N]
            points2: input points data, [B, D, S]
        Return:
            new_points: upsampled points data, [B, D', N]
        """

        # 这里xyz1和xyz2分别是输入点位置数据和采样点位置数据。
        xyz1 = xyz1.permute(0, 2, 1)
        xyz2 = xyz2.permute(0, 2, 1)

        points2 = points2.permute(0, 2, 1)
        B, N, C = xyz1.shape
        _, S, _ = xyz2.shape

        # if S == 1:: 如果采样点 S 的数量为 1,直接将 points2 复制 N 次,以匹配输入点的数
        # 量。这种情况下,不需要插值,因为所有输入点都对应相同的采样点。
        if S == 1:
            interpolated_points = points2.repeat(1, N, 1)
        else:
            # 计算 xyz1 和 xyz2 之间的欧氏距离,返回一个形状为 [B, N, S] 的距离矩阵 dists。
            dists = square_distance(xyz1, xyz2)
            dists, idx = dists.sort(dim=-1)
            # dists[:, :, :3], idx[:, :, :3]: 仅取最近的 3 个采样点的距离和索引,形状变为 [B, N, 3]。
            dists, idx = dists[:, :, :3], idx[:, :, :3]  # [B, N, 3]

            # dist_recip = 1.0 / (dists + 1e-8):
            # 计算距离的倒数 dist_recip,距离越近,权重越大。
            dist_recip = 1.0 / (dists + 1e-8)

            # norm = torch.sum(dist_recip,
            # dim=2, keepdim=True): 计算所有倒数的总和,用于归一化。
            norm = torch.sum(dist_recip, dim=2, keepdim=True)

            # 计算归一化后的权重,使得权重之和为1。
            weight = dist_recip / norm

            # 使用加权插值法计算每个输入点的插值特征
            interpolated_points = torch.sum(index_points(points2, idx) * weight.view(B, N, 3, 1), dim=2)

        if points1 is not None:
            points1 = points1.permute(0, 2, 1)
            new_points = torch.cat([points1, interpolated_points], dim=-1)
        else:
            new_points = interpolated_points

        new_points = new_points.permute(0, 2, 1)
        # 全连接层 卷积
        for i, conv in enumerate(self.mlp_convs):
            bn = self.mlp_bns[i]
            new_points = F.relu(bn(conv(new_points)))
        return new_points

标签:++,xyz,PointNet,笔记,grouped,points,new,self,channel
From: https://www.cnblogs.com/kyszdsmz/p/18353628

相关文章

  • 【C++】typeid与RTTI
    1、简介注意:typeid是操作符,不是函数。这点与sizeof类似)运行时获知变量类型名称,可以使用typeid(变量).name()需要注意不是所有编译器都输出”int”、”float”等之类的名称,对于这类的编译器可以这样使用。intia=3;if(typeid(ia)==typeid(int)){cout<<"int"<<e......
  • 【学习笔记】Matlab和python双语言的学习(图论最短路径)
    文章目录前言一、图论基本概念示例二、代码实现----Matlab三、代码实现----python总结前言通过模型算法,熟练对Matlab和python的应用。学习视频链接:https://www.bilibili.com/video/BV1EK41187QF?p=36&vd_source=67471d3a1b4f517b7a7964093e62f7e6一、图论图论(G......
  • 【C++学习笔记 16】构造函数初始化列表
    当编写类并向其中添加成员时,通常需要某种方式对这些成员进行初始化。常见的方法,如写一个构造函数赋初值classEntity{private: std::stringm_Name;public: Entity(){ m_Name="UnKnow"; } Entity(conststd::string&name){ m_Name=name; } constst......
  • 算法笔记|Day22回溯算法IV
    算法笔记|Day22回溯算法IV☆☆☆☆☆leetcode491.递增子序列题目分析代码☆☆☆☆☆leetcode46.全排列题目分析代码☆☆☆☆☆leetcode47.全排列II题目分析代码☆☆☆☆☆leetcode332.重新安排行程(待补充)题目分析代码☆☆☆☆☆leetcode51.N皇后(待补充)题目分析......
  • 论文笔记:GeoShapley: A Game Theory Approach toMeasuring Spatial Effects in Machin
    (GeoShapley:机器学习模型中测量空间效应的博弈论方法)话题点:geoshapley、XAI、空间效应、非线性一、引言机器学习和人工智能(AI)越来越多地用于模拟地理空间现象,在各个领域都有很好的表现。可解释人工智能(XAI)领域的最新进展为解释黑箱机器学习提供了一种解决方案。排列特征......
  • C++快速理解之面向对象
    文章目录1、定义类2、创建对象3、使用.访问成员4、使用->访问成员5、this指针6、构造函数7、析构函数8、重载对象C++中的类(Class)可以看做C语言中结构体(Struct)的升级版类是一个通用的概念,C++、Python、C#、PHP等很多编程语言中都支持类,都可以通过类创建对象......
  • 高等数学学习笔记(一)
    高等数学学习笔记最近入门了高等数学,特此记录一下学习到的重点。Chapter1实数与实数集这部分内容高中已经接触过很多了,仅补充一些未曾了解过的。1.完备性实数集不仅对加减乘除开方运算封闭,并且对于极限运算也封闭,这个性质被称为“完备性”。实数中的集合通常称为数集。2.......
  • 李宏毅-机器学习-笔记-P1
    P1机器学习基本概念(一)一、机器学习是什么?        MachineLearning≈Lookingforfunction、函数太过复杂,让机器来找比如:SpeechRecognition(语音识别):声音信号通过函数转化为文字,输入到输出      ImageRecognition(图像识别)、PlayingGo二、着重关......
  • 深入浅出!这份阿里内传的“Spring-MVC源码分析与实践笔记”带你看透Spring-MVC源码!太牛
    第二章常见协议和标准DNS协议TCP/IP协议与SocketHTTP协议Servlet与JavaWeb开发第三章DNS的设置DNS解析Windows7设置DNS服务器Windows设置本机域名和IP的对应关系第四章Java中Socket的用法普通Socket的用法NioSocket的用法第五章自己动手实现HTTP协议第六......
  • 汇编语言第二章寄存器(笔记、习题及拓展知识)(王爽汇编语言第四版)
    一、寄存器基础知识​一个典型的CPU(此处讨论的不是某一具体的CPU)由运算器、控制器、寄存器(CPU工作原理)等器件构成,这些器件靠内部总线相连。前一章所说的总线,相对于CPU内部来说是外部总线。内部总线实现CPU内部各个器件之间的联系,外部总线实现CPU和主板上其他器件的联......