找回密码
 立即注册
首页 业界区 安全 深度图与点云去噪实战:双边滤波+统计/半径滤波原理与Op ...

深度图与点云去噪实战:双边滤波+统计/半径滤波原理与Open3D全实现

啤愿 2026-2-6 14:05:06
前言

在3D计算机视觉领域,深度相机(RealSense、Kinect、LiDAR)采集的深度图/点云数据不可避免会引入噪声——比如椒盐噪声、孤立点、稀疏噪点簇、高斯噪声等。这些噪声会直接影响后续的3D重建、点云配准、目标分割等任务的精度,因此针对性去噪是3D数据预处理的核心步骤。
本文将从实际应用出发,先详细讲解点云去噪中经典的统计滤波半径滤波(结合你提供的Open3D工业级代码逐行解析),再深入剖析深度图去噪的核心算法双边滤波(原理+实现+调优),最后实现深度图双边滤波+点云统计/半径滤波的全流程去噪方案,兼顾边缘保留和平滑去噪,适配大部分工业级场景。
1.png

@
目录

  • 前言
  • 前置知识与环境准备

    • 核心依赖
    • 噪声类型说明

  • 第一部分:点云去噪基础——统计滤波&半径滤波

    • 1.1 统计滤波(Statistical Outlier Removal)

      • 核心原理
      • 核心用途
      • 关键参数

    • 1.2 半径滤波(Radius Outlier Removal)

      • 核心原理
      • 核心用途
      • 关键参数

    • 1.3 工业级点云去噪代码逐行解析(你的PLY代码)

      • 完整代码(带详细注释+优化说明)
      • 核心模块解析
      • 调优黄金原则


  • 第二部分:深度图去噪核心——双边滤波(Bilateral Filter)

    • 2.1 为什么高斯滤波不适合深度图?
    • 2.2 双边滤波的核心原理

      • 双边滤波的数学公式

        • 1. 空域核(Spatial Kernel)
        • 2. 值域核(Range Kernel)

      • 双边滤波的核心特点

    • 2.3 双边滤波的核心用途
    • 2.4 双边滤波的实现(2D深度图+3D点云)

      • 2.4.1 实现1:2D深度图的双边滤波(OpenCV+NumPy)

        • 核心代码(深度图读取+双边滤波+可视化)
        • 关键参数调优
        • 效果说明

      • 2.4.2 实现2:3D点云的双边滤波(Open3D)

        • 核心代码(点云双边滤波+统计+半径滤波组合)
        • 点云双边滤波参数说明



  • 第三部分:工业级全流程——深度图→点云的联合去噪

    • 3.1 核心原理:深度图转点云
    • 3.2 全流程完整代码(深度图双边滤波→转点云→点云去噪)
    • 关键注意事项

  • 第四部分:各滤波算法对比与适用场景总结

    • 黄金组合策略

  • 第五部分:常见问题与解决方案
  • 总结

前置知识与环境准备

核心依赖

本文所有代码基于Python实现,需安装以下库:
  1. pip install open3d numpy opencv-python matplotlib
复制代码

  • open3d:3D点云处理核心库,提供滤波、可视化、格式转换等功能;
  • numpy:数值计算基础,处理深度图/点云的数组数据;
  • opencv-python:2D深度图的双边滤波、图像读写;
  • matplotlib:深度图滤波效果可视化。
噪声类型说明

深度图/点云的常见噪声及对应解决方案:

  • 孤立点/椒盐噪声:单个离散的噪点,无邻域点,用统计滤波剔除;
  • 稀疏噪点簇:几个噪点聚集在一起,统计滤波无法识别,用半径滤波剔除;
  • 高斯噪声/均匀噪声:深度图上的平滑噪声,会模糊但不破坏边缘,用双边滤波平滑,且保留物体轮廓;
  • 密集小噪点团:少量噪点紧密聚集,可选3D形态学开运算处理(牺牲少量细节)。
第一部分:点云去噪基础——统计滤波&半径滤波

核心是统计滤波+半径滤波的组合,还做了超大点数优化、低版本Open3D兼容、无效点剔除等实用设计。这部分先讲两个滤波的核心原理,再逐行解析代码,让你知其然更知其所以然。
1.1 统计滤波(Statistical Outlier Removal)

核心原理

统计滤波的核心思想是基于邻域点的距离统计特性剔除异常点,假设点云的正常点在空间中是连续分布的,噪点与邻域点的距离会远大于正常点。
具体步骤:

  • 对每个点,计算其到k个最近邻点的欧式距离的平均值
  • 所有点的平均距离服从高斯分布,计算该分布的均值$\mu$和标准差$\sigma$;
  • 剔除平均距离超过$\mu + std_ratio \times \sigma$的点(即距离远于正常范围的孤立点)。
