PointNet++.pytorch程序注释--点云分割

论文及程序地址

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

源程序
链接: https://github.com/yanx27/Pointnet2_pytorch.

自己标注的程序
链接: https://github.com/jiangdi1998/PointNet2_pytorch.git.

运行环境

硬件:i7-6700HQ、GTX960M-2G
软件:Ubuntu18.04、Python3.6、Pytorch1.6.0、cuda10.2
训练集:ShapeNet

pointnet++特征提取模块

Pointnet_Pointnet2_pytorch/models/pointnet_util.py

注释代码

import torch
import torch.nn as nn
import torch.nn.functional as F
from time import time
import numpy as np

def timeit(tag, t):
    print("{}: {}s".format(tag, time() - t))
    return time()

def pc_normalize(pc):    #归一化
    l = pc.shape[0] #点云长度
    centroid = np.mean(pc, axis=0) #对各列求均值
    pc = pc - centroid #减均值
    m = np.max(np.sqrt(np.sum(pc**2, axis=1))) #求最大
    pc = pc / m #求归一化
    return pc

def square_distance(src, dst):
    """
    Calculate Euclid distance between each two points. #计算两组点云每两点之间的欧氏距离
    其中src和dst的shape例如tensor([[x1,y1,z1],[x2,y2,z2],[x3,y3,z3]],
                           [[x4,y4,z4],[x5,y5,z5],[x6,y6,z6]])
    B = 2,N =3,C=3
    src^T * dst = xn * xm + yn * ym + zn * zm;
    sum(src^2, dim=-1) = xn*xn + yn*yn + zn*zn;
    sum(dst^2, dim=-1) = xm*xm + ym*ym + zm*zm;
    dist = (xn - xm)^2 + (yn - ym)^2 + (zn - zm)^2
         = sum(src**2,dim=-1)+sum(dst**2,dim=-1)-2*src^T*dst

    Input:
        src: source points, [B, N, C]
        dst: target points, [B, M, C]
    Output:
        dist: per-point square distance, [B, N, M] #B为batchsize,N为第一组点s数量,M为第二组点数量,C为通道数3
    """
    B, N, _ = src.shape #赋值给B,N
    _, M, _ = dst.shape #赋值给M
    dist = -2 * torch.matmul(src, dst.permute(0, 2, 1))#对应-2*src^T*dst= xn * xm + yn * ym + zn * zm,其中permute是为了求转置,matmul是乘法
    dist += torch.sum(src ** 2, -1).view(B, N, 1) #sum(src**2,dim=-1),view维度从xyz变成1维才能相乘求和
    dist += torch.sum(dst ** 2, -1).view(B, 1, M) #sum(dst**2,dim=-1)
    return dist


def index_points(points, idx):
    """

    Input:
        points: input points data, [B, N, C] #N为每个点云的点数目,原始各个点云N假设为2048
        idx: sample index data, [B, S]#输入参数S为[1,333,1000,2000],新的N=4,即从B个样本中取每个样本的取第1个点,第二个点云中取第333个点,第三个点云中取第1000个点,第四个点云中取第2000个点,新的点云集为B*4*3
    Return:
        new_points:, indexed points data, [B, S, C]
    """
    device = points.device
    B = points.shape[0]
    view_shape = list(idx.shape)
    view_shape[1:] = [1] * (len(view_shape) - 1)
    repeat_shape = list(idx.shape)
    repeat_shape[0] = 1
    batch_indices = torch.arange(B, dtype=torch.long).to(device).view(view_shape).repeat(repeat_shape)
    new_points = points[batch_indices, idx, :]
    return new_points


