3D体素(Voxel)算法原理内容综述
1. 定义与起源
- 定义
-
Voxel = Volume Element(体积元素),是三维空间的最小离散单元。
-
类似于二维图像的像素(Pixel),Voxel 是三维空间的“像素块”。
-
每个体素可以用来表示:
- 是否被占据(Occupied / Free / Unknown)
- 存储点云数据或体积属性(颜色、法向量、TSDF)
- 历史背景
- 最早用于医学成像(CT、MRI 扫描),用于表示人体三维结构。
- 后来扩展到计算机图形学、点云处理、机器人 SLAM 和 3D 重建。
2. 数学建模
- 空间离散化
- 将连续三维空间划分为固定大小的立方体格子:
xworld=o+(i,j,k)⋅resolution \mathbf{x}_{world} = \mathbf{o} + (i,j,k) \cdot resolution xworld=o+(i,j,k)⋅resolution
- o\mathbf{o}o 为体素网格原点
- i,j,ki,j,ki,j,k 为整数体素索引
resolution
为体素边长
- 体素索引映射
- 坐标 → 索引:
i=⌊x−x0res⌋,j=⌊y−y0res⌋,k=⌊z−z0res⌋ i = \lfloor \frac{x - x_0}{res} \rfloor,\quad j = \lfloor \frac{y - y_0}{res} \rfloor,\quad k = \lfloor \frac{z - z_0}{res} \rfloor i=⌊resx−x0⌋,j=⌊resy−y0⌋,k=⌊resz−z0⌋
- 索引 → 坐标:
x=x0+i⋅res,y=y0+j⋅res,z=z0+k⋅res x = x_0 + i \cdot res, \quad y = y_0 + j \cdot res, \quad z = z_0 + k \cdot res x=x0+i⋅res,y=y0+j⋅res,z=z0+k⋅res
- 体素体积
Vvoxel=res3 V_{voxel} = res^3 Vvoxel=res3
- 分辨率越高,体素越小,表示精度越高,但内存消耗越大。
3. 体素的分类
-
根据占据状态:
- Occupied(占据):体素被点云或表面填充
- Free(自由):体素为空
- Unknown(未知):尚未观测
-
根据数据存储:
- 点集体素:每个体素存储落在其中的点集合
- 统计体素:存储点云统计量(均值、协方差、法向量等)
- TSDF体素:存储表面距离信息(Signed Distance Function + 权重)
-
根据分辨率:
- 均匀体素:整个空间体素大小一致
- 自适应体素:稀疏区域大体素,密集区域小体素(Octree)
4. 体素在不同场景的意义
-
点云处理
- 下采样 / VoxelGrid Filter → 降低点云密度,减少计算量
- 邻域搜索 → 降低最近邻搜索复杂度
-
SLAM / 三维地图
- 占据栅格(Occupancy Grid) → 路径规划、碰撞检测
- TSDF / ESDF → 高精度表面重建
-
计算机图形学
- 光线投射(Ray Casting) → 渲染、可视化
- 体绘制(Volume Rendering) → 烟雾、云、火焰效果
-
医学影像
- CT / MRI → 体素化表示人体组织结构
5. 优缺点分析
优点 | 缺点 |
---|---|
空间结构清晰,便于索引 | 高分辨率内存消耗大 |
支持快速邻域查询和体素化滤波 | 稀疏环境下存储浪费 |
易于与 TSDF/ESDF 等体积方法结合 | 体素分辨率固定可能限制精度 |
支持 GPU 并行处理 | 动态更新复杂 |
6. 体素表示方法
-
占据状态(Occupancy)
- 0 → Free, 1 → Occupied
- 可以用单个位或字节存储
-
点云体素
- 存储体素内的点集合
- 便于统计或下采样
-
TSDF体素
- 存储 Signed Distance 和权重
- 表示距离最近表面的位置
7. 示例(点坐标 → 体素索引)
#include <iostream>
#include <tuple>
#include <cmath>using VoxelIndex = std::tuple<int,int,int>;VoxelIndex pointToVoxel(float x, float y, float z, float res, float ox, float oy, float oz) {int i = std::floor((x - ox) / res);int j = std::floor((y - oy) / res);int k = std::floor((z - oz) / res);return {i,j,k};
}int main() {float res = 0.1f;float ox=0, oy=0, oz=0;auto idx = pointToVoxel(0.23, 0.45, 0.12, res, ox, oy, oz);std::cout << "Voxel index: (" << std::get<0>(idx) << "," << std::get<1>(idx) << "," << std::get<2>(idx) << ")" << std::endl;
}
二、体素网格数据结构
体素网格是空间离散化的核心手段,在点云处理、SLAM、三维重建、碰撞检测等任务中都非常重要。数据结构直接影响存储效率、搜索效率和算法复杂度。
1. 固定分辨率栅格(Dense Grid)
1.1 数据结构
- 三维数组形式:
voxel[x][y][z](0≤x<Nx,0≤y<Ny,0≤z<Nz) \text{voxel}[x][y][z] \quad (0 \le x < N_x, 0 \le y < N_y, 0 \le z < N_z) voxel[x][y][z](0≤x<Nx,0≤y<Ny,0≤z<Nz)
-
每个体素存储:
- 占据状态(Occupied / Free / Unknown)
- 点集合
- TSDF 值 / 权重等统计信息
1.2 优缺点
优点 | 缺点 |
---|---|
索引快速:O(1) | 内存占用大,尤其是稀疏环境 |
实现简单,易于理解和调试 | 不易扩展到大规模环境 |
1.3 使用场景
- 小规模室内 SLAM
- 点云滤波、体素化下采样
- GPU 并行处理(统一内存布局)
1.4 优化方法
- 压缩存储:使用位图或稀疏矩阵存储空闲体素
- 局部更新:只存活跃区域
- 多分辨率网格:大范围用粗体素,局部细节用细体素
2. 哈希体素网格(Voxel Hashing)
2.1 数据结构
- 使用 哈希表存储非空体素:
Hash(VoxelIndex)→VoxelData \text{Hash}(\text{VoxelIndex}) \to \text{VoxelData} Hash(VoxelIndex)→VoxelData
- VoxelIndex =
(i,j,k)
→ 空间体素坐标 - 常用哈希函数:
h(i,j,k)=(i∗p1)⊕(j∗p2)⊕(k∗p3) h(i,j,k) = (i * p_1) \oplus (j * p_2) \oplus (k * p_3) h(i,j,k)=(i∗p1)⊕(j∗p2)⊕(k∗p3)
p_1, p_2, p_3
为大质数- ⊕ 表示按位异或
2.2 优缺点
优点 | 缺点 |
---|---|
内存利用率高(稀疏环境) | 哈希冲突可能影响性能 |
支持大规模地图 | 邻域查询需要额外处理 |
插入删除快速 | 实现比 Dense Grid 复杂 |
2.3 使用场景
- KinectFusion、ElasticFusion 等稀疏 TSDF 融合
- 大规模三维稀疏地图构建
- GPU 并行加速的体素化算法
2.4 优化方法
- 缓存邻域体素:减少哈希查询开销
- 块体素化(Voxel Block):每个哈希桶存一块小体素阵列,提高连续内存访问效率
3. 八叉树(Octree)
3.1 数据结构
-
空间递归划分为 8 个子节点(立方体分割)
-
节点属性:
- 是否被占据(占据概率或TSDF)
- 孩子节点指针(8 个子节点)
- 层级信息(深度)
-
查找公式(文字版):
如果点在节点内且不是叶子节点 → 遍历子节点 \text{如果点在节点内且不是叶子节点 → 遍历子节点} 如果点在节点内且不是叶子节点 → 遍历子节点
3.2 优缺点
优点 | 缺点 |
---|---|
节约内存(空旷区域用大块体素) | 实现复杂 |
自适应分辨率 | 遍历和邻域查询开销高 |
支持多分辨率搜索 | 指针存储增加内存开销 |
3.3 使用场景
- OctoMap 占据栅格
- 大规模三维 SLAM
- 碰撞检测与路径规划
3.4 优化方法
- 叶子节点压缩:叶子节点只存数据索引或统计量
- 层次搜索优化:先粗粒度快速定位,再精粒度搜索
- GPU Octree:将八叉树节点映射到连续数组,提高并行性能
4. 层次体素(Hierarchical Voxel)
- 概念:结合 Dense Grid + Octree 的思想,粗网格 + 细网格混合
- 粗体素快速排除大范围空域
- 细体素用于局部精度计算
5. 数据存储与压缩技术
- 稀疏矩阵 / CSR(Compressed Sparse Row)
- 位图(Bitmask)表示占据状态
- 体素块压缩(Voxel Block Compression)
- GPU-friendly 内存布局:连续数组存储叶子节点,提高 cache 命中率
6. 搜索效率对比
数据结构 | 插入 | 查找 | 邻域搜索 | 内存占用 | 适合环境 |
---|---|---|---|---|---|
Dense Grid | O(1) | O(1) | O(k) | 高 | 小范围、稠密 |
Hash Grid | O(1) | O(1) | O(k) | 中 | 大规模稀疏 |
Octree | O(log N) | O(log N) | O(k*log N) | 低 | 大规模、多分辨率 |
四、体素算法的复杂度与优化
-
空间复杂度
- Dense Grid: O(N3)O(N^3)O(N3)
- Sparse Hash / Octree: O(M)O(M)O(M),其中 M 为活跃体素数。
-
优化方法
- 自适应分辨率(Octree)
- 稀疏存储(哈希映射)
- GPU 并行化(TSDF 融合、Voxel Hashing)
- Sliding Window(只保留局部地图)
五、应用场景
-
SLAM / 机器人导航
- 占据栅格(OctoMap) → 路径规划
- TSDF / ESDF → 碰撞检测和路径优化
-
点云处理
- 体素滤波 → 下采样
- 特征计算(FPFH、SHOT)时用体素划分邻域
-
三维重建
- KinectFusion: TSDF + Marching Cubes
- Neural-SLAM: TSDF + 神经隐式场
-
计算机图形学
- 光线追踪、体绘制(体素化烟雾、火焰)
- 游戏场景(Minecraft 就是体素引擎)
六、优缺点总结
方法 | 优点 | 缺点 |
---|---|---|
Dense Grid | 索引快、实现简单 | 内存消耗大 |
Voxel Hashing | 稀疏高效、GPU友好 | 哈希冲突、邻域查询慢 |
Octree | 自适应分辨率、内存节省 | 实现复杂、遍历开销大 |
TSDF | 表面重建精度高 | 存储需求大、实时性要求高 |
七、示例代码(点云体素滤波)
下面给你一个 PCL VoxelGrid Filter 示例:
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <iostream>int main() {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());cloud->width = 5; cloud->height = 1; cloud->is_dense = false;cloud->points.resize(cloud->width * cloud->height);for (auto& p : cloud->points) {p.x = 1024 * rand() / (RAND_MAX + 1.0f);p.y = 1024 * rand() / (RAND_MAX + 1.0f);p.z = 1024 * rand() / (RAND_MAX + 1.0f);}pcl::VoxelGrid<pcl::PointXYZ> voxel;voxel.setInputCloud(cloud);voxel.setLeafSize(0.1f, 0.1f, 0.1f); // 设置体素大小pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());voxel.filter(*cloud_filtered);std::cout << "Original size: " << cloud->points.size() << ", Filtered size: " << cloud_filtered->points.size() << std::endl;
}