核心用途

专门剔除单点椒盐噪声、离散孤立点,是点云去噪的第一步基础操作,几乎所有点云去噪流程都会先做统计滤波。
关键参数


  • nb_neighbors:每个点的近邻数,一般取20~50(点数越多取越大);
  • std_ratio:标准差系数,一般取1.0~2.0(噪声越严重,系数越小,剔除越严格)。
1.2 半径滤波(Radius Outlier Removal)

核心原理

半径滤波是统计滤波的补充,解决统计滤波对“小噪点簇”无效的问题,核心是基于邻域点的数量剔除异常点
具体步骤:

  • 以每个点为球心,设置一个固定半径r,构建3D球形邻域;
  • 统计球形邻域内的点数量,剔除数量少于min_nn的点;
  • 即使几个噪点聚集,其邻域内的点数仍会远少于正常点,因此能被有效剔除。
核心用途

专门剔除稀疏小噪点簇(2~5个噪点聚集),与统计滤波组合形成“孤立点+小簇噪点”的全覆盖剔除,是点云去噪的黄金组合
关键参数


  • radius:球形邻域半径(单位:米),一般取0.03~0.1m(根据点云密度调整,密度越大半径越小);
  • min_nn:邻域内最小有效点数,一般取8~15(与radius匹配,半径越大,最小点数取越大)。
1.3 工业级点云去噪代码逐行解析(你的PLY代码)

