4、点云预处理:体素滤波降采样、统计滤波去噪、半径滤波去噪

各位同学,欢迎来到点云预处理这一节。

说实话,雷达点云这东西,原始数据往往又密又乱。你直接拿去做聚类,效果通常很糟糕。我刚开始做项目时就被坑过——点云里全是噪声,聚类结果一塌糊涂。后来才明白,预处理这一步,其实比算法本身还重要。

今天咱们就聊聊三种最常用的滤波方法:体素滤波统计滤波半径滤波。它们各有各的脾气,用好了事半功倍。

4.1 体素滤波降采样

体素滤波,说白了就是给点云画个三维网格。每个小格子(体素)里,只保留一个代表点。

为什么要这么做?因为原始点云太密了。64线激光雷达,一帧数据可能有十几万个点。你想想看,这么多点全算一遍,CPU直接冒烟。体素滤波能大幅降低点数,同时保留点云的几何结构。

核心思想:用三维体素网格对点云进行下采样,每个体素内取重心或中心点。

我个人习惯用 Open3Dvoxel_down_sample 函数。参数就一个——体素大小。体素越大,降采样越狠。

import open3d as o3d

# 读取点云
pcd = o3d.io.read_point_cloud("lidar_data.pcd")
print(f"原始点数: {len(pcd.points)}")

# 体素滤波,体素大小设为0.1米
voxel_size = 0.1
pcd_down = pcd.voxel_down_sample(voxel_size)
print(f"降采样后点数: {len(pcd_down.points)}")

# 可视化对比
o3d.visualization.draw_geometries([pcd_down])

我在项目中遇到过一个问题:体素大小设得太小,降采样效果不明显;设得太大,点云细节全丢了。一般建议根据场景来调——室内场景0.05米,室外道路0.1~0.2米。

小技巧:体素滤波后,记得检查一下点云密度。如果降采样后点太稀疏,聚类时容易漏检小目标。

4.2 统计滤波去噪

统计滤波,专门对付离群点。什么是离群点?就是那些孤零零飘在空中的噪声点。

它的原理很简单:计算每个点周围K个邻居的平均距离。如果这个距离太大,说明它周围没几个邻居,那它大概率是噪声。

核心参数:

  • K近邻数:一般取20~50,点云密就取大点
  • 标准差倍数:通常取1.0~2.0,越大越宽松

嗯,这里要注意:标准差倍数设得太小,会把正常点也滤掉;设得太大,噪声滤不干净。我建议先从1.5开始试。

import open3d as o3d

# 统计滤波
pcd = o3d.io.read_point_cloud("lidar_data.pcd")

# 参数:邻居数30,标准差倍数1.5
pcd_filtered, indices = pcd.remove_statistical_outlier(
    nb_neighbors=30, 
    std_ratio=1.5
)

print(f"原始点数: {len(pcd.points)}")
print(f"滤波后点数: {len(pcd_filtered.points)}")

# 可视化滤波结果
o3d.visualization.draw_geometries([pcd_filtered])

我曾经在高速场景测试时发现,统计滤波对稀疏点云效果不太好。为什么呢?因为稀疏点云本身邻居就少,平均距离自然大,容易误判。所以,如果你点云本来就稀疏,建议先用体素滤波加密一下,或者改用半径滤波。

避坑指南:统计滤波对边缘点不太友好。比如建筑物边缘的点,邻居少,容易被当成噪声滤掉。我曾经因为这个把路沿石给滤没了,后来调大了标准差倍数才解决。

4.3 半径滤波去噪

半径滤波,思路更直接:以每个点为中心,画一个球。球里点数少于某个阈值,就干掉它。

你想想看,如果某个点周围半径0.3米内只有两三个点,那它八成是噪声。正常物体表面,点云应该是密集的。

核心参数:

  • 搜索半径:一般0.1~0.5米,根据点云密度来
  • 最少点数阈值:通常3~10个
import open3d as o3d

# 半径滤波
pcd = o3d.io.read_point_cloud("lidar_data.pcd")

# 参数:半径0.2米,最少5个点
pcd_filtered, indices = pcd.remove_radius_outlier(
    nb_points=5, 
    radius=0.2
)

print(f"原始点数: {len(pcd.points)}")
print(f"滤波后点数: {len(pcd_filtered.points)}")

# 可视化
o3d.visualization.draw_geometries([pcd_filtered])

半径滤波和统计滤波有什么区别?我个人的理解是:统计滤波看全局分布,半径滤波看局部密度。统计滤波适合去除稀疏的离群点,半径滤波适合去除孤立的小团噪声。

实战建议:我通常先做体素滤波降采样,再用半径滤波去噪。顺序别搞反——先滤波再降采样,噪声点会被保留下来。

4.4 三种滤波方法对比

方法 适用场景 优点 缺点
体素滤波 点云过密,需要降采样 速度快,保持几何结构 会丢失细节
统计滤波 稀疏离群噪声 参数直观,效果稳定 对边缘点不友好
半径滤波 孤立小团噪声 局部效果好,不易误判 对密度变化敏感

说实话,没有哪种滤波是万能的。我一般会组合使用:先体素滤波降采样到合理密度,再用半径滤波或统计滤波去噪。具体用哪个,看你的噪声类型。

4.5 完整预处理流程

最后,给大家一个我常用的预处理流水线。你可以直接拿去用。

import open3d as o3d
import numpy as np

def preprocess_point_cloud(pcd, voxel_size=0.1, 
                           radius=0.2, min_points=5):
    """
    点云预处理流水线
    参数:
        pcd: 原始点云
        voxel_size: 体素大小
        radius: 半径滤波搜索半径
        min_points: 半径滤波最少点数
    返回:
        预处理后的点云
    """
    # 1. 体素滤波降采样
    pcd_down = pcd.voxel_down_sample(voxel_size)
    
    # 2. 半径滤波去噪
    pcd_clean, _ = pcd_down.remove_radius_outlier(
        nb_points=min_points, 
        radius=radius
    )
    
    return pcd_clean

# 使用示例
pcd = o3d.io.read_point_cloud("lidar_data.pcd")
pcd_processed = preprocess_point_cloud(pcd)

# 保存结果
o3d.io.write_point_cloud("processed.pcd", pcd_processed)
print(f"预处理完成: {len(pcd.points)} -> {len(pcd_processed.points)}")

嗯,这个流程我用了好几年,在多个项目里验证过。当然,参数要根据你的传感器和场景来调。比如车载激光雷达,体素大小0.1米,半径0.3米,最少点数5个,基本够用。

最后提醒:预处理不是越狠越好。滤波太猛,会把小目标(比如行人、自行车)的点云也滤掉。我建议每次调参后都可视化看看效果,别偷懒。

好了,点云预处理就讲到这里。下一节咱们聊聊聚类算法,到时候这些预处理后的干净点云就能派上用场了。


公众号:蓝海资料掘金营,微信deep3321