ScanNet 数据集是一个大规模的 RGB-D 视频数据集,它包含了丰富的三维场景理解任务所需的数据,如三维物体分类、语义体素标签和 CAD 模型检索等。数据集中的每个文件都有特定的作用,以下是一些常见文件及其作用的解释:
-
_vh_clean.ply
和_vh_clean_2.ply
:- 这些文件包含高质量的重建表面网格数据。
_vh_clean.ply
是原始的高分辨率网格,而_vh_clean_2.ply
是经过清理和简化的版本,通常用于语义注释。
- 这些文件包含高质量的重建表面网格数据。
-
.aggregation.json
:- 这个文件包含场景的实例级语义注释。它以 JSON 格式存储,提供了关于不同实例的详细信息,如实例 ID、对象 ID 和所属的原始类别标签。
-
_vh_clean_2.labels.ply
:- 这个文件提供了点云中每个点的 NYU40 语义标签。这些标签可以用来进行语义分割任务。
-
_vh_clean_2.0.010000.segs.json
:- 这是一个分割文件,包含了点云数据中每个点的分割索引。这些索引与
.aggregation.json
文件中的数据匹配,可以用于划分每个点的标签。
- 这是一个分割文件,包含了点云数据中每个点的分割索引。这些索引与
-
.sens
:- 这些文件是 RGB-D 传感器流,包含压缩的二进制格式数据,如每帧的颜色、深度、相机姿势等。
-
_2d-label.zip
和_2d-instance.zip
:- 这些文件包含原始的二维投影标注信息,如深度图、实例图、标签图和位置信息。
_2d-label.zip
包含带有 ScanNet 标签 ID 的 16 位 PNG 文件,而_2d-instance.zip
包含原始的 8 位 PNG 实例标注信息。
- 这些文件包含原始的二维投影标注信息,如深度图、实例图、标签图和位置信息。
-
_2d-label-filt.zip
和_2d-instance-filt.zip
:- 这些文件包含经过过滤的二维投影标注信息,与未过滤的版本类似,但经过了某种形式的清洗或选择,以提高数据质量。
-
scannetv2-labels.combined.tsv
:- 这是一个标签映射文件,提供了原始类别标签到其他标签系统的对应关系,对于数据的理解和使用非常重要。
-
.txt
文件(如scene0000_00.txt
):- 某些 ScanNet 数据集场景中可能包含的文本文件,可能包含场景的元数据或与场景相关的其他信息。
下面进行详细的解释
_vh_clean_2.ply
这些文件包含高质量的重建表面网格数据。_vh_clean.ply 是原始的高分辨率网格,而 _vh_clean_2.ply 是经过清理和简化的版本
加载场景点云
with open(
os.path.join(scan_dir, scan_id, "%s_vh_clean_2.ply" % (scan_id)), "rb"
) as f:
plydata = PlyData.read(f) # elements: vertex, face
points = np.array(
[list(x) for x in plydata.elements[0]]
) # [[x, y, z, r, g, b, alpha]]
coords = np.ascontiguousarray(points[:, :3])
colors = np.ascontiguousarray(points[:, 3:6])
scene0000_00.txt
加载全局的旋转变换矩阵
axisAlignment = 0.945519 0.325568 0.000000 -5.384390 -0.325568 0.945519 0.000000 -2.871780 0.000000 0.000000 1.000000 -0.064350 0.000000 0.000000 0.000000 1.000000
colorHeight = 968
colorToDepthExtrinsics = 0.999973 0.006791 0.002776 -0.037886 -0.006767 0.999942 -0.008366 -0.003410 -0.002833 0.008347 0.999961 -0.021924 -0.000000 0.000000 -0.000000 1.000000
colorWidth = 1296
depthHeight = 480
depthWidth = 640
fx_color = 1170.187988
fx_depth = 571.623718
fy_color = 1170.187988
fy_depth = 571.623718
mx_color = 647.750000
mx_depth = 319.500000
my_color = 483.750000
my_depth = 239.500000
numColorFrames = 5578
numDepthFrames = 5578
numIMUmeasurements = 11834
sceneType = Apartment
读取方式
if apply_global_alignment:
# 如果启用全局对齐,代码将读取一个文本文件以获取对齐矩阵,并应用此矩阵变换点云中的每个点。
align_matrix = np.eye(4)
with open(os.path.join(scan_dir, scan_id, "%s.txt" % (scan_id)), "r") as f:
for line in f:
if line.startswith("axisAlignment"):
align_matrix = (
np.array([float(x) for x in line.strip().split()[-16:]])
.astype(np.float32)
.reshape(4, 4)
)
break
# Transform the points
pts = np.ones((coords.shape[0], 4), dtype=coords.dtype)
pts[:, 0:3] = coords
coords = np.dot(pts, align_matrix.transpose())[:, :3] # Nx4
# Make sure no nans are introduced after conversion
assert np.sum(np.isnan(coords)) == 0
这段代码的目的是将一个全局的刚体变换(如旋转和平移)应用到点云数据上。在三维图形和计算机视觉中,这种变换通常用于将不同视角的点云数据对齐到一个统一的坐标系中,或者将点云数据与某个参考框架对齐。通过使用齐次坐标,可以在一个简单的矩阵乘法操作中同时应用旋转和平移。
_vh_clean_2.labels.ply
这个文件提供了点云中每个点的 NYU40 语义标签。这些标签可以用来进行语义分割任务
加载 semantic label的示例代码
with open(
os.path.join(scan_dir, scan_id, "%s_vh_clean_2.labels.ply" % (scan_id)),
"rb",
) as f:
plydata = PlyData.read(f)
sem_labels = np.array(plydata.elements[0]["label"]).astype(np.long)
assert len(coords) == len(colors) == len(sem_labels)
# >>> sem_labels
# array([16, 16, 16, ..., 1, 1, 1])
.segs.json
里面主要包含的是 segIndices,是一个数组,其中的每个元素对应点云中的一个点,并给出该点所属的分割的索引,如果 segIndices 中有三个连续的元素都是 5753,这表示在点云数据中,接下来的三个点都属于索引为 5753 的分割。
'sceneId': 'scene0000_00'
'segIndices': [5753, 5753, 5753, 5753, ...]
len(): 3
文件读取
# Map each point to segment id
with open(
os.path.join(
scan_dir, scan_id, "%s_vh_clean_2.0.010000.segs.json" % (scan_id)
),
"r",
) as f:
d = json.load(f)
seg = d["segIndices"]
segid_to_pointid = {}
for i, segid in enumerate(seg):
# 如果已经存在这个 key,就不改变 item,否则会新建 item
segid_to_pointid.setdefault(segid, [])
segid_to_pointid[segid].append(i)
.aggregation.json
.aggregation.json
文件是 ScanNet 数据集中用于存储实例级语义注释的 JSON 文件。这个文件为每个扫描场景中的每个实例提供了详细的标注信息,这些信息可以用来进行实例分割、对象识别和其他三维场景理解任务。
.aggregation.json
文件的结构大致如下:
{
"sceneId": "scannet.scene0000_00",
"appId": "Aggregator.v2",
"segGroups": [
{
"id": 0,
"objectId": 0,
"segments": [43652, 43832, 43632, 53294, 44062, 44013, 44158, 53070, 53173, 53253],
"label": "window"
},
// 可能还有更多的 segGroups
],
"segmentsFile": "scannet.scene0000_00_vh_clean_2.0.010000.segs.json"
}
-
sceneId: 这是一个字符串,唯一标识一个扫描场景。
-
appId: 表示用于创建注释的工具的版本。
-
segGroups: 这是一个对象数组,每个对象代表场景中的一个标注组。每个 “segGroup” 包含以下字段:
- id: 分配给该组的唯一的标识符。
- objectId: 与 “id” 相同,通常用于区分不同的实例。
-
segments: 一个整数数组,包含属于该实例的分割索引。这些索引与
.segs.json
文件中的 “segIndices” 相匹配。 - label: 表示该实例的语义类别。通常是一个字符串,对应于一个预定义的类别标签集合中的一个。
-
segmentsFile: 这是一个字符串,指定了包含上述 “segments” 字段中提到的分割索引的文件。这通常指向与
.aggregation.json
文件相同目录下的.segs.json
文件。
在这个例子中,segGroups
数组中的每个对象都表示场景中的一个实例,并且每个实例都有一个与之关联的语义标签(如 “window”)。
这种注释方式允许研究人员识别和区分场景中的不同对象,即使这些对象属于相同的语义类别。例如,即使两个对象都是窗户,它们也可以通过不同的 “objectId” 来区分。
文件读取,得到每个 point 的 instance label
instance_class_labels = []
instance_segids = []
with open(
os.path.join(scan_dir, scan_id, "%s.aggregation.json" % (scan_id)), "r"
) as f:
d = json.load(f)
for i, x in enumerate(d["segGroups"]):
assert x["id"] == x["objectId"] == i
instance_class_labels.append(x["label"])
instance_segids.append(x["segments"]) # 每一个instance包含一些 segments
instance_labels = np.ones(sem_labels.shape[0], dtype=np.long) * -100
for i, segids in enumerate(instance_segids): # 遍历每一个 instance
pointids = []
for segid in segids: # ! 得到属于这个 seg 的 points
pointids += segid_to_pointid[segid]
if np.sum(instance_labels[pointids] != -100) > 0:
# scene0217_00 contains some overlapped instances
print(
scan_id, i, np.sum(instance_labels[pointids] != -100), len(pointids)
)
else:
instance_labels[pointids] = i
assert (
len(np.unique(sem_labels[pointids])) == 1
), "points of each instance should have the same label"
理解上面两个文件,就要理解 scannet 数据集里的三级关系: