【3D占用预测occ体素标签下采样】

该文章已生成可运行项目,

你提供的这段代码是用于 语义场景补全(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 个是 0255,就认为这个块是“无效”
  • 保留 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
分辨率提升 (每个输出体素代表更大物理体积)

🎯 应用范围与典型用途

✅ 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 分割、地图重建等任务中,是连接高分辨率真值与低分辨率模型输出的关键桥梁。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aixiao_xiaoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值