你提供的代码做了很多工业级优化(低版本兼容、超大点数防内存溢出、可选形态学开运算),以下分模块解析核心逻辑,标注关键亮点和参数调优技巧。
完整代码(带详细注释+优化说明)
  1. import open3d as o3d
  2. import numpy as np
  3. def ply_denoise(
  4.     input_ply_path,  # 输入带噪PLY文件路径
  5.     output_ply_path, # 输出去噪后PLY文件路径
  6.     # 统计滤波参数:去3D孤立点
  7.     stat_nb_neighbors=20,
  8.     stat_std_ratio=2.0,
  9.     # 半径滤波参数:去稀疏小噪点簇(单位:米)
  10.     rad_radius=0.05,
  11.     rad_min_nn=10,
  12.     # 可选3D形态学开运算(类似2D腐蚀,处理密集小噪点团)
  13.     use_morphology=False,
  14.     morph_voxel_size=0.02,
  15.     # 超大点数点云专属:轻量体素下采样(默认开启,降低计算量)
  16.     use_down_sample=True,
  17.     down_voxel_size=0.01
  18. ):
  19.     """
  20.     PLY点云文件去噪:低版本Open3D兼容+超大点数点云优化
  21.     核心:统计滤波+半径滤波,支持带颜色/无颜色PLY,保留点云原始信息
  22.     调优原则:噪声越严重,stat_std_ratio越小、rad_radius越大、rad_min_nn越大
  23.     """
  24.     # 1. 读取PLY点云文件,校验有效性
  25.     print(f"正在读取PLY文件:{input_ply_path}")
  26.     pcd = o3d.io.read_point_cloud(input_ply_path)
  27.     if not pcd.has_points():
  28.         raise ValueError("读取PLY失败!文件损坏或非点云文件")
  29.     original_point_num = len(pcd.points)
  30.     print(f"原始点云点数:{original_point_num:,}")  # 千分位显示,提升可读性
  31.     # 2. 核心预处理:无效点+重复点剔除【低版本Open3D兼容亮点】
  32.     # remove_infinite=True 适配所有Open3D版本(旧版本无remove_inf)
  33.     pcd = pcd.remove_non_finite_points(remove_nan=True, remove_infinite=True)
  34.     pcd = pcd.remove_duplicated_points()  # 剔除重复点,避免邻域统计误差
  35.     preprocess_point_num = len(pcd.points)
  36.     if original_point_num - preprocess_point_num > 0:
  37.         print(f"预处理:剔除{original_point_num - preprocess_point_num:,}个无效/重复点")
  38.     # 3. 超大点数优化:体素下采样【工业级亮点,防内存溢出】
  39.     # 点数超100万开启,通过体素化降低点数,不影响整体结构
  40.     if use_down_sample and preprocess_point_num > 1000000:
  41.         pcd = pcd.voxel_down_sample(voxel_size=down_voxel_size)
  42.         down_point_num = len(pcd.points)
  43.         print(f"超大点数优化:体素下采样后点数{down_point_num:,}(体素大小{down_voxel_size}m)")
  44.     else:
  45.         down_point_num = preprocess_point_num
  46.     # 4. 核心去噪:统计滤波+半径滤波【黄金组合,全覆盖离散噪点】
  47.     # 4.1 统计滤波:去孤立点
  48.     cl, ind = pcd.remove_statistical_outlier(nb_neighbors=stat_nb_neighbors, std_ratio=stat_std_ratio)
  49.     pcd_denoised = pcd.select_by_index(ind)
  50.     stat_remove = down_point_num - len(pcd_denoised.points)
  51.     print(f"统计滤波:剔除{stat_remove:,}个孤立噪点")
  52.     # 4.2 半径滤波:去稀疏小噪点簇(统计滤波的补充)
  53.     cl, ind = pcd_denoised.remove_radius_outlier(nb_points=rad_min_nn, radius=rad_radius)
  54.     pcd_denoised = pcd_denoised.select_by_index(ind)
  55.     rad_remove = down_point_num - stat_remove - len(pcd_denoised.points)
  56.     print(f"半径滤波:剔除{rad_remove:,}个稀疏噪点")
  57.     # 5. 可选:3D形态学开运算【处理密集小噪点团,牺牲少量细节】
  58.     # 原理:先腐蚀(剔除小簇噪点)后膨胀(恢复正常点云结构)
  59.     if use_morphology:
  60.         print(f"开启3D开运算(腐蚀+膨胀),体素大小{morph_voxel_size}m")
  61.         pcd_down = pcd_denoised.voxel_down_sample(voxel_size=morph_voxel_size)  # 腐蚀
  62.         pcd_down.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=morph_voxel_size*2, max_nn=30))  # 估计法向量,为膨胀做准备
  63.         pcd_denoised = pcd_down.voxel_up_sample(voxel_size=morph_voxel_size/3)  # 膨胀
  64.     # 6. 保存去噪后PLY文件,支持带颜色点云
  65.     o3d.io.write_point_cloud(output_ply_path, pcd_denoised, write_ascii=True)  # write_ascii=True提升兼容性
  66.     final_num = len(pcd_denoised.points)
  67.     total_remove = original_point_num - final_num
  68.     print(f"\n去噪完成!总剔除{total_remove:,}个噪点,剩余有效点数{final_num:,}")
  69.     print(f"去噪后文件已保存:{output_ply_path}")
  70.     # 7. 可视化:超大点数建议关闭(避免卡顿)
  71.     o3d.visualization.draw_geometries([pcd_denoised], window_name="PLY去噪结果", width=800, height=600)
  72.     return pcd_denoised
  73. # 主函数:仅需修改输入输出路径,默认参数适配80%场景
  74. if __name__ == "__main__":
  75.     INPUT_PLY = "simulated_depth_scan.ply"  # 你的带噪PLY文件路径
  76.     OUTPUT_PLY = "denoised_clean.ply"       # 去噪后保存路径
  77.     ply_denoise(
  78.         input_ply_path=INPUT_PLY,
  79.         output_ply_path=OUTPUT_PLY,
  80.         # 噪声严重时的调优示例
  81.         # stat_nb_neighbors=25,  # 增加近邻数,统计更稳健
  82.         # stat_std_ratio=1.5,    # 减小系数,剔除更严格
  83.         # rad_radius=0.06,       # 增大半径,检测更多稀疏簇
  84.         # rad_min_nn=12,         # 增加最小点数,剔除更严格
  85.         # use_morphology=True,   # 有密集小噪点团时开启
  86.         # morph_voxel_size=0.02
  87.     )
复制代码
核心模块解析


  • 无效点/重复点剔除:Open3D的remove_non_finite_points是核心,修复了低版本兼容问题(将remove_inf改为remove_infinite),重复点会导致邻域统计偏差,必须剔除;
  • 超大点数体素下采样:针对500万+的点云,体素下采样能在不破坏整体结构的前提下降低点数,避免后续滤波的内存溢出和卡顿,是工业级代码的关键优化;
  • 统计+半径滤波组合:先剔除孤立点,再剔除稀疏小簇,两者互补,覆盖了绝大多数离散噪点场景;
  • 可选3D形态学开运算:原理是“腐蚀+膨胀”,适合处理密集小噪点团(比如10个左右噪点紧密聚集),但会损失少量细节,因此设为可选。
调优黄金原则

噪声越严重(比如深度相机距离目标过远、光照复杂),按以下方式调参:

  • 统计滤波:stat_std_ratio调小(1.01.5)、`stat_nb_neighbors`调大(3050);
  • 半径滤波:rad_radius调大(0.060.1m)、`rad_min_nn`调大(1220);
  • 密集噪点团:开启use_morphology,morph_voxel_size取0.02~0.05m(根据点云密度调整)。
