File size: 8,362 Bytes
efaa3d7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
NYU Depth V2 (labeled) 数据集查看与导出工具。
nyu_depth_v2_labeled.mat 是 MATLAB v7.3 (HDF5) 格式,scipy.io.loadmat 无法读取,
这里统一用 h5py 按需读取,避免一次性载入 2.8GB。
数据内容(N = 1449,图像为竖版 640x480,H=640, W=480,RGB 与深度已对齐):
images [N, 3, 640, 480] uint8 RGB
depths [N, 640, 480] float32 已填补深度,单位米
rawDepths [N, 640, 480] float32 原始深度,0 表示缺失
labels [N, 640, 480] uint16 语义标签,0=未标注,1..894 对应 names
instances [N, 640, 480] uint8 实例标签,0=无实例
names [1, 894] cell 类别名(1-based 下标即 label id)
namesToIds containers.Map MATLAB Map,h5py 无法直接解析
scenes [1, N] cell 场景名,如 kitchen_0004
sceneTypes [1, N] cell 场景类型,如 kitchen
rawRgbFilenames [1, N] cell 原始 RGB 文件名
rawDepthFilenames [1, N] cell 原始深度文件名
accelData [4, N] float32 加速度计数据
用法(在 hs/DepthART 目录下执行):
# 打印结构、场景/类别统计和深度数值范围(默认抽样 step=50)
../../venv/bin/python src/tool/inspect_nyu_depth_v2.py --mat ../../datasets/nyu_depth_v2_labeled.mat
# 全量扫描统计(较慢)
... --mat ../../datasets/nyu_depth_v2_labeled.mat --full-stats
# 列出全部 894 个类别名
... --mat ... --list-classes
# 单样本导出:rgb.png / depth.npy / depth_vis.png / raw_depth.npy
... --mat ... --index 0 --out-dir results/nyu_demo --with-labels
# 批量导出,例如某场景全部帧
... --mat ... --export-dir results/nyu_export --scene kitchen_0004 --with-labels
# 批量导出前 50 张
... --mat ... --export-dir results/nyu_export --count 50
"""
import argparse
import collections
from pathlib import Path
import cv2
import h5py
import numpy as np
from tqdm import tqdm
# 深度可视化统一范围(米),超出部分截断
DEPTH_VIS_RANGE = (0.5, 10.0)
def decode_cell(f, ref):
"""把 MATLAB cell 中的 char 数组解码为 str。"""
return ''.join(chr(c) for c in np.asarray(f[ref]).ravel())
def read_cell_list(dset):
return [decode_cell(dset.file, dset[0, i]) for i in range(dset.shape[1])]
def print_info(f, stride, full_stats):
print('== 变量 ==')
for k in f.keys():
if k.startswith('#'):
continue
o = f[k]
line = f'{k:20s} shape={str(o.shape):24s} dtype={o.dtype}'
if o.compression:
line += f' compression={o.compression}'
print(line)
n = f['depths'].shape[0]
h, w = f['depths'].shape[1:]
names = read_cell_list(f['names'])
scenes = read_cell_list(f['scenes'])
scene_types = read_cell_list(f['sceneTypes'])
print('\n== 规模 ==')
print(f'样本数 {n}')
print(f'分辨率 W={w}, H={h}(data layout 为 [N, C, H, W])')
print(f'场景数 {len(set(scenes))}')
print(f'场景类型数 {len(set(scene_types))}')
print(f'语义类别数 {len(names)}(label id 1..{len(names)},0 为未标注)')
print('场景类型分布 ' + ', '.join(f'{k}:{v}' for k, v in
collections.Counter(scene_types).most_common()))
idx = range(0, n) if full_stats else range(0, n, stride)
idx = list(idx)
print(f'\n== 数值统计({"全量" if full_stats else f"抽样 {len(idx)} 张, step={stride}"}) ==')
d_min, d_max = np.inf, -np.inf
raw_missing = 0
raw_total = 0
cls_count = collections.Counter()
inst_max = 0
for i in tqdm(idx, desc='scan', ncols=80):
d = f['depths'][i]
r = f['rawDepths'][i]
d_min = min(d_min, float(d.min()))
d_max = max(d_max, float(d.max()))
raw_missing += int((r == 0).sum())
raw_total += r.size
for c in np.unique(f['labels'][i]):
if c > 0:
cls_count[int(c)] += 1
inst_max = max(inst_max, int(f['instances'][i].max()))
print(f'depths 范围 [{d_min:.4f}, {d_max:.4f}] m')
print(f'rawDepths 缺失 {raw_missing / raw_total * 100:.2f}% (值为 0)')
print(f'出现类别数 {len(cls_count)} / {len(names)}')
print(f'instances 最大 {inst_max}')
print('\n最常见类别 ' + ', '.join(
f'{names[c - 1]}({cnt})' for c, cnt in cls_count.most_common(15)))
def depth_to_vis(depth, raw=False):
lo, hi = DEPTH_VIS_RANGE
norm = np.clip((depth - lo) / (hi - lo), 0.0, 1.0)
vis = cv2.applyColorMap((norm * 255).astype(np.uint8), cv2.COLORMAP_TURBO)
if raw:
vis[depth <= 0] = 0
return vis
def export_sample(f, index, out_dir, with_labels):
entry = out_dir / f'{index:05d}_{decode_cell(f, f["scenes"][0, index])}'
entry.mkdir(parents=True, exist_ok=True)
rgb = np.asarray(f['images'][index]).transpose(1, 2, 0) # CHW -> HWC, RGB
depth = np.asarray(f['depths'][index], dtype=np.float32)
raw = np.asarray(f['rawDepths'][index], dtype=np.float32)
cv2.imwrite(str(entry / 'rgb.png'), cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR))
cv2.imwrite(str(entry / 'depth_vis.png'), depth_to_vis(depth))
cv2.imwrite(str(entry / 'raw_depth_vis.png'), depth_to_vis(raw, raw=True))
np.save(entry / 'depth.npy', depth)
np.save(entry / 'raw_depth.npy', raw)
if with_labels:
labels = np.asarray(f['labels'][index])
np.save(entry / 'labels.npy', labels)
# 语义标签着色:按 label id 的哈希色,0 保持黑色
rng = np.random.default_rng(0)
lut = rng.integers(40, 255, size=(65536, 3), dtype=np.uint8)
lut[0] = 0
cv2.imwrite(str(entry / 'labels_vis.png'), lut[labels])
return entry
def main():
parser = argparse.ArgumentParser(description='查看/导出 NYU Depth V2 labeled 数据集')
parser.add_argument('--mat', required=True, help='nyu_depth_v2_labeled.mat 路径')
parser.add_argument('--list-classes', action='store_true', help='打印全部类别名及 id')
parser.add_argument('--index', type=int, help='导出单个样本的下标')
parser.add_argument('--out-dir', help='--index 模式的输出目录')
parser.add_argument('--export-dir', help='批量导出目录')
parser.add_argument('--count', type=int, help='批量导出数量,默认全部')
parser.add_argument('--step', type=int, default=1, help='批量导出间隔,默认 1')
parser.add_argument('--scene', help='只导出指定场景,如 kitchen_0004')
parser.add_argument('--with-labels', action='store_true', help='同时导出语义标签')
parser.add_argument('--full-stats', action='store_true', help='统计时扫描全部样本')
parser.add_argument('--stats-stride', type=int, default=50, help='抽样统计间隔,默认 50')
args = parser.parse_args()
with h5py.File(args.mat, 'r') as f:
if args.list_classes:
names = read_cell_list(f['names'])
for i, name in enumerate(names, 1):
print(f'{i:4d} {name}')
return
if args.index is not None:
out_dir = Path(args.out_dir or '.')
entry = export_sample(f, args.index, out_dir, args.with_labels)
print(f'已导出 {entry}')
return
if args.export_dir:
n = f['depths'].shape[0]
indices = list(range(0, n, args.step))
if args.scene:
scenes = read_cell_list(f['scenes'])
indices = [i for i in indices if scenes[i] == args.scene]
print(f'场景 {args.scene} 命中 {len(indices)} 张')
if args.count:
indices = indices[:args.count]
out_dir = Path(args.export_dir)
for i in tqdm(indices, desc='export', ncols=80):
export_sample(f, i, out_dir, args.with_labels)
print(f'已导出 {len(indices)} 张到 {out_dir}')
return
print_info(f, args.stats_stride, args.full_stats)
if __name__ == '__main__':
main()
|