
本文介绍如何利用布尔索引与位运算思想,仅用一次向量化计算即可为任意数量的 3d 点分配唯一卦限索引(0–7),避免冗余条件判断,兼顾性能、可读性与高维可扩展性。
本文介绍如何利用布尔索引与位运算思想,仅用一次向量化计算即可为任意数量的 3d 点分配唯一卦限索引(0–7),避免冗余条件判断,兼顾性能、可读性与高维可扩展性。
在三维欧几里得空间中,所有非零点可根据其坐标 $x, y, z$ 的正负号被自然划分为 8 个卦限(octants),每个卦限对应一个唯一的符号三元组 $(\text{sgn}(x), \text{sgn}(y), \text{sgn}(z))$,其中 $\text{sgn}(t) = +1$ 当 $t \geq 0$,$\text{sgn}(t) = -1$ 当 $t
更优解是将每个卦限映射为一个 0 到 7 的整数索引,类比二进制编码:
- $x$ 轴符号 → 第 0 位(权重 $2^0 = 1$)
- $y$ 轴符号 → 第 1 位(权重 $2^1 = 2$)
- $z$ 轴符号 → 第 2 位(权重 $2^2 = 4$)
于是,索引可统一表示为:
$$
\text{idx} = \mathbb{I}(x \geq 0) + 2 \cdot \mathbb{I}(y \geq 0) + 4 \cdot \mathbb{I}(z \geq 0)
$$
其中 $\mathbb{I}(\cdot)$ 是指示函数(NumPy 中 arr >= 0 返回布尔数组,参与算术运算时自动转为 0/1)。
该公式完全向量化,仅需 3 次布尔比较 + 2 次标量乘法 + 2 次加法,全程无 Python 循环,性能远超逐点 for 版本(尤其对大规模点云)。
以下是推荐的高效实现:
import numpy as np
def octant_partition(points):
"""
将 (N, 3) 形状的 3D 点阵列按符号划分为 8 个卦限。
Parameters
----------
points : np.ndarray, shape (N, 3)
输入点集,列顺序为 [x, y, z]
Returns
-------
list of np.ndarray
长度为 8 的列表,第 i 个元素为属于第 i 卦限的点子集(形状 (Ni, 3)),
其中 i ∈ [0, 7] 对应二进制 (x≥0)(y≥0)(z≥0)。
"""
# 向量化生成卦限索引:shape (N,)
x_pos = (points[:, 0] >= 0).astype(np.int32)
y_pos = (points[:, 1] >= 0).astype(np.int32)
z_pos = (points[:, 2] >= 0).astype(np.int32)
octant_idx = x_pos + 2 * y_pos + 4 * z_pos # 值域 [0, 7]
# 使用 np.split 配合 argsort 实现分组(稳定且高效)
sort_idx = np.argsort(octant_idx)
sorted_points = points[sort_idx]
sorted_labels = octant_idx[sort_idx]
# 获取每个卦限的切片边界
boundaries = np.flatnonzero(np.diff(sorted_labels, prepend=-1)) # [0, i1, i2, ..., 8]
# 分割点集
partitions = np.split(sorted_points, boundaries[1:])
# 补齐空卦限(若某卦限无点,则返回空数组)
result = [np.empty((0, 3), dtype=points.dtype)] * 8
for i, part in enumerate(partitions):
if len(part) > 0:
result[sorted_labels[boundaries[i]]] = part
return result
# 示例用法
if __name__ == "__main__":
np.random.seed(42)
pts = np.random.uniform(-4, 4, (1000, 3))
parts = octant_partition(pts)
for i, p in enumerate(parts):
print(f"Octant {i} ({bin(i)[2:].zfill(3)}): {len(p)} points")
✅ 关键优势:
- 纯向量化:无显式循环,充分利用 NumPy 底层优化;
-
内存友好:
np.split+argsort避免多次布尔索引复制数据; - 健壮性:自动处理空卦限,返回统一形状的子数组;
- 可扩展:只需修改权重系数($2^0, 2^1, 2^2, \dots$)即可推广至 $d$ 维空间($2^d$ 个区域);
-
语义清晰:索引
i的二进制位直接对应各轴非负性,便于调试与验证。
⚠️ 注意事项:
- 本方案将
x >= 0视为正向(含零),若需严格区分零点(如将 $x=0$ 单独归类),需预处理:先用np.isclose(points, 0)标记零值,再按需求调整分类逻辑; - 若点集极大(千万级),可考虑使用
scipy.ndimage.map_coordinates或分块处理以控制内存峰值; - 返回的列表中各子数组不保证原始顺序(因经
argsort排序),如需保序,请改用布尔掩码索引(牺牲少量内存):
# 保序版本(简洁但略占内存) masks = [(points[:,0]>=0) == a for a in [0,1]] # ... 类似构造全部 8 个掩码,或用 itertools.product
综上,通过将几何划分转化为位索引问题,我们以极简、高效、可扩展的方式完成了 3D 点的卦限分类——这不仅是代码优化,更是用计算思维重构几何直觉的典型实践。