第二部分:深度图去噪核心——双边滤波(Bilateral Filter)

统计滤波和半径滤波是点云3D层面的去噪,适合剔除离散噪点,但无法处理深度图2D层面的高斯噪声/均匀噪声(这类噪声会让深度图整体粗糙,无明显离散点)。
而双边滤波是深度图去噪的经典算法,核心优势是边缘保留的平滑去噪——普通高斯滤波会模糊物体边缘,而双边滤波能在平滑噪声的同时,完整保留深度图的边缘轮廓(比如桌子和墙面的交界、物体的轮廓),这对后续的3D重建至关重要。
2.1 为什么高斯滤波不适合深度图?

在讲双边滤波前,先理解高斯滤波的局限性:
高斯滤波是空域唯一的滤波,其权重仅由像素的空间距离决定——距离越近,权重越大,参与滤波的贡献越高。
公式:$G_\sigma(x,y) = \frac{1}{2\pi\sigma2}e{-\frac{x2+y2}{2\sigma^2}}$
问题在于:深度图的边缘处,相邻像素的深度值差异极大(比如墙面深度1m,桌子深度0.5m),高斯滤波会将边缘两侧的像素混合,导致边缘模糊,而边缘是3D场景的核心结构信息,模糊后会严重影响后续的点云生成和3D重建。
2.2 双边滤波的核心原理

双边滤波的核心创新是:将空域核(Spatial Kernel)和值域核(Range Kernel)结合,滤波权重由空间距离深度值相似性共同决定。
只有满足两个条件的像素,才会参与当前像素的滤波计算:

  • 空间近:像素在当前像素的邻域内(空域核控制);
  • 深度值相似:像素的深度值与当前像素的深度值差异小(值域核控制)。
这样一来,边缘两侧的像素因深度值差异大,不会互相参与滤波,从而保留边缘;而同一区域内的像素因空间近且深度值相似,会被平滑滤波,从而去除噪声
双边滤波的数学公式

对于深度图中的像素$p(x_p,y_p)$,其滤波后的深度值$I(p)$为:
$$I(p) = \frac{1}{W_p} \sum_{q \in N(p)} w(p,q) \cdot I(q)$$
其中:

  • $N(p)$:像素$p$的邻域(比如3×3、5×5);
  • $W_p = \sum_{q \in N(p)} w(p,q)$:归一化权重,保证滤波后深度值范围不变;
  • $w(p,q) = w_s(p,q) \cdot w_r(p,q)$:联合权重,由空域核和值域核相乘得到。
1. 空域核(Spatial Kernel)

与高斯滤波一致,控制空间距离的权重,$\sigma_s$为空域标准差
$$w_s(p,q) = e{-\frac{|p-q|2}{2\sigma_s^2}}$$

  • $|p-q|$:像素$p$和$q$的欧式距离;
  • $\sigma_s$越大,参与滤波的空间范围越广,平滑效果越强。
2. 值域核(Range Kernel)

控制深度值相似性的权重,$\sigma_r$为值域标准差,$I(p)$、$I(q)$为像素$p$、$q$的深度值:
$$w_r(p,q) = e{-\frac{|I(p)-I(q)|2}{2\sigma_r^2}}$$

  • $|I(p)-I(q)|$:像素$p$和$q$的深度值差异;
  • $\sigma_r$越大,对深度值差异的容忍度越高,平滑效果越强,但边缘保留越弱;$\sigma_r$越小,边缘保留越严格,平滑效果越弱。
双边滤波的核心特点


  • 边缘保留:最核心的优势,适合深度图、彩色图像等需要保留边缘的场景;
  • 局部性:仅利用邻域像素进行滤波,计算速度快,非迭代;
  • 非线性:因值域核的存在,双边滤波是非线性滤波(高斯滤波是线性);
  • 无参数迭代:仅需调优$\sigma_s$和$\sigma_r$,无需复杂的迭代参数。
2.3 双边滤波的核心用途


  • 深度图去噪:平滑高斯噪声、均匀噪声,保留物体边缘,是深度图预处理的首选算法
  • 彩色图像去噪:边缘保留的平滑去噪,避免图像模糊;
  • 点云平滑:Open3D提供了点云层面的双边滤波,可对3D点云进行边缘保留的平滑;
  • 深度图空洞填充:结合邻域深度值相似性,对小空洞进行合理填充。
2.4 双边滤波的实现(2D深度图+3D点云)

