本文共 6386 字,大约阅读时间需要 21 分钟。
前段时间在做点云深度学习网络课题时,随手在博客“”中放了两串代码,目的是方便自己的存取(当时不会GIT的菜鸟)。结果成了所写博客中访问最多,提问最多的博文(瑟瑟发抖)。近段时间在恶补语言基础知识,索性换换脑,将这部分的知识进行一个相对完整的整理。希望对那些想快速入手点云深度学习,用来做课设、毕设的小朋友一点点帮助。以下都是个人的所学所思所想,不一定正确,仅做参考。也欢迎各路大神指出问题,将虚心改进。
一、组织架构
为了遵循博客简短干货原则,也为了更细致的描述及解决问题,我将该问题分成了3部分,分别如下:
二、三维点云数据的采集
随着科技的迅速发展,目前获得物体三维点云的方法也越来越多。一种是通过硬件设备(深度传感器,结构光等)直接得到物体的三维信息,另一种是通过软件算法进行图像间匹配间接计算出物体的三维信息。常见的方法手段,如图所示:
这里不再对其中的内容进行展开说明。无论那一种方法,最终目的都是为了获得植物的三维点云数据。三、三维点云数据上标签
深度学习中,最繁杂且缺少技术含量的事情应该就是为采集的数据上标签。工欲善其事必先利其器,好的标注方法势必会让我们的工作变得更加高效。这里就介绍一种较为方便的点云标注工具。
代码的开源地址:
安装和使用教程:上述博客已经介绍的十分清楚了,可以放心食用。
手动标记保存之后,得到的pcd文件前后如下图所示:
值得注意的是,该软件的一个缺陷就是在进行标记及保存之后的点云文件是不带颜色信息的(该工具原始目标任务是针对室外激光雷达点云的),颜色信息只能通过后处理手段加上去。此外,如果使用PCL自带的XYZL点云类型进行显示可视化需要注意:保存得到的pcd文件数据类型为(TYPE F F F I I)见上图有右侧所示。我在实际读取过程中就发现会报错,解决办法是将(TYPE F F F I I 批量替换成 TYPE F F F U U).import osfrom os import listdir, pathpath_str = 'E:\\标注原始文件' # your directory pathtxts = [f for f in listdir(path_str) if f.endswith('.pcd') and path.isfile(path.join(path_str, f))]for txt in txts: with open(os.path.join(path_str, txt), 'r') as f: lines = f.readlines() lines[3] = lines[3].replace('I','U') with open(os.path.join(path_str,os.path.splitext(txt)[0]+".pcd"), 'w') as f: f.write(''.join(lines[0:]))
四、点云深度学习数据集处理流程
终于熬到了最后,接下来就介绍如何将标记好的点云数据集,生成网络的输入训练数据(或如何融合到现有的数据集中进行训练)
1、PCD文件生成TXT文件
这一步主要是将PCD文件的头信息进行去掉,得到只含有点坐标,颜色和标签的数据文件。import osfrom os import listdir, pathpath_str = 'E:\\标注原始文件' # your directory pathtxts = [f for f in listdir(path_str) if f.endswith('.txt') and path.isfile(path.join(path_str, f))]for txt in txts: with open(os.path.join(path_str, txt), 'r') as f: lines = f.readlines() with open(os.path.join(path_str,os.path.splitext(txt)[0]+".txt"), 'w') as f: f.write(''.join(lines[11:]))
2、中心化和尺度缩放
这一步的主要目的是将点云中心移动到坐标原点,并将所有点的坐标的绝对值限制在1以内(对于小型数据集仿照的ShapeNet数据格式)# 输入的data数据为点云的坐标信息和标签信息(或者额外的颜色信息与法向量信息)def change_scale(data): #centre xyz_min = np.min(data[:,0:3],axis=0) xyz_max = np.max(data[:,0:3],axis=0) xyz_move = xyz_min+(xyz_max-xyz_min)/2 data[:,0:3] = data[:,0:3]-xyz_move #scale scale = np.max(data[:,0:3]) data[:,0:3] = data[:,0:3]/scale return data
3、点云的降采样
这一步的主要目的是将每个点云文件点进行规范化到固定的点数,本文中我们采用随机采样的方式。当然如果有好的想法,采样的方式是可以按照需求进行改进的。def sample_data(data, num_sample): """ data is in N x ... we want to keep num_samplexC of them. if N > num_sample, we will randomly keep num_sample of them. if N < num_sample, we will randomly duplicate samples. """ N = data.shape[0] if (N == num_sample): return data, range(N) elif (N > num_sample): sample = np.random.choice(N, num_sample) return data[sample, ...], sample else: sample = np.random.choice(N, num_sample-N) dup_data = data[sample, ...] return np.concatenate([data, dup_data], 0), list(range(N))+list(sample)
4、划分训练集与测试集并保存为.H5文件
这一步就是将处理好的数据划分为训练集和测试集,并将其保存为.H5文件。# train and test number, save datadef save_data(data, ratio): """ data is in N x 4 (XYZL) ... ratio of training set to test set if ratio = 0.8 train data is 80%, test data is 20% """ if not os.path.exists('train.h5'): with h5py.File('train.h5') as f: f['data'] = data[0:int(data.shape[0]*ratio),0:3] f['labels'] = data[0:int(data.shape[0]*ratio),4] if not os.path.exists('test.h5'): with h5py.File('test.h5') as f: f['data'] = data[int(data.shape[0]*ratio):,0:3] f['labels'] = data[int(data.shape[0]*ratio):,4]
到此就完成。当然上面的代码只是方法的演示,针对不同数据需要进行不同的调整。最后给一个相对完整的代码,同样是仅供参考:
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Created on Wed Sep 25 21:53:03 2019@author: xxx"""import osimport sysimport numpy as npimport h5pyBASE_DIR = os.path.dirname(os.path.abspath(__file__))sys.path.append(BASE_DIR)def getDataFiles(list_filename): return [line.rstrip() for line in open(list_filename)]def loadDataFile(path): data = np.loadtxt(path) point_xyz = data[:,0:3] # 下面的ins_label, sem_label和obj_lable # 需要根据自己的实际进行赋值(以下只是我的一个示例,就不再解释) ins_label = (data[:,3]).astype(int) find_index = np.where(ins_label>=1) sem_label = np.zeros((data.shape[0]), dtype=int) obj_lable = data[:,4] sem_label[find_index] = obj_lable[find_index]+1 return point_xyz, ins_label, sem_label, obj_labledef change_scale(data): #centre xyz_min = np.min(data[:,0:3],axis=0) xyz_max = np.max(data[:,0:3],axis=0) xyz_move = xyz_min+(xyz_max-xyz_min)/2 data[:,0:3] = data[:,0:3]-xyz_move #scale scale = np.max(data[:,0:3])# change_data[:,0:3] = data[:,0:3]/scale# return data[:,0:3]/scale return data[:,0:3]def sample_data(data, num_sample): """ data is in N x ... we want to keep num_samplexC of them. if N > num_sample, we will randomly keep num_sample of them. if N < num_sample, we will randomly duplicate samples. """ N = data.shape[0] if (N == num_sample): return data, range(N) elif (N > num_sample): sample = np.random.choice(N, num_sample) return data[sample, ...], sample else: sample = np.random.choice(N, num_sample-N) dup_data = data[sample, ...] return np.concatenate([data, dup_data], 0), list(range(N))+list(sample) if __name__ == "__main__": DATA_FILES =getDataFiles(os.path.join(BASE_DIR, 'file_path.txt')) num_sample = 4096*2 DATA_ALL = [] for fn in range(len(DATA_FILES)): print(DATA_FILES[fn]) current_data, current_ins_label, current_sem_label,current_obj_lable = loadDataFile(DATA_FILES[fn]) change_data = change_scale(current_data)# data_sample,index = sample_data(change_data, num_sample) data_label = np.column_stack((change_data,current_ins_label,current_sem_label,current_obj_lable)) DATA_ALL.append(data_label) output = np.vstack(DATA_ALL) output = output.reshape(-1,num_sample,6) # 这里没将训练测试集单独分开 if not os.path.exists('4096_4096_no_scale.h5'): with h5py.File('4096_4096_no_scale.h5') as f:# sample = np.random.choice(8192, 2048) f['data'] = output[:,:,0:3] f['pid'] = output[:,:,3] f['seglabel'] = output[:,:,4] f['obj'] = output[:,:,5]
写在后面的话:到此为止这个主题的三个部分就全部写完了。总体来说还是从思想上进行分析,不是那种拿起代码就能使用的状态。中间也间接穿插了点自己在阅读这方面论文和科研工作中的想法,值得去讨论分析下,多人利益相关也无法说的太明白,但我本人还是非常乐意与各位大佬交流分享,共同进步。
转载地址:http://fpyci.baihongyu.com/