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

点云预处理,说白了就是给原始点云「洗个澡」。

我刚开始做点云项目时,拿到的数据往往又大又脏。几十万个点挤在一起,还有一堆离群点飘在外面。直接扔进分割算法?结果惨不忍睹。后来我学乖了——预处理做得好,后面事半功倍。

今天咱们就聊聊四种最常用的滤波方法。它们各有各的脾气,用对了地方,效果立竿见影。

2.1 体素滤波降采样

体素滤波,我习惯叫它「点云瘦身术」。你想想看,一个激光雷达扫一圈,几百万个点就来了。这么多点,很多其实是冗余的——同一个平面上,几百个点和几十个点,对算法来说差别不大。

核心思想:把空间切成一个个小立方体(体素),每个体素里只保留一个代表点。这个代表点通常是体素内所有点的重心,或者中心点。

关键参数:体素大小(leaf size)

  • 体素越大,降采样越狠,点云越稀疏
  • 体素太小,降采样效果不明显
  • 我一般从 0.05m 开始试,根据场景调整

我在项目中遇到过一个问题:体素滤波后,原本的精细结构被磨平了。比如一个螺丝钉的螺纹,体素太大就直接消失了。所以选体素大小时,得先问问自己——我要保留多细的细节?

// C++ 示例:PCL 体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.1f, 0.1f, 0.1f);  // 体素边长 10cm
pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>);
voxel_filter.filter(*filtered_cloud);

小技巧:体素滤波不光能降采样,还能顺便把离群点干掉。因为孤立的点往往落不到体素里,或者体素里就它一个,滤波后自然就没了。

2.2 统计滤波去噪

统计滤波,我管它叫「点云清道夫」。它专门对付那些飘在空中的噪声点。

原理其实很简单:每个点周围都有邻居吧?计算每个点到它 k 个最近邻的平均距离。如果这个距离太大,说明它离大伙儿太远,八成是个噪声点,干掉它。

为什么会这样?因为真实物体的点云,点与点之间距离比较均匀。而噪声点往往是随机出现的,周围没什么伙伴。

参数 含义 我的经验值
k(邻居数) 计算距离时考虑多少个近邻 20~50,点云密的话取大点
std_dev_mult(标准差倍数) 距离均值 ± 几倍标准差之外算离群 1.0~2.0,保守点取 1.5

我曾经在一个工厂项目里,点云里全是飞溅的焊渣噪声。统计滤波一把梭,干净多了。但要注意——如果物体本身边缘很薄,比如一片树叶,统计滤波可能会把边缘点也当成噪声删掉。嗯,这里要小心。

// C++ 示例:PCL 统计滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50);          // 每个点考虑 50 个近邻
sor.setStddevMulThresh(1.5); // 标准差倍数阈值
sor.filter(*filtered_cloud);

避坑指南:我曾经把 std_dev_mult 设成 0.5,结果把正常点删掉了一大半。记住,这个值越小,删得越狠。保守点就从 2.0 开始往下调。

2.3 直通滤波

直通滤波,说白了就是「切一刀」。在某个坐标轴方向画个范围,范围内的留下,范围外的滚蛋。

这招在什么场景下好用?比如你只关心地面以上 1 米到 2 米之间的物体,那就把 Z 轴范围设成 [1.0, 2.0],其他点全砍掉。简单粗暴,但非常有效。

我个人习惯在做分割之前,先用直通滤波把感兴趣区域(ROI)框出来。这样后面的算法就不用处理那些无关的点,速度能快不少。

// C++ 示例:PCL 直通滤波(Z 轴方向)
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(1.0, 2.0);  // 保留 Z 在 1m~2m 之间的点
pass.filter(*filtered_cloud);

注意:直通滤波可以沿 X、Y、Z 任意轴做,也可以组合使用。比如先切 X 轴,再切 Y 轴,最后切 Z 轴,就能得到一个立方体 ROI。

2.4 半径滤波

半径滤波和统计滤波有点像,但思路更直接:以每个点为中心,画一个半径为 r 的球。如果球里的邻居数少于某个阈值,就认为这个点是离群点,删掉。

你想想看,一个正常的点,周围总该有几个伙伴吧?如果它孤零零的,周围半径 0.1 米内一个点都没有,那它八成有问题。

参数 含义 我的经验值
radius(搜索半径) 画多大的球来找邻居 0.05~0.2m,看点云密度
min_neighbors(最少邻居数) 球内至少要有几个点 3~10,密度大就取大点

我记得有一次做室内场景重建,点云里有很多飘浮的灰尘点。统计滤波搞不定,因为灰尘点分布太散了。换成半径滤波,把半径设小一点,邻居数设成 2,灰尘点全没了。说白了,半径滤波对稀疏噪声更敏感。

// C++ 示例:PCL 半径滤波
pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror;
ror.setInputCloud(cloud);
ror.setRadiusSearch(0.1);      // 搜索半径 0.1m
ror.setMinNeighborsInRadius(5); // 至少要有 5 个邻居
ror.filter(*filtered_cloud);

对比一下:统计滤波看的是「平均距离」,适合均匀分布的噪声;半径滤波看的是「绝对数量」,适合稀疏的孤立点。我一般先用统计滤波,再用半径滤波收尾。

2.5 四种滤波方法对比

说了这么多,咱们来张表总结一下:

方法 主要用途 优点 缺点
体素滤波 降采样 速度快,保留形状 会丢失细节
统计滤波 去噪 对均匀噪声效果好 可能误删边缘点
直通滤波 提取 ROI 简单高效 只能沿坐标轴切
半径滤波 去稀疏噪声 对孤立点敏感 参数调起来麻烦

实际项目中,我通常把这四种方法串起来用:先直通滤波框出 ROI,再体素滤波降采样,然后统计滤波去均匀噪声,最后半径滤波收尾。一套组合拳下来,点云干净又轻量。

点云预处理流程 原始点云 直通滤波 提取 ROI 体素滤波 降采样 统计滤波 去均匀噪声 半径滤波 去稀疏噪声 预处理后 干净点云 实际项目中,这四种滤波方法可以灵活组合使用 顺序:直通滤波 → 体素滤波 → 统计滤波 → 半径滤波

好了,四种滤波方法就聊到这儿。每种方法都有自己的脾气,关键是要理解它们的原理,然后根据你的数据特点去选。别死记参数,多试几次,手感就来了。

专注资料整理