def farthest_point_sample(xyz, npoint): #采样点之间距离足够远
    """
    Input:
        xyz: pointcloud data, [B, N, 3]
        npoint: number of samples
    Return:
        centroids: sampled pointcloud index, [B, npoint]
    """
    device = xyz.device
    B, N, C = xyz.shape
    centroids = torch.zeros(B, npoint, dtype=torch.long).to(device) #初始化采样点矩阵B*npoint零矩阵,npoint为采样点数
    distance = torch.ones(B, N).to(device) * 1e10 #初始化距离,B*npoints矩阵每个值都是1e10
    farthest = torch.randint(0, N, (B,), dtype=torch.long).to(device) #随机初始化最远点,随机数范围是从0-N,一共是B个,维度是1*B,保证每个B都有一个最远点
    batch_indices = torch.arange(B, dtype=torch.long).to(device) #0~(B-1)的数组
    for i in range(npoint):#寻找并选取空间中每个点距离多个采样点的最短距离,并存储在dist
        centroids[:, i] = farthest #设采样点为farthers点,[:,i]为取所有行的第i个数据
        centroid = xyz[batch_indices, farthest, :].view(B, 1, 3) #取中心点也是farthest点
        dist = torch.sum((xyz - centroid) ** 2, -1) #求所有点到farthest点的欧式距离和
        mask = dist < distance
        distance[mask] = dist[mask]
        farthest = torch.max(distance, -1)[1]#返回最大距离的点
    return centroids


def query_ball_point(radius, nsample, xyz, new_xyz): #寻找球半径里面的点,从S个球内采样nsample个点
    """
    Input:
        radius: local region radius球半径
        nsample: max sample number in local region每个球所要采样的点数
        xyz: all points, [B, N, 3],全部点
        new_xyz: query points, [B, S, 3],S个球形领域中心点,由farthestpoint组成
    Return:
        group_idx: grouped points index, [B, S, nsample],输出球形领域采样点索引
    """
    device = xyz.device
    B, N, C = xyz.shape #原始点云的BNC
    _, S, _ = new_xyz.shape #由index_points得出的S,例如一共有S个球
    group_idx = torch.arange(N, dtype=torch.long).to(device).view(1, 1, N).repeat([B, S, 1]) #获取点云的各个点的序列位置
    sqrdists = square_distance(new_xyz, xyz)#计算中心点与所有点的欧式距离
    group_idx[sqrdists > radius ** 2] = N #大于欧氏距离平方的点序列标签设置为N
    group_idx = group_idx.sort(dim=-1)[0][:, :, :nsample]#升序排列,N是最大值,剩下的点为设定点数nsample的点
    group_first = group_idx[:, :, 0].view(B, S, 1).repeat([1, 1, nsample])#用第一个点代替nsample个点中被赋值为N的点
    mask = group_idx == N #把N点也替换成第一个点的值
    group_idx[mask] = group_first[mask]
    return group_idx


def sample_and_group(npoint, radius, nsample, xyz, points, returnfps=False):#每个点云被分割成group局部区域也就是每个球,使用Pointnet计算每个group全局特征
    """
    Input:
        npoint:
        radius:
        nsample:
        xyz: input points position data, [B, N, 3]
        points: input points data, [B, N, D]
    Return:
        new_xyz: sampled points position data, [B, npoint, nsample, 3]
        new_points: sampled points data, [B, npoint, nsample, 3+D]
    """
    B, N, C = xyz.shape
    S = npoint
    fps_idx = farthest_point_sample(xyz, npoint) # [B, npoint, C] fathest_point_sample函数索引到采样点
    torch.cuda.empty_cache()
    new_xyz = index_points(xyz, fps_idx) #通过index_points函数将最远点从原始点云中挑选出来作为新的xyz
    torch.cuda.empty_cache()
    idx = query_ball_point(radius, nsample, xyz, new_xyz)#将原始点云分割为每个球体,每个球体有nsample个采样点
    torch.cuda.empty_cache()
    grouped_xyz = index_points(xyz, idx) # [B, npoint, nsample, C]
    torch.cuda.empty_cache()
    grouped_xyz_norm = grouped_xyz - new_xyz.view(B, S, 1, C)#每个球体区域的点减去中心点
    torch.cuda.empty_cache()

    if points is not None:
        grouped_points = index_points(points, idx)
        new_points = torch.cat([grouped_xyz_norm, grouped_points], dim=-1) # [B, npoint, nsample, C+D]
    else:
        new_points = grouped_xyz_norm
    if returnfps:
        return new_xyz, new_points, grouped_xyz, fps_idx
    else:
        return new_xyz, new_points


