赞
踩
S3DIS是一个大型的3d室内数据集。S3DIS数据集共五个区域 共271个房间 每个区域有多个物体,每个物体的类别有一个对应的txt文件,txt文件中存储的都是点的坐标和颜色信息,其类别用文件名进行表示。
五个区域
每个区域中有多个房间,每个房间中有多个物体。如下图所示
每个物体用txt文件存储,每个txt存储了物体坐标和颜色信息。**特别要注意的是,该txt文件中没有存储标签信息,其标签信息是用文件名称来进行区分。例如chair txt文件中的数据都是其中一个椅子的点云数据。因此,在正式处理之前,需要进行处理,为每个点补上标签。
上述这个过程的实现是通过PointNet++中的collect_indoor3d_data.py实现的。该脚本文件为每个点生成了标签,并转换为npy文件,加速文件的读取过程。
数据集的整体流程如下:
在训练s3dis数据集之前,需要按照要求运行collect_indor3d_data.py文件,生成npy文件,并给每个像素点打上标签。
由于这个数据集比较大,不利于训练,PointNet++训练之前会将它进行预处理。对每个房间点的数量对其进行随机采样,随机划分划分为1mx1m的block,然后再放入网络进行训练。
对于数据集的划分,又牵扯到非常多的问题了,例如 如何采样才能使选出的点云信息尽可能少丢失,如何才能保证覆盖到全部点,将大点云分割多少个小点云比较合适,这些都是值得思考的问题。
**
**
我们以测试区域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方法。
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的后三维坐标进行补充,中心坐标的偏移,颜色信息的归一化和标签的选择。
以上就是我对s3dis数据集代码的理解,这个里面貌似有些变量没有使用,例如labelweight,我也不知道是干嘛的,知道的同学可以告诉我一下。
下面是完整的代码
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格式来读入数据,要快一些。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。