pointnet++ 语义分割代码解析

准备工作:
在训练s3dis数据集之前,需要按照要求运行collect_indor3d_data.py文件,每个点打上标签,然后生成npy文件

Indoor_3d.util

原始数据集文件转换为data_label文件(每行为XYZRGBL)并写进npy文本中(一个)

def collect_point_label(anno_path, out_filename, file_format='txt'):
    """ Convert original dataset files to data_label file (each line is XYZRGBL).
   
        We aggregated all the points from each instance in the room.

    Args:
        anno_path: path to annotations. e.g. Area_1/office_2/Annotations/
        out_filename: path to save collected points and labels (each line is XYZRGBL)
        保存收集的点和标签的路径(每行都是XYZRGBL)
        file_format: txt or numpy, determines what file format to save.
    Returns:
        None
    Note:
        the points are shifted before save, the most negative point is now at origin.
    """
    points_list = []
    for f in glob.glob(os.path.join(anno_path, '*.txt')):
        """查找符合特定规则的文件路径名,返回所有匹配的文件路径列表"""
        cls = os.path.basename(f).split('_')[0]
        """将物品类名取出,例如:beam,board,board..."""
        """os.path.basename() 返回path最后的文件名"""
        print(f)
        if cls not in g_classes: # note: in some room there is 'staris' class..
            cls = 'clutter'

        """如果cls不在class_names里面,则为clutter"""
        points = np.loadtxt(f)
        """np.loadtxt()读取txt文件,读入数据文件,要求每一行数据的格式相同,XYZRGB"""
        labels = np.ones((points.shape[0],1)) * g_class2label[cls]
        """比如one生成(点云数量,1) * 该类别的索引编号,即为该类别所有点云打上了标签"""
        # g_class2label = {cls: i for i,cls in enumerate(g_classes)} 将class_names中的类名分索引
        points_list.append(np.concatenate([points, labels], 1)) # Nx7
        """
            np.concatenate()将列表变为numpy数组,列表进行拼接,xyz相对位置 + label 
            """
    
    data_label = np.concatenate(points_list, 0)
    xyz_min = np.amin(data_label, axis=0)[0:3]#取出所有坐标中的最小值
    """
        np.amin(a,axis),返回数组中的最小值
        """
    data_label[:, 0:3] -= xyz_min
    """坐标全部减去最小坐标,全部移动至原点处"""
    if file_format=='txt':
        fout = open(out_filename, 'w')
        for i in range(data_label.shape[0]):
            fout.write('%f %f %f %d %d %d %d\n' % \
                          (data_label[i,0], data_label[i,1], data_label[i,2],
                           data_label[i,3], data_label[i,4], data_label[i,5],
                           data_label[i,6]))
        fout.close()


        """
     将房间中包含的所有类别的点云数据全部写入一个npy文本中,
     """
    elif file_format=='numpy':
        np.save(out_filename, data_label)
    else:
        print('ERROR!! Unknown file format: %s, please use txt or numpy.' % \
            (file_format))
        exit()



***

# collect_indoor3d_data.py

***
创建文件夹stanford_indoor3d  并遍历所有点云,将每个房间中包含的所有类别的点云数据全部写入每个对应的npy文本中,

import os
import sys
from indoor3d_util import DATA_PATH, collect_point_label

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(BASE_DIR)
sys.path.append(BASE_DIR)

anno_paths = [line.rstrip() for line in open(os.path.join(BASE_DIR, 'meta/anno_paths.txt'))]

"""
读取出每个房间点云文件的相对路径,Annotatations中包含了桌子椅子等类别的点云文件,
包括xyz和RGB
"""


anno_paths = [os.path.join(DATA_PATH, p) for p in anno_paths]

""" 
读取绝对路径
"""

output_folder = os.path.join(ROOT_DIR, 'data/stanford_indoor3d')

"""创建了一个stanford_indoor3d文件夹用于存放npy文件"""


if not os.path.exists(output_folder):
    os.mkdir(output_folder)