def sample_and_group_all(xyz, points): #将所有点作为一个group,和上面相同
    """
    Input:
        xyz: input points position data, [B, N, 3]
        points: input points data, [B, N, D]
    Return:
        new_xyz: sampled points position data, [B, 1, 3]
        new_points: sampled points data, [B, 1, N, 3+D]
    """
    device = xyz.device
    B, N, C = xyz.shape
    new_xyz = torch.zeros(B, 1, C).to(device)
    grouped_xyz = xyz.view(B, 1, N, C)
    if points is not None:
        new_points = torch.cat([grouped_xyz, points.view(B, 1, N, -1)], dim=-1)
    else:
        new_points = grouped_xyz
    return new_xyz, new_points


class PointNetSetAbstraction(nn.Module):
    def __init__(self, npoint, radius, nsample, in_channel, mlp, group_all):
        super(PointNetSetAbstraction, self).__init__()
        self.npoint = npoint
        self.radius = radius
        self.nsample = nsample
        self.mlp_convs = nn.ModuleList()
        self.mlp_bns = nn.ModuleList()
        last_channel = in_channel
        for out_channel in mlp:
            self.mlp_convs.append(nn.Conv2d(last_channel, out_channel, 1))
            self.mlp_bns.append(nn.BatchNorm2d(out_channel))
            last_channel = out_channel
        self.group_all = group_all

    def forward(self, xyz, points):
        """
        Input:
            xyz: input points position data, [B, C, N]
            points: input points data, [B, D, N]
        Return:
            new_xyz: sampled points position data, [B, C, 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)

        if self.group_all:
            new_xyz, new_points = sample_and_group_all(xyz, points)
        else:
            new_xyz, new_points = sample_and_group(self.npoint, self.radius, self.nsample, xyz, points)
        # new_xyz: sampled points position data, [B, npoint, C]
        # new_points: sampled points data, [B, npoint, nsample, C+D]
        new_points = new_points.permute(0, 3, 2, 1) # [B, C+D, nsample,npoint]
        for i, conv in enumerate(self.mlp_convs):
            bn = self.mlp_bns[i] #对获取到的点做MLP操作
            new_points =  F.relu(bn(conv(new_points))) #归一化操作

        new_points = torch.max(new_points, 2)[0] #最大池化得到全局特征
        new_xyz = new_xyz.permute(0, 2, 1)
        return new_xyz, new_points


class PointNetSetAbstractionMsg(nn.Module): #MSG层,相比于普通的radius,这里是radius_list,例如[0.1,0.2,0.4],针对不同的半径做ball query,最后将不同半径下的点云特征保存在list里面,再拼接在一起
    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]:
                convs.append(nn.Conv2d(last_channel, out_channel, 1))
                bns.append(nn.BatchNorm2d(out_channel))
                last_channel = out_channel
            self.conv_blocks.append(convs)
            self.bn_blocks.append(bns)

    def forward(self, xyz, points):
        """
        Input:
            xyz: input points position data, [B, C, N]
            points: input points data, [B, D, N]
        Return:
            new_xyz: sampled points position data, [B, C, 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
        new_xyz = index_points(xyz, farthest_point_sample(xyz, S))
        new_points_list = []
        for i, radius in enumerate(self.radius_list): #针对不同半径,做不同的ball query
            K = self.nsample_list[i]
            group_idx = query_ball_point(radius, K, xyz, new_xyz)
            grouped_xyz = index_points(xyz, group_idx)
            grouped_xyz -= new_xyz.view(B, S, 1, C)
            if points is not None:
                grouped_points = index_points(points, group_idx)
                grouped_points = torch.cat([grouped_points, grouped_xyz], dim=-1)
            else:
                grouped_points = grouped_xyz

            grouped_points = grouped_points.permute(0, 3, 2, 1)  # [B, D, K, S]
            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)))
            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 = 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:
            interpolated_points = points2.repeat(1, N, 1)
        else: #线性插值,上采样
            dists = square_distance(xyz1, xyz2)
            dists, idx = dists.sort(dim=-1)
            dists, idx = dists[:, :, :3], idx[:, :, :3]  # [B, N, 3]

            dist_recip = 1.0 / (dists + 1e-8)
            norm = torch.sum(dist_recip, dim=2, keepdim=True)
            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

pointnet++网络层结构

在这里插入图片描述

运行训练程序命令

python3 train_cls.py --model pointnet2_cls_msg --normal --log_dir pointnet2_cls_msg

测试程序命令

python3 test_cls.py --normal --log_dir pointnet2_cls_msg

运行结果
在这里插入图片描述在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值