• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

S3DIS数据集学习笔记

武飞扬头像
正在学习的浅语
帮助1

1.整体介绍

S3DIS是一个大型的3d室内数据集。S3DIS数据集共五个区域 共271个房间 每个区域有多个物体,每个物体的类别有一个对应的txt文件,txt文件中存储的都是点的坐标和颜色信息,其类别用文件名进行表示。
学新通
五个区域
学新通
每个区域中有多个房间,每个房间中有多个物体。如下图所示
学新通
每个物体用txt文件存储,每个txt存储了物体坐标和颜色信息。**特别要注意的是,该txt文件中没有存储标签信息,其标签信息是用文件名称来进行区分。例如chair txt文件中的数据都是其中一个椅子的点云数据。因此,在正式处理之前,需要进行处理,为每个点补上标签。
上述这个过程的实现是通过PointNet 中的collect_indoor3d_data.py实现的。该脚本文件为每个点生成了标签,并转换为npy文件,加速文件的读取过程。

2.数据集代码分析

数据集的整体流程如下:
学新通
在训练s3dis数据集之前,需要按照要求运行collect_indor3d_data.py文件,生成npy文件,并给每个像素点打上标签。

由于这个数据集比较大,不利于训练,PointNet 训练之前会将它进行预处理。对每个房间点的数量对其进行随机采样,随机划分划分为1mx1m的block,然后再放入网络进行训练。

对于数据集的划分,又牵扯到非常多的问题了,例如 如何采样才能使选出的点云信息尽可能少丢失,如何才能保证覆盖到全部点,将大点云分割多少个小点云比较合适,这些都是值得思考的问题。

**

3.初始化方法