# Note: there is an extra character in the v1.2 data in Area_5/hallway_6. It's fixed manually.
for anno_path in anno_paths:
    """
    将每一个Annotation文件夹中的文件读取出
    """
    print(anno_path)
    try:
        elements = anno_path.split('/')
        out_filename = elements[-3]+'_'+elements[-2]+'.npy' # Area_1_hallway_1.npy
        """获取npy文件名称"""
        collect_point_label(anno_path, os.path.join(output_folder, out_filename), 'numpy')
        """对应npy文件写入数据"""
        """
         将房间中包含的所有类别的点云数据全部写入一个npy文本中,
         """
    except:
        print(anno_path, 'ERROR!!')

S3DISDataLoader.py

每个房间划分几个子区域  
class S3DISDataset(Dataset):
    def __init__(self, split='train', data_root='trainval_fullarea', num_point=4096, test_area=5, block_size=1.0, sample_rate=1.0, transform=None):
        super().__init__()
        self.num_point = num_point
        self.block_size = block_size
        self.transform = transform
        rooms = sorted(os.listdir(data_root))#   data_root = 'data/s3dis/stanford_indoor3d/'
        rooms = [room for room in rooms if 'Area_' in room]
        "rooms里面存放的是之前转换好的npy数据的名字,例如:Area_1_conferenceRoom1.npy....这样的数据,含义:保留 Area_ 字符串开头的文件"
        """for room in rooms:
            if 'Area_' in room:
            rooms = rooms.append(room)"""
        if split == 'train':
            rooms_split = [room for room in rooms if not 'Area_{}'.format(test_area) in room]# area 1,2,3,4,6为训练区域,5为测试区域
        else:
            rooms_split = [room for room in rooms if 'Area_{}'.format(test_area) in room]

        self.room_points, self.room_labels = [], []# 每个房间的点云和标签
        self.room_coord_min, self.room_coord_max = [], []# 每个房间的最大值和最小值
        num_point_all = []# 初始化每个房间点的总数的列表
        labelweights = np.zeros(13)# 初始标签权重,后面用来统计标签的权重

        for room_name in tqdm(rooms_split, total=len(rooms_split)):#tqdm是 Python 进度条库,可以在 Python长循环中添加一个进度提示信息。用户只需要封装任意的迭代器,是一个快速、扩展性强的进度条工具库。
            room_path = os.path.join(data_root, room_name)
            room_data = np.load(room_path)  # xyzrgbl, N*7
            points, labels = room_data[:, 0:6], room_data[:, 6]  # xyzrgb, N*6; l, N  # xyzrgb, N*6; l, N 将训练数据与标签分开

            "前面已经将标签进行了分离,那么这里 np.histogram就是统计每个房间里所有标签的总数,例如,第一个元素就是属于类别0的点的总数"
            "将数据集所有点统计一次之后,就知道每个类别占总类别的比例,为后面加权计算损失做准备"

            tmp, _ = np.histogram(labels, range(14)) #应该只有13种??0-13 14种  这里打断点 还有一种是啥
            # 也就是有多少个点属于第i个类别
            labelweights += tmp
            coord_min, coord_max = np.amin(points, axis=0)[:3], np.amax(points, axis=0)[:3]
            # coord_min ,coord_max  寻找该房间中所有xyz的最小值和最大值 其输出形状为3 获取当前房间坐标的最值
            self.room_points.append(points), self.room_labels.append(labels)
            self.room_coord_min.append(coord_min), self.room_coord_max.append(coord_max)
            num_point_all.append(labels.size)
            # 标签的数量  也就是点的数量 第一次循环这里的值为1112933  "通过for循环后,所有的房间里类别分布情况和坐标情况都被放入了相应的变量中,后面就是计算权重了

            """它将所有的标签进行了累加,即将0-num_class的标签总数进行了累加,累加之后labelweights之中就记录了所有点云中的点的类别的分布情况。
            2. 将每个小房间的点的总数(label.size)放入列表num_point_all中
            3. 将点和标签放入相应列表中
            4.它将每个小房间的xyz坐标的最大值和最小值放到了对应的列表里"""



        labelweights = labelweights.astype(np.float32)
        labelweights = labelweights / np.sum(labelweights)
        # 计算标签的权重,每个类别的点云总数/总的点云总数
        "感觉这里应该是为了避免有的点数量比较少,计算出训练的iou占miou的比重太大,所以在这里计算一下加权(根据点标签的数量进行加权)"

        self.labelweights = np.power(np.amax(labelweights) / labelweights, 1 / 3.0)
        print(self.labelweights)
        sample_prob = num_point_all / np.sum(num_point_all)
        # 每个房间的点数占所有房间的点数之比  也就是需要采样的比例,比重越大 采样的点越多
        #首先计算了sample_prob(采样概率),即每个小房间的点数占整个数据集点数的比例,如果该小房间点数多,我们对应应该多采样点,如果小应该少采样。 然后根据这个比例来计算每个小房间总共需要划分多少个子点云
        num_iter = int(np.sum(num_point_all) * sample_rate / num_point)
        # 如果按 sample rate进行采样,那么每个区域用4096个点 计算需要采样的次数,值为476
        #计算应该划分为多少个子点云,它的含义就是将数据集的点数求和然后乘采样概率(超参数,这里为0.01)然后除以采样后子区域的点数(4096)  0.01可能要改
        room_idxs = []

        # 该处计算按照上面的划分规则,每个子区域需要采样多少次,才能将整个区域分完
        # 例如 假如第一个房间按照上述规则能够被划分七个区域  那么index里面的值 应该有7个0  其中0表示第一个区域 7 表示能分成几块
        for index in range(len(rooms_split)):
            room_idxs.extend([index] * int(round(sample_prob[index] * num_iter)))
        self.room_idxs = np.array(room_idxs)
        print("Totally {} samples in {} set.".format(len(self.room_idxs), split))
        """用来决定每个小房间应该采样多少个子点云的。它的计算方式如下:rooms_split的长度为小房间的数量,即需要训练的数据。用每个房间采样的概率乘以总共
        划分的房间数,就可以得到该小房间能够被划分几次,然后去乘以index, 就能将它复制多少份。例如
        假如第一个房间按照上述规则能够被划分七个区域,那么index里面的值应该有7个0,其中0表示第一个区,7表示能分成几块。"""


