3. 直通滤波 (PassThrough)
一、直通滤波到底在干什么?
直通滤波,名字听着挺唬人。说白了,就是一刀切。
你想象一下,你面前有一堆点云数据,里面既有近处的花花草草,也有远处的建筑物。你只想要距离你5米到10米之间的那部分数据。怎么办?
直通滤波就是干这个的。它沿着某个坐标轴(比如X轴、Y轴或Z轴),设定一个范围。在这个范围之内的点,留下;范围之外的,全部砍掉。
我个人习惯把直通滤波叫做「范围过滤器」。它不关心点的颜色、强度,只关心点的位置。简单、粗暴,但非常实用。
核心思想: 设定一个维度(如Z轴),设定一个区间 [min, max],保留区间内的点,剔除区间外的点。
二、为什么需要直通滤波?
你可能会问:「直接处理全部点云不行吗?」
行,但效率太低。我举个例子。
有一次我在做道路检测项目,原始点云有上千万个点。里面包含了天空中的飞鸟、远处的山、还有地面上的车辆。如果直接对这些点做聚类或分割,计算量巨大,而且容易误判。
我只需要地面附近2米范围内的点。这时候,一个直通滤波,沿着Z轴一裁,数据量直接减少80%。后续处理速度飞起。
直通滤波的典型应用场景:
- 去除背景噪声: 比如去掉远处的点,只保留感兴趣区域。
- 提取特定高度层: 比如只保留地面以上的点,用于行人检测。
- 数据预处理: 在复杂算法之前,先快速缩小数据范围。
三、原理:其实就一个条件判断
直通滤波的原理,简单到令人发指。
对于点云中的每一个点,检查它在指定维度上的坐标值。如果这个值在设定的最小值和最大值之间,就保留;否则,丢弃。
用数学公式表达就是:
if min ≤ point.dimension ≤ max:
保留该点
else:
丢弃该点
嗯,就这么简单。但为什么它这么有效?因为很多场景下,我们关心的物体确实只存在于某个特定的空间范围内。
比如,自动驾驶中,激光雷达安装在车顶。它扫描到的点,Z轴(高度)方向,地面以下的部分肯定是噪声,车顶以上的部分可能是树枝或桥梁。我们只保留地面到车顶高度之间的点,这就是直通滤波的典型用法。
四、代码实现:手把手教你写
我用的是PCL(Point Cloud Library)的C++版本。当然,Python版本也有,但C++在工业界更常见。我个人推荐初学者先从C++入手,性能更好,理解更深。
4.1 核心代码片段
#include <pcl/filters/passthrough.h>
// 创建直通滤波对象
pcl::PassThrough<pcl::PointXYZ> pass;
// 设置输入点云
pass.setInputCloud(cloud);
// 设置滤波维度:这里选择Z轴
pass.setFilterFieldName("z");
// 设置范围:保留Z轴在0.5米到2.0米之间的点
pass.setFilterLimits(0.5, 2.0);
// 执行滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_filtered);
你看,核心代码就这几行。但有几个细节,我当年踩过坑,必须提醒你。
避坑指南:
- 维度名称必须正确: 如果你用的是PointXYZ,维度名就是"x"、"y"、"z"。如果你用的是PointXYZRGB,维度名不变。但如果你自定义了点类型,一定要确认字段名。
- 范围是闭区间: setFilterLimits(0.5, 2.0) 包含0.5和2.0这两个边界点。如果你不想包含边界,可以设置setFilterLimitsNegative(true)。
- 输入输出指针: 记得cloud_filtered要提前创建,否则会报空指针错误。我曾经因为这个bug调试了半小时。
4.2 完整示例代码
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
int main()
{
// 加载点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("input.pcd", *cloud) == -1)
{
PCL_ERROR("Couldn't read file input.pcd \n");
return -1;
}
std::cout << "原始点云点数: " << cloud->points.size() << std::endl;
// 创建直通滤波对象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.5, 2.0);
// 执行滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_filtered);
std::cout << "滤波后点云点数: " << cloud_filtered->points.size() << std::endl;
// 保存结果
pcl::io::savePCDFileASCII("output.pcd", *cloud_filtered);
return 0;
}
五、参数调优:别让一刀切得太狠
直通滤波的参数很少,就两个:维度名称和范围。但调起来,还是有点门道的。
| 参数 | 说明 | 调优建议 |
|---|---|---|
| setFilterFieldName | 选择滤波的坐标轴 | 根据场景选择。地面场景常用Z轴,水平场景常用X或Y轴。 |
| setFilterLimits(min, max) | 设置保留范围 | 先观察点云分布,再设定范围。不要拍脑袋。 |
| setFilterLimitsNegative | 是否取反 | 默认false。设为true时,保留范围之外的点。 |
调优技巧:
- 先可视化,再调参: 我习惯先用PCL Visualizer或CloudCompare看一眼点云的分布。比如Z轴的范围是多少,然后根据实际需求设定。不要上来就设个0到100,那跟没滤波一样。
- 范围宁宽勿窄: 如果你不确定精确范围,先设宽一点。比如你想保留1米到2米,可以先设0.5到2.5。看看效果,再逐步缩小。太窄了会丢失有用数据。
- 多维度组合使用: 直通滤波可以串联使用。先沿Z轴滤波,再沿X轴滤波。我做过一个项目,先裁掉地面以下的点,再裁掉左右两侧的噪声,效果非常好。
个人经验: 有一次我在做隧道点云处理,隧道内壁的点云非常密集。我原本想用直通滤波去掉隧道顶部的点,结果范围设得太窄,把隧道壁上的关键特征点也去掉了。后来我调整了范围,保留了顶部以下0.5米的缓冲区域,才保住了关键数据。记住,留点余量。
六、实战案例:从道路点云中提取车辆
好了,理论讲完了,代码也写了。咱们来一个真实的案例。
场景: 你有一份道路点云数据,里面包含路面、路边的树木、以及几辆汽车。你的目标是提取出车辆的点云。
思路:
- 第一步: 沿Z轴滤波。车辆的高度一般在0.5米到2.5米之间。路面和地面以下的点,直接去掉。
- 第二步: 沿Y轴滤波(假设Y轴是道路的横向方向)。车辆在道路中间,左右两侧的树木和建筑物,去掉。
- 第三步: 沿X轴滤波(假设X轴是道路的纵向方向)。只保留你感兴趣的路段。
代码实现:
// 第一步:Z轴滤波,提取车辆高度范围
pcl::PassThrough<pcl::PointXYZ> pass_z;
pass_z.setInputCloud(cloud);
pass_z.setFilterFieldName("z");
pass_z.setFilterLimits(0.5, 2.5);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_z(new pcl::PointCloud<pcl::PointXYZ>);
pass_z.filter(*cloud_z);
// 第二步:Y轴滤波,提取道路宽度范围
pcl::PassThrough<pcl::PointXYZ> pass_y;
pass_y.setInputCloud(cloud_z);
pass_y.setFilterFieldName("y");
pass_y.setFilterLimits(-3.0, 3.0); // 假设道路宽6米
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_y(new pcl::PointCloud<pcl::PointXYZ>);
pass_y.filter(*cloud_y);
// 第三步:X轴滤波,提取感兴趣路段
pcl::PassThrough<pcl::PointXYZ> pass_x;
pass_x.setInputCloud(cloud_y);
pass_x.setFilterFieldName("x");
pass_x.setFilterLimits(10.0, 50.0); // 提取10米到50米之间的路段
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass_x.filter(*cloud_filtered);
经过这三步滤波,原始点云从几百万个点,减少到几万个点。而且这些点基本就是车辆和少量残留噪声。后续再用聚类算法,就能轻松提取出每辆车。
效果: 原始点云100万点 → 滤波后3万点。处理时间从5秒降到0.2秒。这就是直通滤波的魅力。
七、知识体系与核心逻辑
为了让你更直观地理解直通滤波在整个点云处理流程中的位置,我画了一张图。
八、总结与避坑
直通滤波,说白了就是点云处理中的「入门级」滤波。但它非常实用,几乎每个项目都会用到。
最后,给你几个避坑建议:
- 不要过度依赖直通滤波: 它只能处理空间范围上的噪声。对于离群点、稀疏噪声,它无能为力。这时候需要配合统计滤波或半径滤波。
- 注意坐标系: 不同传感器,坐标系定义可能不同。有的激光雷达Z轴朝上,有的朝下。滤波前,先确认坐标系方向。
- 多维度滤波时注意顺序: 我一般先做Z轴滤波,因为Z轴方向的数据量通常最大,先裁掉可以大幅减少后续计算量。
我的习惯: 每次做直通滤波前,我都会先用可视化工具看一眼点云的直方图分布。这样设定范围时心里有数,不会盲目乱设。你也不妨试试。