双边滤波的实现分两种场景:2D深度图层面(预处理,效果最好)和3D点云层面(后处理,优化点云平滑度),以下分别实现,且与前文的统计+半径滤波结合。
2.4.1 实现1:2D深度图的双边滤波(OpenCV+NumPy)

OpenCV提供了现成的cv2.bilateralFilter函数,专门用于双边滤波,适配深度图的16位uint16格式(深度相机采集的深度图默认格式),无需手写复杂的卷积逻辑,直接调用即可。
核心代码(深度图读取+双边滤波+可视化)
  1. import cv2
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. def depth_bilateral_filter(
  5.     depth_img_path,
  6.     output_depth_path,
  7.     d=5,        # 滤波邻域直径,奇数(3/5/7),越大平滑范围越广
  8.     sigmaColor=10,  # 值域标准差σr,深度值相似性权重
  9.     sigmaSpace=10   # 空域标准差σs,空间距离权重
  10. ):
  11.     """
  12.     2D深度图双边滤波:边缘保留平滑去噪,适配16位uint16深度图
  13.     调优原则:噪声越严重,d/ sigmaSpace越大;需强边缘保留,sigmaColor越小
  14.     """
  15.     # 1. 读取深度图(深度相机采集的深度图为16位uint16,单通道)
  16.     depth = cv2.imread(depth_img_path, cv2.IMREAD_UNCHANGED)
  17.     if depth is None:
  18.         raise ValueError("读取深度图失败!文件损坏或非深度图文件")
  19.     # 转换为float32,避免滤波时数值溢出
  20.     depth_float = depth.astype(np.float32)
  21.     print(f"深度图尺寸:{depth.shape},深度值范围:{np.min(depth)}~{np.max(depth)}")
  22.     # 2. 双边滤波核心调用
  23.     # cv2.bilateralFilter:src-输入图像,d-邻域直径,sigmaColor-值域σ,sigmaSpace-空域σ
  24.     # 注意:深度图为单通道,彩色图为3通道,函数自动适配
  25.     depth_denoised = cv2.bilateralFilter(depth_float, d=d, sigmaColor=sigmaColor, sigmaSpace=sigmaSpace)
  26.     # 转换回16位uint16,保存为原始深度图格式
  27.     depth_denoised = depth_denoised.astype(np.uint16)
  28.     # 3. 保存滤波后的深度图
  29.     cv2.imwrite(output_depth_path, depth_denoised)
  30.     print(f"双边滤波后的深度图已保存:{output_depth_path}")
  31.     # 4. 可视化滤波效果(对比原始和去噪后的深度图)
  32.     plt.figure(figsize=(12, 6))
  33.     # 原始深度图
  34.     plt.subplot(1, 2, 1)
  35.     plt.imshow(depth, cmap="jet")
  36.     plt.title("Original Depth Map", fontsize=14)
  37.     plt.axis("off")
  38.     plt.colorbar()
  39.     # 去噪后深度图
  40.     plt.subplot(1, 2, 2)
  41.     plt.imshow(depth_denoised, cmap="jet")
  42.     plt.title("Denoised Depth Map (Bilateral Filter)", fontsize=14)
  43.     plt.axis("off")
  44.     plt.colorbar()
  45.     plt.tight_layout()
  46.     plt.show()
  47.     return depth_denoised
  48. # 主函数调用
  49. if __name__ == "__main__":
  50.     INPUT_DEPTH = "depth_noise.png"  # 你的带噪深度图(16位uint16)
  51.     OUTPUT_DEPTH = "depth_denoised.png"  # 滤波后深度图
  52.     depth_bilateral_filter(
  53.         depth_img_path=INPUT_DEPTH,
  54.         output_depth_path=OUTPUT_DEPTH,
  55.         d=5,           # 5×5邻域,适中的平滑范围
  56.         sigmaColor=15, # 值域σ,对深度值差异的容忍度适中
  57.         sigmaSpace=15  # 空域σ,空间邻域范围适中
  58.     )
复制代码
关键参数调优

cv2.bilateralFilter的3个核心参数,调优直接决定滤波效果,黄金调优原则

  • d(邻域直径):取3/5/7(奇数),噪声越严重取越大,过大会导致轻微边缘模糊;
  • sigmaColor(值域$\sigma_r$):深度图一般取1030,**需强边缘保留则取小值(510)**,需强平滑则取大值(20~30);
  • sigmaSpace(空域$\sigma_s$):与sigmaColor匹配,一般取相同值,10~30即可。
效果说明