def __getitem__(self, idx):
        room_idx = self.room_idxs[idx] #通过前面的分析,已经self.room_idxs中存放的是每个小房间应该划分为几个子区域
        points = self.room_points[room_idx]   # N * 6
        labels = self.room_labels[room_idx]   # N
        N_points = points.shape[0]
        #它表示每个小房间能够划分为多少个区域,当通过idx索引时,第一次会读取到0,即第0个房间,然后读取room_points中第0个位置的点;当通过idx索引时,第二次还是会读取0,
        # 应该上面计算量这个小房间能够被(应该被)划分三次,
        # 所以还是会读room_points中的第0个位置,这样就能对小房间进行重复采样。但是此时得到的room_points的值还是未采样的,接下来还是需要采样
        while (True):#  这里是不是对应的就是将一个房间的点云切分为一个区域
            center = points[np.random.choice(N_points)][:3] #从该个房间随机选一个点作为中心点
            block_min = center - [self.block_size / 2.0, self.block_size / 2.0, 0]
            "找到符合要求点的索引(min<=x,y,z<=max),坐标被限制在最小和最大值之间"
            block_max = center + [self.block_size / 2.0, self.block_size / 2.0, 0]
            point_idxs = np.where((points[:, 0] >= block_min[0]) & (points[:, 0] <= block_max[0]) & (points[:, 1] >= block_min[1]) & (points[:, 1] <= block_max[1]))[0]
            if point_idxs.size > 1024:   #可能要改
                break
        "如果符合要求的点至少有1024个,那么跳出循环,否则继续随机选择中心点,继续寻找"
        if point_idxs.size >= self.num_point:  # 如果找到符合条件的点大于给定的4096个点,那么随机采样4096个点作为被选择的点
            selected_point_idxs = np.random.choice(point_idxs, self.num_point, replace=False)
        else:#如果符合条件的点小于4096 则随机重复采样凑够4096个点
            selected_point_idxs = np.random.choice(point_idxs, self.num_point, replace=True)

        # normalize
        selected_points = points[selected_point_idxs, :]  # num_point * 6  # num_point * 6 拿到筛选后的4096个点
        current_points = np.zeros((self.num_point, 9))  # num_point * 9
        current_points[:, 6] = selected_points[:, 0] / self.room_coord_max[room_idx][0]# 选择点的坐标/被选择房间的最大值  做坐标的归一化
        current_points[:, 7] = selected_points[:, 1] / self.room_coord_max[room_idx][1]
        current_points[:, 8] = selected_points[:, 2] / self.room_coord_max[room_idx][2]
        selected_points[:, 0] = selected_points[:, 0] - center[0]# 再将坐标移至随机采样的中心点
        selected_points[:, 1] = selected_points[:, 1] - center[1]
        selected_points[:, 3:6] /= 255.0# 颜色信息归一化
        current_points[:, 0:6] = selected_points
        current_labels = labels[selected_point_idxs]
        if self.transform is not None:
            current_points, current_labels = self.transform(current_points, current_labels)
        return current_points, current_labels

    def __len__(self):
        return len(self.room_idxs)
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
PointNet++是一种用于点云识别的神经网络算法,它包含基于点云数据的多个层次,可以适应不同大小和分辨率的点云。以下是PointNet++的一个简单实现示例。 ``` import tensorflow as tf from tensorflow.keras import layers def pointnet2_model(num_classes, input_shape): inputs = layers.Input(shape=input_shape) # Pointnet++ MSG x = layers.Conv1D(64, 1, activation='relu')(inputs) x = layers.BatchNormalization()(x) x = layers.Conv1D(64, 1, activation='relu')(x) x = layers.BatchNormalization()(x) # Set Abstraction Block 1 x0 = x x = layers.Conv1D(64, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Conv1D(64, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x1 = layers.Conv1D(64, 1)(x) x = layers.Conv1D(128, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Conv1D(128, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x2 = layers.Conv1D(128, 1)(x) x = layers.Conv1D(256, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Conv1D(256, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x3 = layers.Conv1D(256, 1)(x) # Set Abstraction Block 2 x = layers.Concatenate()([x1, x2, x3]) x = layers.Conv1D(256, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Conv1D(512, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Conv1D(1024, 1, activation='relu')(x) x = layers.BatchNormalization()(x) x4 = layers.Conv1D(1024, 1)(x) # Global Feature Extraction x = layers.MaxPooling1D(pool_size=input_shape[0])(x4) x = layers.Dense(512, activation='relu')(x) x = layers.BatchNormalization()(x) x = layers.Dense(256, activation='relu')(x) x = layers.BatchNormalization()(x) # Classification x = layers.Flatten()(x) outputs = layers.Dense(num_classes, activation='softmax')(x) # Model Definition model = tf.keras.Model(inputs=inputs, outputs=outputs, name='pointnet2') return model ``` 在这个示例中,我们定义了一个名为`pointnet2_model`的函数,该函数接受两个参数:`num_classes`是输出类别的数量,`input_shape`是输入点云数据的形状。函数返回一个Keras模型对象。 该模型包含两个Set Abstraction Block和一个Global Feature Extraction层,以及一个分类器层。每个Set Abstraction Block包含一个MSG层和三个PointNet++层,用于对点云数据进行多个分辨率的特征提取。Global Feature Extraction层将所有点的特征向量池化为一个全局特征向量,用于分类器。 在训练过程中,我们可以使用类似于其他神经网络模型的训练方法,如随机梯度下降(SGD)或自适应矩估计(Adam)。 ``` model = pointnet2_model(num_classes=10, input_shape=(1024, 3)) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, batch_size=32, epochs=100, validation_data=(x_val, y_val)) ``` 在这个示例中,我们使用了Adam优化器和交叉熵损失函数进行训练。我们还将训练数据分为批次,以便更有效地训练模型,并记录模型的准确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值