你提供的这段代码是用于 语义场景补全(Semantic Scene Completion, SSC) 任务中的标签下采样函数,其核心目的是将高分辨率的体素标签(如 240×144×240)降采样为低分辨率体素网格(如 60×36×60,当 downscale=4 时),同时保留语义信息并合理处理空/遮挡区域。
✅ 函数完整保留(原始代码)
def _downsample_label(label, voxel_size=(240, 144, 240), downscale=4):
r"""downsample the labeled data,
code taken from https://github.com/waterljwant/SSC/blob/master/dataloaders/dataloader.py#L262
Shape:
label, (240, 144, 240)
label_downscale, if downsample==4, then (60, 36, 60)
"""
if downscale == 1:
return label
ds = downscale
small_size = (
voxel_size[0] // ds,
voxel_size[1] // ds,
voxel_size[2] // ds,
) # small size
label_downscale = np.zeros(small_size, dtype=np.uint8)
empty_t = 0.95 * ds * ds * ds # threshold
s01 = small_size[0] * small_size[1]
label_i = np.zeros((ds, ds, ds), dtype=np.int32)
for i in range(small_size[0] * small_size[1] * small_size[2]): ## 体素数量减少64倍
z = int(i / s01)
y = int((i - z * s01) / small_size[0])
x = int(i - z * s01 - y * small_size[0])
label_i[:, :, :] = label[
x * ds : (x + 1) * ds, y * ds : (y + 1) * ds, z * ds : (z + 1) * ds
]
label_bin = label_i.flatten()
zero_count_0 = np.array(np.where(label_bin == 0)).size
zero_count_255 = np.array(np.where(label_bin == 255)).size
zero_count = zero_count_0 + zero_count_255
if zero_count > empty_t: ## empty_t 表示空体素数量阈值,大于此值,则下采样后的体素为空
label_downscale[x, y, z] = 0 if zero_count_0 > zero_count_255 else 255 ## 0 表示空, 255 表示遮挡
else:
label_i_s = label_bin[
np.where(np.logical_and(label_bin > 0, label_bin < 255))
]
label_downscale[x, y, z] = np.argmax(np.bincount(label_i_s)) ## 选体素最多类别最为最终的类别,np.bincount(label_i_s)统计非负整数数组中每个值出现的次数,返回一个计数数组
return label_downscale ## 下采样downscale倍,体素数量减少为原来的1/downscale**3,体素分别率resolution提高为原来的downscale倍
🔍 功能解释与逐行分析
🧩 输入参数
| 参数 | 含义 |
|---|---|
label | 原始体素标签,形状为 (240, 144, 240),每个值是语义类别 ID(0=空, 1~254=物体, 255=忽略/遮挡) |
voxel_size | 默认 (240, 144, 240),表示原始体素空间尺寸 |
downscale | 下采样倍率,如 4 → 输出为 (60, 36, 60) |
✅ 主要步骤详解
1. 快速返回:无需下采样
if downscale == 1:
return label
- 如果不降采样,直接返回原标签
2. 定义输出尺寸
small_size = (voxel_size[0]//ds, voxel_size[1]//ds, voxel_size[2]//ds)
label_downscale = np.zeros(small_size, dtype=np.uint8)
- 输出体素尺寸缩小
ds=4倍 → 总体素数减少4³ = 64倍 - 使用
uint8节省内存(类别通常 < 255)
3. 设置“空”体素阈值
empty_t = 0.95 * ds * ds * ds # 例如 ds=4 → 0.95×64 ≈ 60.8
- 一个
4×4×4的体素块共64个体素 - 如果其中超过
60.8个是0或255,就认为这个块是“无效” - 保留
5%的非空体素容忍度
4. 循环每个输出体素(i 从 0 到 N-1)
for i in range(small_size[0] * small_size[1] * small_size[2]):
z = int(i / s01)
y = int((i - z * s01) / small_size[0])
x = int(i - z * s01 - y * small_size[0])
- 将线性索引
i映射为 3D 坐标(x, y, z),顺序为 x 优先(C-order) s01 = small_size[0] * small_size[1]是 XY 平面大小
👉 这种写法是为了避免使用
np.meshgrid,用纯数学方式遍历 3D 网格。
5. 提取对应输入体素块
label_i[:, :, :] = label[
x * ds : (x + 1) * ds,
y * ds : (y + 1) * ds,
z * ds : (z + 1) * ds
]
- 每个输出体素
(x,y,z)对应输入中一个ds×ds×ds的立方体块 - 例如
ds=4:对应4×4×4 = 64个体素
6. 展平并统计 0 和 255 的数量
label_bin = label_i.flatten()
zero_count_0 = np.where(label_bin == 0).size
zero_count_255 = np.where(label_bin == 255).size
zero_count = zero_count_0 + zero_count_255
0:代表“空”(free space)255:代表“被遮挡”或“忽略”(occluded / invalid)- 统计这两类总计是否超过
95%
7. 判断是否为空/遮挡区域
if zero_count > empty_t:
label_downscale[x, y, z] = 0 if zero_count_0 > zero_count_255 else 255
-
如果无效体素占比 > 95%,则:
- 若
0更多 → 标记为0(空) - 若
255更多 → 标记为255(遮挡)
- 若
✅ 目的:避免在稀疏区域引入噪声预测
8. 否则:取有效类别中出现最多的类
else:
label_i_s = label_bin[np.where(np.logical_and(label_bin > 0, label_bin < 255))]
label_downscale[x, y, z] = np.argmax(np.bincount(label_i_s))
- 过滤出
1~254的有效语义类别 - 使用
np.bincount()统计频次 np.argmax(...)返回出现最多的类别 ID
✅ 示例:
label_i_s = [10, 10, 10, 11, 11] → bincount → [0,0,...,3,2] → argmax=10
📈 输出说明
| 属性 | 值 |
|---|---|
| 输入 shape | (240, 144, 240) |
| 输出 shape | (60, 36, 60)(当 downscale=4) |
| 体素总数 | 减少 64 倍 |
| 分辨率 | 提升 4×(每个输出体素代表更大物理体积) |
🎯 应用范围与典型用途
✅ 1. Semantic Scene Completion (SSC)
- 同时完成体素存在性预测 + 语义分类
- 网络常在低分辨率
(60×36×60)上训练,需对真值标签降采样
✅ 2. Occupancy Prediction / Map Reconstruction
- 将 LiDAR 点云重建为体素网格后,进行多帧融合或压缩
- 下采样以减少计算量
✅ 3. 模型输出后处理
- 某些模型预测低分辨率输出,需将真值标签对齐到相同分辨率进行监督
✅ 4. 数据增强或预-processing
- 构造多尺度监督信号
- 实现粗到细的训练策略
⚠️ 注意事项与潜在问题
| 问题 | 建议 |
|---|---|
np.where(...).size 冗余 | 可替换为 (label_bin == 0).sum() 更高效 |
np.zeros((ds,ds,ds)) 可复用 | 可移到循环外避免重复初始化 |
np.bincount() 要求 int | 确保 label 是整型,不能是 float |
| 忽略类别不平衡 | 若小物体被淹没,可加权重机制 |
| 单线程循环慢 | 可向量化优化(但复杂) |
✅ 改进建议(性能优化版)
def _downsample_label_fast(label, voxel_size=(240, 144, 240), downscale=4):
if downscale == 1:
return label
ds = downscale
H, W, D = voxel_size
small_shape = (H // ds, W // ds, D // ds)
label_downscale = np.zeros(small_shape, dtype=np.uint8)
empty_th = int(0.95 * ds ** 3)
for x in range(small_shape[0]):
for y in range(small_shape[1]):
for z in range(small_shape[2]):
block = label[x*ds:(x+1)*ds, y*ds:(y+1)*ds, z*ds:(z+1)*ds]
flat = block.flatten()
num_0 = (flat == 0).sum()
num_255 = (flat == 255).sum()
num_empty = num_0 + num_255
if num_empty > empty_th:
label_downscale[x, y, z] = 0 if num_0 >= num_255 else 255
else:
valid = flat[(flat > 0) & (flat < 255)]
if len(valid) == 0:
label_downscale[x, y, z] = 0
else:
label_downscale[x, y, z] = np.argmax(np.bincount(valid))
return label_downscale
更清晰、更安全(避免
where().size)、显式循环。
✅ 总结
| 特性 | 说明 |
|---|---|
| 用途 | 将高分辨率体素标签降采样为低分辨率,用于 SSC 等任务 |
| 核心逻辑 | 每个输出体素取 ds×ds×ds 块中最多见的有效类别 |
| 特殊处理 | 若超过 95% 是空或遮挡,则继承其状态 |
| 输出 | (H/ds, W/ds, D/ds) 的语义标签 |
| 优点 | 简单有效、保留结构、避免噪声传播 |
| 缺点 | 循环慢、小物体易丢失 |
📌 一句话总结:
该函数通过 多数投票 + 空/遮挡优先判断 实现了语义体素标签的安全降采样,广泛应用于 3D 场景补全、BEV 分割、地图重建等任务中,是连接高分辨率真值与低分辨率模型输出的关键桥梁。

2499

被折叠的 条评论
为什么被折叠?