**
我们以测试区域5为例,看一下PointNet2中对S3DIS的初始化方法。

    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 # 4096
        self.block_size = block_size # 1.0
        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] # 'Area_1_WC_1.npy' # 'Area_1_conferenceRoom_1.npy'
        "rooms里面存放的是之前转换好的npy数据的名字,例如:Area_1_conferenceRoom1.npy....这样的数据"

        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]
        "按照指定的test_area划分为训练集和测试集,默认是将区域5作为测试集"

        #创建一些储存数据的列表
        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)):
            #每次拿到的room_namej就是之前划分好的'Area_1_WC_1.npy'
            room_path = os.path.join(data_root, room_name) #每个小房间的绝对路径,根路径 .npy
            # .astype(np.long)  # 加载数据 xyzrgbl,  (1112933, 7) N*7  room中点云的值 最后一个是标签#
            room_data = np.load(room_path).astype(np.float32)
            points, labels = room_data[:, 0:6], room_data[:, 6]  # xyzrgb, N*6; l, N 将训练数据与标签分开
            #self.p ,self.l = points,labels
            "前面已经将标签进行了分离,那么这里 np.histogram就是统计每个房间里所有标签的总数,例如,第一个元素就是属于类别0的点的总数"
            "将数据集所有点统计一次之后,就知道每个类别占总类别的比例,为后面加权计算损失做准备"
            tmp, _ = np.histogram(labels, range(14)) # 统计标签的分布情况 [192039 185764 488740      0      0      0  28008      0      0      0,      0      0 218382]
            #也就是有多少个点属于第i个类别
            labelweights  = tmp # 将它们累计起来
            #coord_min ,coord_max  寻找该房间中所有xyz的最小值和最大值 其输出形状为3
            coord_min, coord_max = np.amin(points, axis=0)[:3], np.amax(points, axis=0)[: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循环后,所有的房间里类别分布情况和坐标情况都被放入了相应的变量中,后面就是计算权重了"

这个for循环会将需要训练的数据依次遍历一遍,读取每个小房间的坐标和标签(这里总共有204个小房间参与训练),通过debug可知,每个小房间都有数十万的点,而我们仅需要采样4096个点,如何才能合理采样呢。
这里我们先按下不表,先简单理解这段代码的功能。可以发现:
1.它将所有的标签进行了累加,即将0-num_class的标签总数进行了累加,累加之后labelweights之中就记录了所有点云中的点的类别的分布情况。
2. 将每个小房间的点的总数放入列表num_point_all中
3. 将点和标签放入相应列表中
4.它将每个小房间的xyz坐标的最大值和最小值放到了对应的列表里(这里np.amin计算的是每列的最小值,也就是所有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('label weight\n') #通过上述操作又将labelweights缩放到大于 1 的数了
        print(self.labelweights)
        sample_prob = num_point_all / np.sum(num_point_all) # 每个房间的点数占所有房间的点数之比  也就是需要采样的比例,比重越大 采样的点越多
        num_iter = int(np.sum(num_point_all) * sample_rate / num_point)  # 如果按 sample rate进行采样,那么每个区域用4096个点 计算需要采样的次数,值为476

对整个数据集,我们应该将其划分为多少个子点云? 可以看到,num_iter = int(np.sum(num_point_all) * sample_rate / num_point) 一行代码就是用来计算应该划分为多少个子点云,它的含义就是将数据集的点数求和然后乘采样概率(超参数,这里为0.01)然后除以采样后子区域的点数(4096)

那么每个小房间又应该如何采样呢,或者说,总不能对所有对所有房间一同对待吧(因为每个房间的点数就不一样)
代码中首先计算了sample_prob(采样概率),即每个小房间的点数占整个数据集点数的比例,如果该小房间点数多,我们对应应该多采样点,如果小应该少采样。 然后根据这个比例来计算每个小房间总共需要划分多少个子点云,这个变量很关键,后面会用到。

         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 表示能分成几块。

上面就是整个初始化函数里面做的事情了,变量比较多,也比较复杂,其中重要的变量已经进行了详细的解释,请读者仔细思考一下带self的类变量的含义,以便更好的分析getitem方法。

4. getitem方法

getitem中解决的就是如何读取数据的问题,即如何具体的划分数据。接下来还是一段一段的带着大家进行分析。

 def __getitem__(self, idx):
        #通过前面的分析,已经self.room_idxs中存放的是每个小房间应该划分为几个子区域
        room_idx = self.room_idxs[idx]
        points = self.room_points[room_idx]   # N * 6 --》 debug 1112933,6
        labels = self.room_labels[room_idx]   # N
        N_points = points.shape[0]

room_idxs的值如下所示
学新通
它表示每个小房间能够划分为多少个区域,当通过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]
            block_max = center   [self.block_size / 2.0, self.block_size / 2.0, 0]
            "找到符合要求点的索引(min<=x,y,z<=max),坐标被限制在最小和最大值之间"
            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]
            "如果符合要求的点至少有1024个,那么跳出循环,否则继续随机选择中心点,继续寻找"
            if point_idxs.size > 1024:
                break

采样过程也是比较简单,先将点的数据利用random_choice进行打乱,选取第一个点作为中心点,以这个中心点为中心,传入的窗口尺寸为半径,画一个窗口。
学新通
基本上就是图上这个意思,通过这个窗口获得一个蒙版,然后去找符合条件的点,如果符合条件的点大于1024则退出循环,否则的话 更换中心点,再次打乱数据,进行重复操作。
对于大于1024个点的这个条件,我也不知道作者为什么要设置这个值(可能认为大于1024就能提取到一个区域的特征吧,个人觉得这里设置4096也是可以的),但是通过调试来看这个值是很容易满足的(因为小房间的点数足够多)。

学新通
从上图中可以发现,第一次就能找到40016个符合条件的点。

        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 拿到筛选后的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

这一部分代码就比较容易了,主要就是对s3dis的后三维坐标进行补充,中心坐标的偏移,颜色信息的归一化和标签的选择。

