准备工作:
在训练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)