Point Transformer V3(PTv3)
文章目录
- 背景
- 原理
- 点云序列化
- 空间填充曲线(Space-Filling Curves)
- 序列化编码(Serialized Encoding)
【【为了详细说明,整体内容会比较多,这篇会逐步完善】】
背景
原理
点云序列化
空间填充曲线(Space-Filling Curves)
- 作用:将高维空间(如3D点云)映射到1D序列,同时保留空间邻近性(即空间中相邻的点在序列中也尽量靠近)
- 两种基础曲线:
- Z-order曲线(图3a):计算简单,但局部性保持较弱。
- Hilbert曲线(图3b):局部性保持更强,计算稍复杂
- 改进变体:通过调整坐标轴遍历顺序生成新曲线(如先遍历y轴再x轴),命名为 Trans Z-order(图3c)和 Trans Hilbert(图3d)。这些变体可捕捉标准曲线忽略的空间关系
序列化编码(Serialized Encoding)
- 基本原理
- 示例
假设有一个包含 6个点 的小型点云(坐标已归一化到 [0,1]),批处理索引 b=0,网格大小 g=0.2,使用 Z-order曲线 进行序列化,点云原始数据点索引 坐标 (x, y, z) P0 (0.15, 0.15, 0.15) P1 (0.35, 0.25, 0.30) P2 (0.10, 0.40, 0.20) P3 (0.55, 0.60, 0.70) P4 (0.25, 0.10, 0.25) P5 (0.50, 0.55, 0.65)
步骤1: 离散化与编码计算
-
离散化:将坐标除以网格大小
g=0.2
后取整:⌊p/g⌋
结果:- P0:
(0,0,0)
- P1:
(1,1,1)
- P2:
(0,2,1)
- P3:
(2,3,3)
- P4:
(1,0,1)
- P5:
(2,2,3)
- P0:
-
Z-order逆映射
φ⁻¹
(3D→1D):
按 比特交错规则(x高位→y中位→z低位)计算编码:编码 = (x_bin << 2) | (y_bin << 1) | z_bin // 以2位二进制为例
- P0:
(0,0,0)
→00 00 00
→000 000
→ 0 - P1:
(1,1,1)
→01 01 01
→000 111
→ 7 - P2:
(0,2,1)
→00 10 01
→010 001
= 17 - P3:
(2,3,3)
→10 11 11
→111 011
→ 59 - P4:
(1,0,1)
→01 00 01
→000 101
→ 9 - P5:
(2,2,3)
→10 10 11
→111 001
→ 57
- P0:
-
批处理编码(64位整数):
假设分配 低6位 给序列编码(k=6
),则:
Encode = (b << 6) | φ⁻¹
- P0:
(0<<6) | 0 = 0
- P1:
0 | 7 = 7
- P2:
0 | 17 = 17
- P3:
0 | 59 = 59
- P4:
0 | 9 = 9
- P5:
0 | 57 = 57
- P0:
步骤2: 序列化排序
-
按编码值升序排序:
点索引 编码值 新序列顺序 P0 0 1 P1 7 2 P4 9 3 P2 17 4 P5 57 5 P3 59 6 -
映射关系记录(不移动实际坐标):
映射表 = [P0→1, P1→2, P4→3, P2→4, P5→5, P3→6]
步骤3: 空间邻近性分析
- 原始空间距离:
P0(0.15,0.15,0.15) 和 P2(0.35, 0.25, 0.30) 距离较近(≈0.15) - 序列中的位置:
P0(位置1) 和 P1(位置2) 在序列中相邻 ✅ - 对比 P0 和 P3:
- 空间距离远(≈0.98)
- 序列位置(1 和 6)不相邻 ✅
💡 说明:Z-order曲线将空间邻近的点(如P0/P1)映射到相近的编码值,使它们在序列中相邻。
**步骤4: 分块(Patch Grouping)
假设 每块大小=2:
- 块0:位置1-2 → 点 {P0, P1}
- 块1:位置3-4 → 点 {P4, P2}
- 块2:位置5-6 → 点 {P5, P3}
⚠️ 注意:P1(0.35,0.25,0.30) 和 P0(0.35, 0.25, 0.30) 空间距离较近(≈0.18),被分到同一块,验证局部性保持。
注意
P1: (1,1,1) → 111 → 7这一个,为什不是010101呢
- Z-order编码原理
在3D空间中,Z-order编码将点的整数坐标(x, y, z)
的二进制位按固定顺序交错,生成一个唯一整数。
交错规则:x的最高位 → y的最高位 → z的最高位 → x的次高位 → y的次高位 → ...
-
P1(1,1,1) 为何是
111
而不是010101
?-
坐标值范围决定比特位数:
在示例中,所有坐标值均 ∈ [0, 3](因网格大小g=0.2
,坐标范围[0,1]
离散化为{0,1,2,3}
),
因此只需 2比特 表示每个坐标(值范围0~3对应二进制00
,01
,10
,11
)。 -
但Z-order编码只需最小比特位:
对于点P1(1,1,1)
:x=1
→ 二进制1
(1比特)y=1
→ 二进制1
(1比特)z=1
→ 二进制1
(1比特)
由于所有坐标值均 ≤1,1比特足够表示,无需补零到2比特。
-
比特交错过程:
直接按x→y→z
顺序拼接:x位 = 1 y位 = 1 z位 = 1 最终编码 = (x y z)₂ = 111₂ = 7
- 对比:多比特情况(如P3)
对于P3(2,3,3)
:
x=2
→ 二进制10
(2比特)y=3
→ 二进制11
(2比特)z=3
→ 二进制11
(2比特)
比特交错步骤:
- 取所有坐标的最高位:
x[1]=1
,y[1]=1
,z[1]=1
→ 拼接为111
- 取次高位:
x[0]=0
,y[0]=1
,z[0]=1
→ 拼接为011
- 组合:
111
+011
=111011₂
= 59
✅ 此时编码是6位(
111011
),符合2比特坐标的交错规则。
- 为什么不是
010101
?
-
若强制用2比特表示
P1(1,1,1)
:x=01
y=01
z=01
按Z-order交错:x[1]→y[1]→z[1]→x[0]→y[0]→z[0]
=0→0→0→1→1→1
=000111₂ = 7
结果仍是7,因为前导零不影响整数值。
-
关键点:
Z-order编码本质是比特交错,与是否补前导零无关:(1,1,1)
用1比特 →111₂ = 7
(1,1,1)
用2比特 →000111₂ = 7
两种方式结果相同,因此无需显式补零。
结论
- 对于
P1(1,1,1)
,Z-order编码为111₂ = 7
是正确的。 - 在实现中,通常直接根据坐标值范围动态计算比特位数,无需手动补零。
010101
可能是误解了坐标的比特表示方式(误以为必须统一补足到固定位数)。
-
z-order的代码原理
# 参考 pointcept/models/utils/serialization/z_order.py
def xyz2key(self, x, y, z, depth):key = torch.zeros_like(x)for i in range(depth):mask = 1 << ikey = (key| ((x & mask) << (2 * i + 2))| ((y & mask) << (2 * i + 1))| ((z & mask) << (2 * i + 0)))return key
从代码中可以明显看出其原理:
- mask每次左移1位,用来取对应的bit位
- x,y,x每次分别左移2i + 2、2i + 1、2*i + 0表示取出来的bit位按xyz重排
- 循环和key按位取或,每次循环表示取出来1个bit位后的值
- 代码中先重排低位,再重排高位
关键结论
5. 高效性:排序操作复杂度仅 O(N log N)
,远低于KNN的 O(N²)
6. 局部性保持:
- 邻近点(如P0/P2、P1/P4)在序列中相邻
- 非邻近点(如P0/P3)在序列中远离
- 动态切换:通过修改映射表,可无缝切换至 Hilbert曲线 或其他变体(如Trans Z-order),无需重新计算点坐标。