论文:http://arxiv.org/abs/1909.03613
数据集下载:https://v-sense.scss.tcd.ie/DublinCity/
数据集结构
下载下来后我们得到的是13个.bin
文件
T_315500_234500_NE.bin
T_315500_234500_NW.bin
T_315500_234500_SE.bin
T_315500_234500_SW.bin
T_316000_233500_NW.bin
T_316000_233500_SE.bin
T_316000_234000_NE.bin
T_316000_234000_NW.bin
T_316000_234000_SE.bin
T_316000_234000_SW.bin
T_315500_233500_NE_T_315500_234000_SE.bin
T_316000_233500_NE_T_316000_233500_SW.bin
T_316500_234000_SW_T_316500_233500_NW.bin
用 CloudCompare 打开任意一个文件,其点云结构如图所示:
一个文件包含4个大分类,其下还会有各种细分:
可以参照作者给出的点云分类拓扑图来看:
数据集中的坑
这个数据集里有很多坑,为了说明白我需要分几个章节。
1类别的坑
1.1 建筑立面包含房顶/天窗
按照作者论文中的说法,建筑首先分为立面和房顶:
我们先不看窗户/门之类的,只看立面和房顶。如下图,我红框标注的是房顶所在文件拓扑位置和房顶的大致点云位置(蓝色点云),绿框标注的是立面所在文件拓扑位置和立面的大致点云位置(紫色点云)。可以看出,所谓的"立面"不止包含了立面,而是包含了除了门窗房顶的所有其他内容。
进一步查看其他房屋相关点云,我们发现部分(也许是大部分)房屋的“立面”甚至包含了屋顶和天窗,如下图,屋顶/天窗类别(蓝色/青色)虽然被切分了,但是仍有一份保留在了“立面”(紫色)类别中。
这个问题并非个例,从全局俯视图中可以看出,紫色的是立面包含房顶的房屋,蓝色是立面与房顶分离的房屋。
1.2 树和灌木不分
按照作者论文中的说法,植物分为树和灌木:
在数据集中,分类方式百花齐放:
a.正常的
b.树和灌木分同一类的
c.不想分类灌木的
d.树和底下的灌木挨一起的
e.树的倒影也算树的
2 数据的坑
我们通过 CloudCompare 将每个.bin
文件的所有点云合并后保存成.ply
格式,再进行后续处理。
使用python读取.ply文件为ndarray格式:
array([(315971.48901367, 234185.87695312, 9.77700043, 170, 0, 255, 14., -28., 0., 1., 2., 2., 392735.9 , 45., 2., nan),
(315970.75 , 234185.17285156, 4.39499998, 170, 0, 255, 14., -27., 0., 1., 4., 4., 392735.9 , 11., 4., nan),
(315971.52404785, 234185.72705078, 9.74199963, 170, 0, 255, 14., -28., 0., 1., 1., 1., 392735.88, 54., 2., nan),
...,
(315945.21496582, 234174.02294922, 4.25099993, 255, 255, 127, 35., -13., 0., 1., 1., 1., 403171.97, 39., 4., nan),
(315944.9510498 , 234173.76220703, 4.18499994, 255, 255, 127, 35., -13., 0., 1., 1., 1., 403171.94, 79., 4., nan),
(315934.50097656, 234184.57519531, -86.21900177, 255, 255, 127, 32., 3., 0., 1., 2., 2., 402608.75, 20., 4., nan)],
dtype=[('x', '<f8'), ('y', '<f8'), ('z', '<f8'), ('red', 'u1'), ('green', 'u1'), ('blue', 'u1'), ('scalar_PointSourceId', '<f4'), ('scalar_ScanAngleRank', '<f4'), ('scalar_EdgeOfFlightLine', '<f4'), ('scalar_ScanDirectionFlag', '<f4'), ('scalar_NumberOfReturns', '<f4'), ('scalar_ReturnNumber', '<f4'), ('scalar_GpsTime', '<f4'), ('scalar_Intensity', '<f4'), ('scalar_Classification', '<f4'), ('scalar_Original_cloud_index', '<f4')])
XYZ
为点云坐标
RGB
可一一映射为语义类别
labels_rgb = np.vstack((data['red'], data['green'], data['blue'])).T
labels = np.zeros_like(labels_rgb[:, 0]) - 1
labels[np.all(labels_rgb == [255, 255, 127], 1)] = 0 # 未定义
labels[np.all(labels_rgb == [0, 170, 0], 1)] = 1 # 树木
labels[np.all(labels_rgb == [170, 255, 127], 1)] = 2 # 草地
labels[np.all(labels_rgb == [255, 170, 0], 1)] = 3 # 人行道
labels[np.all(labels_rgb == [170, 0, 0], 1)] = 4 # 道路
labels[np.all(labels_rgb == [170, 0, 255], 1)] = 5 # 立面
labels[np.all(labels_rgb == [0, 85, 255], 1)] = 5 # 房顶
labels[np.all(labels_rgb == [0, 255, 255], 1)] = 5 # 窗户
labels[np.all(labels_rgb == [255, 0, 255], 1)] = 5 # 门
assert labels.max() != 255
Intensity
为反射强度
Classification
并不是代表语义类别,而是数据来源类别,分为顶视视图和倾斜图像,论文原文如下。并且在数据中,顶视图像获取的点云标记为2,倾斜点云标记为4。
2.1 数据来源类别错误
读取.ply文件,检查每个区块点云的类别分布:
data_path = ''
files=[os.path.join(data_path, i) for i in os.listdir(data_path) if '.ply' in i]
for f in files:
data = read_ply(f)
print(f)
print(set(data['scalar_Classification']))
T_316000_234000_NW.ply
{2.0, 4.0}
T_315500_233500_NE_T_315500_234000_SE.ply
{2.0, 4.0}
T_315500_234500_SE.ply
{2.0, 4.0}
T_316000_234000_SW.ply
{2.0, 4.0}
T_316000_234000_NE.ply
{2.0, 4.0}
T_316000_233500_SE.ply
{2.0, 4.0}
T_315500_234500_NW.ply
{2.0, 4.0}
T_316000_234000_SE.ply
{2.0, 4.0}
T_316000_233500_NW.ply
{2.0, 4.0}
T_316000_233500_NE_T_316000_233500_SW.ply
{2.0, 4.0}
T_316500_234000_SW_T_316500_233500_NW.ply
{2.0, 4.0}
T_315500_234500_NE.ply
{2.0, 4.0, 1.0306705e-32}
T_315500_234500_SW.ply
{2.0, 4.0}
大部分区块都老老实实按照2(顶视)
,4(倾斜)
的方式来标记,但是T_315500_234500_NE
却有3个点是1.0306705e-32
。
移除这3个点:
data = read_ply(files[-2])
new = data[data['scalar_Classification'] > 1]
write_ply(files[-2],
[new['x'],new['y'],new['z'],new['red'],new['green'],new['blue'],new['scalar_Intensity'],new['scalar_Classification']],
['x', 'y', 'z', 'red', 'green', 'blue','scalar_Intensity','scalar_Classification'])
2.2 反射强度错误
检查每个区块点云的强度分布:
data_path = ''
files=[os.path.join(data_path, i) for i in os.listdir(data_path) if '.ply' in i]
for f in files:
data = read_ply(f)
print(f)
print(data['scalar_Intensity'].min(), data['scalar_Intensity'].max())
T_316000_234000_NW.ply
1.0 65534.0
T_315500_233500_NE_T_315500_234000_SE.ply
0.0 527830800000.0
T_315500_234500_SE.ply
1.0 65534.0
T_316000_234000_SW.ply
0.0 65534.0
T_316000_234000_NE.ply
1.0 65533.0
T_316000_233500_SE.ply
0.0 65534.0
T_315500_234500_NW.ply
0.0 65531.0
T_316000_234000_SE.ply
0.0 65534.0
T_316000_233500_NW.ply
0.0 65534.0
T_316000_233500_NE_T_316000_233500_SW.ply
0.0 65533.0
T_316500_234000_SW_T_316500_233500_NW.ply
1.0 65534.0
T_315500_234500_NE.ply
-2.875652e-18 65534.0
T_315500_234500_SW.ply
1.0 65530.0
该数据集强度分布为0~65534
,除了T_315500_233500_NE_T_315500_234000_SE
中有3个点反射率特别高(>1e+10)
。
移除这3个点:
data = read_ply(files[1])
new = data[data['scalar_Intensity'] < 65536]
write_ply(files[1],
[new['x'],new['y'],new['z'],new['red'],new['green'],new['blue'],new['scalar_Intensity'],new['scalar_Classification']],
['x', 'y', 'z', 'red', 'green', 'blue','scalar_Intensity','scalar_Classification'])
3 分类的坑
从上述2个区块的边界可以看出,右区块将车辆、围栏等标记为未分类(蓝色)
左区块直接将其划分为地面(橙色)
右区块将路边草坛标记为草地(亮绿色)
,左区块仍然将其标记为地面(橙色)
上图中上区块将地面认为是道路(street,橙色)
,下区块认为该地面是小路(sidewalk,黄色)
这种问题广泛存在于每2个区块之间