4. 点云降采样
任务目标
实现均匀采样算法,从输入点云中均匀抽取固定数量的点,使采样结果在空间分布上保持均匀,避免局部点云过密或过疏。
核心原理
均匀采样的关键是将三维空间划分为体素网格,每个体素内最多保留一个点。通过遍历所有点并记录其所在体素,仅保留每个体素中第一个遇到的点,即可实现空间均匀分布。该方法不依赖点云密度,采样结果数量由体素尺寸决定,而非固定数量。
完整实现
import numpy as np
def uniform_sample(points, voxel_size):
"""
均匀采样:将空间划分为体素网格,每个体素保留一个点
参数:
points: (N, 3) float array, 输入点云坐标
voxel_size: float, 体素边长
返回:
sampled: (M, 3) float array, 采样后的点云 (M ≤ N)
"""
if voxel_size <= 0:
raise ValueError("voxel_size 必须为正数")
if points.shape[0] == 0:
return points.copy()
# 计算每个点所属的体素索引
voxel_indices = np.floor(points / voxel_size).astype(np.int64)
# 使用字典记录每个体素中第一个出现的点
seen = {}
sampled_list = []
for i in range(points.shape[0]):
key = tuple(voxel_indices[i])
if key not in seen:
seen[key] = True
sampled_list.append(points[i])
return np.array(sampled_list)
def uniform_sample_fixed_count(points, target_count, max_iter=20):
"""
均匀采样到指定数量:通过二分搜索体素尺寸,使采样点数接近目标值
参数:
points: (N, 3) float array, 输入点云
target_count: int, 目标采样点数
max_iter: int, 最大迭代次数
返回:
sampled: (M, 3) float array, 采样结果 (M ≈ target_count)
voxel_size: float, 最终使用的体素尺寸
"""
if target_count <= 0:
raise ValueError("target_count 必须为正数")
if points.shape[0] <= target_count:
return points.copy(), 0.0
# 计算点云包围盒
min_bound = points.min(axis=0)
max_bound = points.max(axis=0)
diag = np.linalg.norm(max_bound - min_bound)
# 二分搜索体素尺寸
lo, hi = diag / 1000, diag
best_voxel = hi
best_count = 1
for _ in range(max_iter):
mid = (lo + hi) / 2
sampled = uniform_sample(points, mid)
count = sampled.shape[0]
if count > target_count:
lo = mid # 体素太大,采样点太少 → 缩小体素
else:
hi = mid # 体素太小,采样点太多 → 增大体素
if abs(count - target_count) < abs(best_count - target_count):
best_voxel = mid
best_count = count
# 用最优体素尺寸做最终采样
sampled = uniform_sample(points, best_voxel)
return sampled, best_voxel
运行与验证
# 生成测试数据:两个密度不同的区域
np.random.seed(42)
dense = np.random.uniform([0, 0, 0], [1, 1, 1], size=(5000, 3))
sparse = np.random.uniform([2, 2, 2], [3, 3, 3], size=(500, 3))
points = np.vstack([dense, sparse])
# 执行均匀采样
sampled, voxel = uniform_sample_fixed_count(points, target_count=1000)
print(f"输入点数: {points.shape[0]}")
print(f"采样点数: {sampled.shape[0]}")
print(f"体素尺寸: {voxel:.4f}")
# 验证空间均匀性:统计两个区域的采样点比例
dense_region = ((sampled[:, 0] < 1.5) & (sampled[:, 1] < 1.5) & (sampled[:, 2] < 1.5)).sum()
sparse_region = sampled.shape[0] - dense_region
print(f"密集区采样点数: {dense_region}")
print(f"稀疏区采样点数: {sparse_region}")
print(f"原始比例(密集:稀疏) = 5000:500 = 10:1")
print(f"采样比例 = {dense_region}:{sparse_region}")
通过条件
- 采样点数接近目标值(偏差在±5%以内)
- 密集区与稀疏区的采样点比例明显小于原始10:1,表明空间分布更均匀
- 采样结果中不存在重复点(每个体素仅保留一个点)
关键说明
均匀采样与体素下采样(Voxel Grid)的区别在于:体素下采样用体素内所有点的重心代替该体素,而均匀采样直接保留体素内第一个原始点。前者会生成新点,后者保留原始点坐标。当需要固定输出数量时,使用二分搜索自动确定体素尺寸,避免手动调参。