第4章:直通滤波(PassThrough)—— 最直观的点云“剪刀手”
各位好,欢迎来到第四章。
今天聊一个非常基础,但我在项目中几乎天天用的滤波方法——直通滤波。
你想想看,激光雷达扫出来的点云,动不动就几十万甚至上百万个点。但很多时候,我们关心的区域就那么一小块。比如自动驾驶里,我只想看前方50米内的障碍物,远处那些稀疏的噪点留着干嘛?
直通滤波,说白了就是一把“剪刀”。沿着X、Y、Z轴,咔嚓一下,把范围外的点全剪掉。
4.1 原理讲解:一维上的“切分”
直通滤波的原理极其简单,简单到有时候新手会觉得“这也能叫算法?”
它的核心思想是:对点云在指定维度(X、Y或Z轴)上设定一个范围,保留范围内的点,剔除范围外的点。
举个例子:
- 你设定Z轴范围:0.5米 到 2.0米
- 点云中每个点,如果它的Z值在[0.5, 2.0]之间,就留下
- 否则,直接扔掉
就这么简单。没有复杂的数学公式,没有迭代计算。但它的威力,在于组合使用。
核心公式(其实算不上公式):
保留条件:min_limit ≤ point.axis ≤ max_limit
其中 axis ∈ {x, y, z}
我个人习惯,把直通滤波叫做“点云预处理的第一道门”。不管后面要做什么——分割、聚类、识别——先把无关区域切掉,能省下大量计算时间。
4.2 PCL实现:三行代码搞定
在PCL(Point Cloud Library)里,直通滤波的实现封装得非常好。你只需要创建一个滤波器对象,设置参数,然后调用滤波函数。
来看代码:
#include <pcl/filters/pass_through.h>
// 1. 创建滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass;
// 2. 设置输入点云
pass.setInputCloud(cloud_input);
// 3. 设置滤波轴和范围
pass.setFilterFieldName("z"); // 沿Z轴滤波
pass.setFilterLimits(0.5, 2.0); // 保留0.5米到2.0米之间的点
// 4. 执行滤波
pass.filter(*cloud_filtered);
嗯,这里要注意:setFilterLimits的两个参数,第一个是下限,第二个是上限。默认是保留范围内的点。
如果你想反过来——保留范围外的点——可以加一行:
pass.setFilterLimitsNegative(true); // 取反,保留范围外的点
这个功能我在项目中用过一次。当时需要剔除地面附近的点(Z值小于0.3米的),用setFilterLimitsNegative(true)配合Z轴下限,一行代码就搞定了。
4.3 设置滤波范围:X/Y/Z轴的选择策略
很多新手会问:到底该用哪个轴?范围设多少?
我的经验是:先看你的应用场景,再看传感器安装位置。
| 应用场景 | 常用滤波轴 | 典型范围 | 说明 |
|---|---|---|---|
| 自动驾驶(前向) | X轴(前进方向) | 0 ~ 50米 | 剔除后方和过远的点 |
| 机器人导航 | Z轴(高度) | 0.2 ~ 1.8米 | 剔除地面和天花板 |
| 道路边界检测 | Y轴(横向) | -5 ~ 5米 | 只保留车道内的点 |
| 室内建图 | Z轴 | 0.1 ~ 2.5米 | 剔除地面和过高区域 |
我曾经在一个项目中,激光雷达装在车顶,Z轴零点在雷达中心。结果地面反射点特别多,导致后续聚类总是把地面当成障碍物。我直接用Z轴直通滤波,把0.2米以下的点全切掉,问题立刻解决。
小技巧:如果你不确定范围设多少,可以先可视化点云,用鼠标量一下目标区域的范围。PCL的pcl_viewer支持按住Shift键查看点的坐标。
4.4 实战:去除远处噪点
好,理论讲完了,咱们来一个实战案例。
场景:你有一帧户外点云,激光雷达能扫到100米远。但50米以外的点非常稀疏,而且有很多飞点(噪点)。你想保留50米内的点,用于后续的障碍物检测。
步骤:
- 加载点云(假设是PCD文件)
- 创建直通滤波器,沿X轴(假设雷达正前方为X正方向)
- 设置范围:0.5米 ~ 50.0米(0.5米以下可能是雷达自身盲区)
- 执行滤波,保存结果
完整代码:
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/pass_through.h>
int main()
{
// 加载点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("outdoor_scan.pcd", *cloud) == -1)
{
PCL_ERROR("Couldn't read file outdoor_scan.pcd \n");
return -1;
}
std::cout << "原始点云点数: " << cloud->size() << std::endl;
// 创建滤波器
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("x");
pass.setFilterLimits(0.5, 50.0); // 保留0.5米到50米之间的点
// 执行滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_filtered);
std::cout << "滤波后点云点数: " << cloud_filtered->size() << std::endl;
// 保存结果
pcl::io::savePCDFileASCII("outdoor_scan_filtered.pcd", *cloud_filtered);
return 0;
}
注意:直通滤波不会改变点的坐标,它只是“筛选”。如果你发现滤波后点云数量为0,检查一下你的范围设置是否合理。我曾经犯过这个错——把Z轴范围设成了0到0,结果全没了。
运行这段代码,你会发现原始点云可能有10万个点,滤波后只剩3万个。远处那些稀疏的噪点被干净地切掉了。后续做聚类或者分割,速度会快很多。
4.5 知识体系:直通滤波的核心逻辑
为了让你更直观地理解,我画了一张流程图。它展示了直通滤波在整个点云处理流程中的位置,以及它的内部逻辑。
从这张图你可以看到,直通滤波的本质就是一个逐点判断的过程。它不关心点的邻居关系,也不做任何插值。简单、粗暴、有效。
4.6 避坑指南
最后,分享几个我踩过的坑:
- 坐标系搞反了:雷达坐标系中,X轴可能是前进方向,也可能是侧向。一定要先确认。我曾经把X和Y搞反,切了半天发现切错了方向。
- 范围边界太紧:如果你设的边界正好在目标物体的边缘,可能会把有用的点切掉。建议留一点余量,比如目标在10米处,范围设到10.5米。
- 忘了考虑雷达盲区:很多雷达近处有盲区(比如0.3米以内)。如果你把下限设为0,可能会保留一些雷达自身的噪声点。
我的习惯:每次做直通滤波前,先用pcl_viewer看一眼点云,心里大概有个范围。然后先设一个宽松的范围,看效果,再逐步收紧。别一上来就设死。
好了,直通滤波就讲到这里。它虽然简单,但用好了能省下大量时间。下一章我们会聊一个更“聪明”的滤波方法——体素滤波。到时候你会发现,有时候“粗”比“细”更管用。
可可精品资料库