目录
Open3D点云算法汇总及实战案例汇总的目录地址:
Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客
一、概述
基于法线的双边滤波是一种用于点云平滑的技术,它结合了点与其邻域点之间的几何距离和法线方向的相似性,从而在去除噪声的同时保留几何特征。这种滤波方法广泛应用于点云处理领域,尤其是在需要保留表面细节和边缘的场景中。
1.1原理
双边滤波是一种非线性滤波方法,它在计算点云中某个点的加权平均值时,同时考虑了几何距离和法线方向的相似性。对于点云中的每个点,其滤波结果是其邻域点的加权平均值,权重由两个因素决定:
- 空间权重 (spatial weight): 反映点与邻域点之间的几何距离,距离越近的点权重越大。空间权重通常使用高斯函数来计算。
- 法线权重 (normal weight): 反映点与邻域点之间的法线差异,法线方向越相似的点权重越大。法线权重也通常使用高斯函数来计算。
1.2实现步骤
- 加载点云数据: 使用 Open3D 加载原始点云。
- 估计法向量: 计算点云中每个点的法向量,为双边滤波提供参考信息。
- 自定义双边滤波: 使用自定义的双边滤波函数,对点云进行平滑处理,保留边缘和几何特征。
- 可视化结果: 显示原始点云和滤波后的点云,便于对比滤波效果。
1.3应用场景
- 降噪: 在点云数据中减少噪声,同时保留几何结构,适用于点云数据的预处理。
- 边缘保留平滑: 在保持边缘特征的同时,平滑点云表面。
- 点云表面重建: 提高点云表面重建的精度,保留表面细节
二、代码实现
2.1关键函数
bilateral_filter 函数的作用是通过结合点的几何距离和法线方向的差异,对点云进行双边滤波,保留边缘特征的同时减少噪声。
def bilateral_filter(pcd, radius, sigma_s, sigma_r):
"""
对点云进行双边滤波,使用点的邻域几何距离和法线差异来平滑点云并保留结构特征。
参数:
pcd (open3d.geometry.PointCloud): 输入的点云。
radius (float): 搜索邻域的半径,定义邻域的范围。
sigma_s (float): 空间域参数,控制几何距离的影响。
sigma_r (float): 强度域参数,控制法线差异的影响。
返回:
open3d.geometry.PointCloud: 滤波后的点云。
"""
# 计算法向量,法向量用于在双边滤波过程中考虑法线差异
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=radius, max_nn=30))
# 建立 KD 树用于快速邻域搜索
kdtree = o3d.geometry.KDTreeFlann(pcd)
# 获取点云的点坐标和法向量数据
points = np.asarray(pcd.points)
normals = np.asarray(pcd.normals)
# 准备一个空数组存储滤波后的点
filtered_points = np.zeros_like(points)
num_points = len(points) # 点云中点的数量
# 遍历每个点,对其进行双边滤波处理
for i in range(num_points):
# 在给定半径内搜索当前点的邻域点
[k, idx, _] = kdtree.search_radius_vector_3d(points[i], radius)
# 如果邻域点少于3个,则跳过当前点的滤波处理
if k < 3:
filtered_points[i] = points[i]
continue
# 获取邻域点的坐标和法向量
neighbors = points[idx]
neighbor_normals = normals[idx]
# 计算邻域点与当前点的几何距离
spatial_distances = np.linalg.norm(neighbors - points[i], axis=1)
# 计算邻域点与当前点的法线方向差异
normal_distances = np.linalg.norm(neighbor_normals - normals[i], axis=1)
# 计算空间域的高斯权重
spatial_weights = np.exp(-0.5 * (spatial_distances / sigma_s) ** 2)
# 计算法线域的高斯权重
normal_weights = np.exp(-0.5 * (normal_distances / sigma_r) ** 2)
# 结合空间权重和法线权重,计算最终的权重
weights = spatial_weights * normal_weights
# 对权重进行归一化,使得权重总和为1
weights /= np.sum(weights)
# 根据权重对邻域点进行加权平均,得到滤波后的点位置
filtered_points[i] = np.sum(neighbors * weights[:, np.newaxis], axis=0)
# 创建新的点云对象,并更新点的位置
filtered_pcd = o3d.geometry.PointCloud()
filtered_pcd.points = o3d.utility.Vector3dVector(filtered_points)
filtered_pcd.normals = o3d.utility.Vector3dVector(normals)
return filtered_pcd
输入参数:
- pcd (open3d.geometry.PointCloud): 输入的点云对象,用于执行滤波操作。
- radius (float): 搜索邻域的半径,定义了每个点在进行滤波时考虑的邻域范围。较大的半径会使得更多的邻域点参与滤波,适合较大尺度的点云,而较小的半径则更适合细节丰富的点云。
- sigma_s (float): 控制空间距离影响的参数。这个值越大,几何距离对滤波结果的影响越小,反之亦然。如果设定得过小,则只有非常接近的邻域点才会对当前点产生显著影响,可能会过度平滑点云。
- sigma_r (float): 控制法线差异影响的参数。这个值越大,法线方向差异对滤波结果的影响越小,反之亦然。较小的值会更加强调保留法线相似的邻域点,有助于保留边缘特征。
输出参数:
- filtered_pcd (open3d.geometry.PointCloud): 滤波后的点云对象,保留了原始点云的几何结构,同时减少了噪声。
参数影响:
- radius: 影响邻域的大小。较大的半径会平滑更多的点,但也可能导致细节丢失;较小的半径则可能保留更多细节,但平滑效果不明显。
- sigma_s: 控制空间距离的影响。较大的 sigma_s 会使得远离当前点的邻域点也有较大权重,适合处理平滑的大区域。较小的 sigma_s 则会更关注于局部区域,适合处理具有较多细节的点云。
- sigma_r: 控制法线差异的影响。较大的 sigma_r 会使得法线差异对权重的影响减小,适合处理表面变化较缓的点云。较小的 sigma_r 则有助于保留边缘等具有明显法线变化的特征区域。
通过合理调整 radius、sigma_s 和 sigma_r 的数值,可以在平滑点云与保留几何特征之间找到一个平衡点,确保滤波后的点云既减少了噪声又保留了关键的几何特征。
2.2完整代码
import open3d as o3d
import numpy as np
def bilateral_filter(pcd, radius, sigma_s, sigma_r):
"""
对点云进行双边滤波,使用点的邻域几何距离和法线差异来平滑点云并保留结构特征。
参数:
pcd (open3d.geometry.PointCloud): 输入的点云。
radius (float): 搜索邻域的半径,定义邻域的范围。
sigma_s (float): 空间域参数,控制几何距离的影响。
sigma_r (float): 强度域参数,控制法线差异的影响。
返回:
open3d.geometry.PointCloud: 滤波后的点云。
"""
# 计算法向量,法向量用于在双边滤波过程中考虑法线差异
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=radius, max_nn=30))
# 建立 KD 树用于快速邻域搜索
kdtree = o3d.geometry.KDTreeFlann(pcd)
# 获取点云的点坐标和法向量数据
points = np.asarray(pcd.points)
normals = np.asarray(pcd.normals)
# 准备一个空数组存储滤波后的点
filtered_points = np.zeros_like(points)
num_points = len(points) # 点云中点的数量
# 遍历每个点,对其进行双边滤波处理
for i in range(num_points):
# 在给定半径内搜索当前点的邻域点
[k, idx, _] = kdtree.search_radius_vector_3d(points[i], radius)
# 如果邻域点少于3个,则跳过当前点的滤波处理
if k < 3:
filtered_points[i] = points[i]
continue
# 获取邻域点的坐标和法向量
neighbors = points[idx]
neighbor_normals = normals[idx]
# 计算邻域点与当前点的几何距离
spatial_distances = np.linalg.norm(neighbors - points[i], axis=1)
# 计算邻域点与当前点的法线方向差异
normal_distances = np.linalg.norm(neighbor_normals - normals[i], axis=1)
# 计算空间域的高斯权重
spatial_weights = np.exp(-0.5 * (spatial_distances / sigma_s) ** 2)
# 计算法线域的高斯权重
normal_weights = np.exp(-0.5 * (normal_distances / sigma_r) ** 2)
# 结合空间权重和法线权重,计算最终的权重
weights = spatial_weights * normal_weights
# 对权重进行归一化,使得权重总和为1
weights /= np.sum(weights)
# 根据权重对邻域点进行加权平均,得到滤波后的点位置
filtered_points[i] = np.sum(neighbors * weights[:, np.newaxis], axis=0)
# 创建新的点云对象,并更新点的位置
filtered_pcd = o3d.geometry.PointCloud()
filtered_pcd.points = o3d.utility.Vector3dVector(filtered_points)
filtered_pcd.normals = o3d.utility.Vector3dVector(normals)
return filtered_pcd
def main():
# 加载点云数据
pcd = o3d.io.read_point_cloud("bunny_nosiy.pcd")
# 显示原始点云
print("Displaying original point cloud...")
o3d.visualization.draw_geometries([pcd], window_name="Original Point Cloud", width=800, height=600)
# 应用自定义的双边滤波
filtered_pcd = bilateral_filter(pcd, radius=0.01, sigma_s=0.3, sigma_r=0.3)
# 显示滤波后的点云
print("Displaying bilateral filtered point cloud...")
o3d.visualization.draw_geometries([filtered_pcd], window_name="Bilateral Filtered Point Cloud", width=800,
height=600)
if __name__ == '__main__':
main()