下面是完整的代码

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 # 4096
        self.block_size = block_size # 1.0
        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] # 'Area_1_WC_1.npy' # 'Area_1_conferenceRoom_1.npy'
        "rooms里面存放的是之前转换好的npy数据的名字,例如:Area_1_conferenceRoom1.npy....这样的数据"

        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]
        "按照指定的test_area划分为训练集和测试集,默认是将区域5作为测试集"

        #创建一些储存数据的列表
        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)):
            #每次拿到的room_namej就是之前划分好的'Area_1_WC_1.npy'
            room_path = os.path.join(data_root, room_name) #每个小房间的绝对路径,根路径 .npy
            # .astype(np.long)  # 加载数据 xyzrgbl,  (1112933, 7) N*7  room中点云的值 最后一个是标签#
            room_data = np.load(room_path).astype(np.float32)
            points, labels = room_data[:, 0:6], room_data[:, 6]  # xyzrgb, N*6; l, N 将训练数据与标签分开
            #self.p ,self.l = points,labels
            "前面已经将标签进行了分离,那么这里 np.histogram就是统计每个房间里所有标签的总数,例如,第一个元素就是属于类别0的点的总数"
            "将数据集所有点统计一次之后,就知道每个类别占总类别的比例,为后面加权计算损失做准备"
            tmp, _ = np.histogram(labels, range(14)) # 统计标签的分布情况 [192039 185764 488740      0      0      0  28008      0      0      0,      0      0 218382]
            #也就是有多少个点属于第i个类别
            labelweights  = tmp # 将它们累计起来
            #coord_min ,coord_max  寻找该房间中所有xyz的最小值和最大值 其输出形状为3
            coord_min, coord_max = np.amin(points, axis=0)[:3], np.amax(points, axis=0)[: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循环后,所有的房间里类别分布情况和坐标情况都被放入了相应的变量中,后面就是计算权重了"
        labelweights = labelweights.astype(np.float32)
        labelweights = labelweights / np.sum(labelweights) # 计算标签的权重,每个类别的点云总数/总的点云总数
        "感觉这里应该是为了避免有的点数量比较少,计算出训练的iou占miou的比重太大,所以在这里计算一下加权(根据点标签的数量进行加权)"
        self.labelweights = np.power(np.amax(labelweights) / labelweights, 1 / 3.0)
        print('label weight\n') #通过上述操作又将labelweights缩放到大于 1 的数了
        print(self.labelweights)
        sample_prob = num_point_all / np.sum(num_point_all) # 每个房间的点数占所有房间的点数之比  也就是需要采样的比例,比重越大 采样的点越多
        num_iter = int(np.sum(num_point_all) * sample_rate / num_point)  # 如果按 sample rate进行采样,那么每个区域用4096个点 计算需要采样的次数,值为476
        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))

    def __getitem__(self, idx):
        #通过前面的分析,已经self.room_idxs中存放的是每个小房间应该划分为几个子区域
        room_idx = self.room_idxs[idx]
        points = self.room_points[room_idx]   # N * 6 --》 debug 1112933,6
        labels = self.room_labels[room_idx]   # N
        N_points = points.shape[0]

        while (True):  #  这里是不是对应的就是将一个房间的点云切分为一个区域
            center = points[np.random.choice(N_points)][:3]  #从该个房间随机选一个点作为中心点
            block_min = center - [self.block_size / 2.0, self.block_size / 2.0, 0]
            block_max = center   [self.block_size / 2.0, self.block_size / 2.0, 0]
            "找到符合要求点的索引(min<=x,y,z<=max),坐标被限制在最小和最大值之间"
            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]
            "如果符合要求的点至少有1024个,那么跳出循环,否则继续随机选择中心点,继续寻找"
            if point_idxs.size > 1024:
                break
            "这里可以尝试修改一下1024这个参数,感觉采4096个点的话,可能存在太多重复的点"
        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 拿到筛选后的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)

整体流程

学新通

其实这个代码处理数据挺慢的,建议使用hdf5格式来读入数据,要快一些。
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgffjcc
系列文章
更多 icon
同类精品
更多 icon
继续加载