滤波后,深度图的局部噪声会被平滑(比如同一平面的粗糙点),而物体边缘会被完整保留(比如深度值突变的区域),这是高斯滤波无法实现的效果。
2.4.2 实现2:3D点云的双边滤波(Open3D)

Open3D提供了点云层面的双边滤波函数filter_smooth_bilateral,可对3D点云进行边缘保留的平滑,适合将深度图转点云后,进一步优化点云的平滑度,可与前文的统计+半径滤波组合使用。
核心代码(点云双边滤波+统计+半径滤波组合)
  1. import open3d as o3d
  2. import numpy as np
  3. def pcd_bilateral_denoise(
  4.     input_ply_path,
  5.     output_ply_path,
  6.     # 双边滤波参数
  7.     bilateral_sigma1=0.01,  # 空域σ,点云空间距离权重
  8.     bilateral_sigma2=0.05,  # 值域σ,点云法向量/深度值相似性权重
  9.     # 统计+半径滤波参数(复用前文)
  10.     stat_nb_neighbors=20,
  11.     stat_std_ratio=2.0,
  12.     rad_radius=0.05,
  13.     rad_min_nn=10
  14. ):
  15.     """
  16.     3D点云全流程去噪:双边滤波(平滑)+ 统计滤波+半径滤波(剔离散噪点)
  17.     先平滑,再剔噪点,效果优于单独使用某一种滤波
  18.     """
  19.     # 1. 读取点云并预处理
  20.     pcd = o3d.io.read_point_cloud(input_ply_path)
  21.     if not pcd.has_points():
  22.         raise ValueError("读取点云失败!")
  23.     print(f"原始点云点数:{len(pcd.points):,}")
  24.     # 剔除无效/重复点
  25.     pcd = pcd.remove_non_finite_points(remove_nan=True, remove_infinite=True)
  26.     pcd = pcd.remove_duplicated_points()
  27.     # 2. 点云双边滤波【边缘保留平滑】
  28.     # 先估计法向量(双边滤波需要法向量计算值域相似性)
  29.     pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
  30.     # 双边滤波核心调用
  31.     pcd_smooth = pcd.filter_smooth_bilateral(sigma1=bilateral_sigma1, sigma2=bilateral_sigma2)
  32.     print(f"双边滤波后点云点数:{len(pcd_smooth.points):,}")
  33.     # 3. 统计+半径滤波【剔除离散噪点】
  34.     cl, ind = pcd_smooth.remove_statistical_outlier(nb_neighbors=stat_nb_neighbors, std_ratio=stat_std_ratio)
  35.     pcd_denoised = pcd_smooth.select_by_index(ind)
  36.     cl, ind = pcd_denoised.remove_radius_outlier(nb_points=rad_min_nn, radius=rad_radius)
  37.     pcd_denoised = pcd_denoised.select_by_index(ind)
  38.     # 4. 保存并可视化
  39.     o3d.io.write_point_cloud(output_ply_path, pcd_denoised)
  40.     print(f"去噪后点云点数:{len(pcd_denoised.points):,},已保存至:{output_ply_path}")
  41.     o3d.visualization.draw_geometries([pcd_denoised], window_name="点云双边滤波+统计半径滤波结果")
  42.     return pcd_denoised
  43. # 主函数调用
  44. if __name__ == "__main__":
  45.     INPUT_PLY = "simulated_depth_scan.ply"
  46.     OUTPUT_PLY = "pcd_bilateral_denoised.ply"
  47.     pcd_bilateral_denoise(
  48.         input_ply_path=INPUT_PLY,
  49.         output_ply_path=OUTPUT_PLY,
  50.         bilateral_sigma1=0.01,
  51.         bilateral_sigma2=0.05
  52.     )
复制代码
点云双边滤波参数说明


  • sigma1:空域标准差,控制点云的空间距离权重,一般取0.01~0.05m(根据点云密度调整);
  • sigma2:值域标准差,控制点云的法向量/深度值相似性权重,一般取0.05~0.1m,越大平滑效果越强。
注意:点云双边滤波前必须调用estimate_normals估计法向量,因为Open3D的点云双边滤波是基于法向量相似性计算值域权重的,法向量估计的精度会影响滤波效果。
第三部分:工业级全流程——深度图→点云的联合去噪

实际项目中,深度相机采集的是2D深度图,需要先将深度图转换为3D点云,再进行后续处理。因此最优的去噪流程是:
深度图双边滤波(2D层面平滑,保留边缘)→ 深度图转点云 → 点云统计+半径滤波(3D层面剔离散噪点)
该流程结合了双边滤波的边缘保留平滑和统计+半径滤波的离散噪点剔除,是工业级深度图/点云去噪的黄金流程,以下实现完整代码。
3.1 核心原理:深度图转点云

深度图转点云的核心是相机内参,对于像素$(u,v)$,其深度值为$z$,对应的3D空间坐标$(X,Y,Z)$为:
$$X = (u - cx) \times \frac{z}{fx}$$
$$Y = (v - cy) \times \frac{z}{fy}$$
$$Z = z$$
其中:

  • $(fx, fy)$:相机的焦距(像素单位);
  • $(cx, cy)$:相机的主点坐标(像素单位);
  • 以上参数可从深度相机的说明书或SDK中获取(比如Intel RealSense的fx≈600,fy≈600,cx≈320,cy≈240)。
3.2 全流程完整代码(深度图双边滤波→转点云→点云去噪)
  1. import cv2
  2. import numpy as np
  3. import open3d as o3d
  4. # 第一步:深度图双边滤波(复用前文函数)
  5. def depth_bilateral_filter(depth_img_path, d=5, sigmaColor=15, sigmaSpace=15):
  6.     depth = cv2.imread(depth_img_path, cv2.IMREAD_UNCHANGED)
  7.     depth_float = depth.astype(np.float32)
  8.     depth_denoised = cv2.bilateralFilter(depth_float, d=d, sigmaColor=sigmaColor, sigmaSpace=sigmaSpace)
  9.     return depth_denoised.astype(np.uint16)
  10. # 第二步:深度图转点云(基于相机内参)
  11. def depth2pcd(depth_img, fx=615.0, fy=615.0, cx=320.0, cy=240.0, scale=1000.0):
  12.     """
  13.     深度图转3D点云
  14.     参数:
  15.         depth_img: 16位uint16深度图
  16.         fx/fy: 相机焦距(像素单位),默认适配Intel RealSense D435
  17.         cx/cy: 相机主点坐标,默认适配640×480分辨率
  18.         scale: 深度值缩放系数(深度图值为mm,转m需除以1000)
  19.     """
  20.     h, w = depth_img.shape
  21.     # 生成像素坐标网格
  22.     u, v = np.meshgrid(np.arange(w), np.arange(h))
  23.     # 计算3D空间坐标
  24.     z = depth_img / scale  # 转米单位
  25.     x = (u - cx) * z / fx
  26.     y = (v - cy) * z / fy
  27.     # 拼接点云坐标,剔除深度值为0的无效点
  28.     points = np.stack([x, y, z], axis=-1).reshape(-1, 3)
  29.     valid_mask = z.reshape(-1) > 0  # 剔除深度为0的点
  30.     points = points[valid_mask]
  31.     # 创建Open3D点云对象
  32.     pcd = o3d.geometry.PointCloud()
  33.     pcd.points = o3d.utility.Vector3dVector(points)
  34.     print(f"深度图转点云完成,有效点数:{len(pcd.points):,}")
  35.     return pcd
  36. # 第三步:点云统计+半径滤波(复用前文工业级函数)
  37. def ply_denoise(pcd, stat_nb_neighbors=20, stat_std_ratio=2.0, rad_radius=0.05, rad_min_nn=10):
  38.     pcd = pcd.remove_non_finite_points(remove_nan=True, remove_infinite=True)
  39.     pcd = pcd.remove_duplicated_points()
  40.     # 统计滤波
  41.     cl, ind = pcd.remove_statistical_outlier(nb_neighbors=stat_nb_neighbors, std_ratio=stat_std_ratio)
  42.     pcd_denoised = pcd.select_by_index(ind)
  43.     # 半径滤波
  44.     cl, ind = pcd_denoised.remove_radius_outlier(nb_points=rad_min_nn, radius=rad_radius)
  45.     pcd_denoised = pcd_denoised.select_by_index(ind)
  46.     return pcd_denoised
  47. # 主流程:深度图双边滤波 → 转点云 → 点云去噪 → 保存可视化
  48. if __name__ == "__main__":
  49.     # 配置参数
  50.     INPUT_DEPTH = "depth_noise.png"    # 输入带噪深度图
  51.     OUTPUT_DEPTH = "depth_denoised.png"# 输出滤波后深度图
  52.     OUTPUT_PLY = "final_denoised.ply" # 输出最终点云
  53.     # 相机内参(根据你的深度相机修改!)
  54.     FX, FY = 615.0, 615.0
  55.     CX, CY = 320.0, 240.0
  56.     # 1. 深度图双边滤波
  57.     depth_denoised = depth_bilateral_filter(INPUT_DEPTH, d=5, sigmaColor=15, sigmaSpace=15)
  58.     cv2.imwrite(OUTPUT_DEPTH, depth_denoised)
  59.     # 2. 滤波后的深度图转点云
  60.     pcd = depth2pcd(depth_denoised, fx=FX, fy=FY, cx=CX, cy=CY)
  61.     # 3. 点云统计+半径滤波
  62.     pcd_final = ply_denoise(pcd)
  63.     # 4. 保存并可视化最终点云
  64.     o3d.io.write_point_cloud(OUTPUT_PLY, pcd_final)
  65.     print(f"全流程去噪完成,最终点云已保存:{OUTPUT_PLY},点数:{len(pcd_final.points):,}")
  66.     o3d.visualization.draw_geometries([pcd_final], window_name="深度图+点云全流程去噪结果")
复制代码
关键注意事项


  • 相机内参修改:代码中的fx/fy/cx/cy是默认值,需根据你的深度相机实际参数修改(可从相机SDK、标定工具中获取),否则点云会出现畸变;
  • 深度值缩放:深度相机采集的深度图值一般为毫米(mm),代码中scale=1000将其转为米(m),若你的深度图单位是米,需将scale改为1;
  • 分辨率匹配:内参与深度图分辨率一一对应(比如640×480的深度图对应cx=320,cy=240),若深度图分辨率为1280×720,需调整cx/cy为640/360。
第四部分:各滤波算法对比与适用场景总结

为了让你在实际项目中快速选择合适的滤波算法,以下对统计滤波、半径滤波、双边滤波进行全方位对比,明确各自的适用场景和优缺点:
滤波算法处理层面核心功能优点缺点适用场景统计滤波3D点云剔除孤立点/椒盐噪声计算快、参数少、适配所有点云对小噪点簇无效点云初步去噪,剔除离散单点噪声半径滤波3D点云剔除稀疏小噪点簇补充统计滤波,处理小簇噪点半径参数需根据点云密度调整统计滤波后二次去噪,剔除稀疏簇噪声双边滤波(2D)2D深度图边缘保留的平滑去噪保留边缘、平滑高斯/均匀噪声、效果最好对离散点无效深度图预处理,平滑整体噪声,保留场景结构双边滤波(3D)3D点云边缘保留的点云平滑优化点云平滑度,保留3D边缘需估计法向量、对离散点无效点云后处理,优化平滑度(配合统计+半径滤波)黄金组合策略


  • 深度图预处理:优先使用2D双边滤波,平滑噪声并保留边缘,这是最关键的一步;
  • 点云去噪:深度图转点云后,使用统计滤波+半径滤波组合,剔除离散噪点;
  • 点云优化:若点云整体粗糙,可在统计+半径滤波前增加3D双边滤波,实现平滑+剔噪的双重效果;
  • 密集噪点团:若存在密集小噪点团,开启3D形态学开运算(牺牲少量细节)。
第五部分:常见问题与解决方案


  • 深度图滤波后出现空洞:深度图本身存在的空洞,双边滤波无法填充,可使用cv2.inpaint进行空洞填充,或在点云层面使用o3d.geometry.PointCloud.voxel_up_sample补点;
  • 点云可视化卡顿/内存溢出:开启体素下采样(voxel_down_sample),降低点云点数,体素大小取0.01~0.05m;
  • 双边滤波后边缘模糊:减小sigmaColor(值域σ)或d(邻域直径),增强边缘保留效果;
  • 统计+半径滤波后丢失有效点:调大stat_std_ratio、减小rad_radius或rad_min_nn,降低剔除严格度;
  • 深度图转点云后点云畸变:检查相机内参是否正确,确保内参与深度图分辨率、相机型号匹配。
总结

深度图/点云去噪是3D计算机视觉的基础预处理步骤,其核心是针对性选择滤波算法

  • 双边滤波是深度图去噪的核心,凭借边缘保留的平滑特性,完胜传统的高斯滤波,是2D层面去噪的首选;
  • 统计滤波+半径滤波是点云去噪的黄金组合,能全覆盖剔除3D层面的孤立点和稀疏小噪点簇,且计算高效、参数易调;
  • 工业级的最优流程是深度图双边滤波→转点云→点云统计+半径滤波,结合了2D层面的平滑和3D层面的剔噪,兼顾效果和效率。
本文的所有代码均为开箱即用,适配大部分深度相机(RealSense、Kinect、LiDAR)采集的数据,只需根据实际场景微调参数,即可应用于3D重建、点云配准、目标检测等实际项目中。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-2-11 15:24:26

举报

7 天前

举报

您需要登录后才可以回帖 登录 | 立即注册