3、直通滤波(PassThrough Filter):原理与适用场景、基于坐标轴的裁剪、多轴联合滤波实战
3.1 直通滤波到底在干什么?
直通滤波,名字听着挺唬人,其实说白了就是「切一刀」。
你想象一下,你面前有一堆散乱的点云数据。这些点分布在三维空间里,有高有低、有远有近。但很多时候,我们只关心某个范围内的点。比如,我只想看前方 5 米到 10 米之间的物体,或者我只想保留高度在 1.5 米以下的点。
直通滤波干的事就是:指定一个坐标轴(比如 X 轴),设定一个范围(比如 5 到 10),然后把这个范围之外的点全部扔掉。
嗯,就是这么简单粗暴。但你别小看它,我在实际项目中,80% 的预处理工作都是靠它完成的。
核心思想: 沿指定坐标轴方向,设定一个区间 [min, max],保留区间内的点,滤除区间外的点。
3.2 适用场景:什么时候该用它?
我个人习惯,拿到点云数据后,第一件事就是做直通滤波。为什么?因为原始数据里「脏东西」太多了。
- 场景一:去除背景远点
比如用激光雷达扫描一个房间,远处的墙壁、窗外的树都会进来。我只关心房间内的物体,那就沿着 Z 轴(深度方向)切一刀,把 10 米以外的点全干掉。 - 场景二:限定工作区域
做机器人导航时,机器人只需要关注前方 3 米内的障碍物。太远的点不仅没用,还会增加计算量。沿 X 轴设个 [0, 3] 的区间,干净利落。 - 场景三:去除地面或天花板
沿 Y 轴(高度方向)裁剪,保留 0.2 米到 1.8 米的点,这样地面和天花板就被去掉了,剩下的就是中间的物体。
说白了,直通滤波就是「空间范围限定器」。你想想看,很多高级算法(比如聚类、分割)都怕噪声,而直通滤波就是最廉价、最有效的去噪手段之一。
我的经验: 直通滤波一定要放在预处理的第一步。先切掉无关区域,后面的算法跑起来会快很多。我曾经在一个项目中,先做直通滤波,点云数量从 30 万降到了 5 万,后续处理时间直接缩短了 80%。
3.3 基于坐标轴的裁剪:单轴滤波实战
我们先看最简单的单轴滤波。假设你有一个点云,每个点包含 (x, y, z) 坐标。你想保留 x 在 0 到 5 之间的点。
代码实现(C++ + PCL 库)是这样的:
// 创建直通滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud); // 输入点云
pass.setFilterFieldName("x"); // 沿 X 轴裁剪
pass.setFilterLimits(0.0, 5.0); // 保留 [0, 5] 区间
// 注意:默认保留区间内的点,也可以取反
// pass.setFilterLimitsNegative(true); // 保留区间外的点
// 执行滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_filtered);
你看,就这么几行代码。但这里有个坑,我踩过好几次。
避坑指南: 我曾经在项目中直接用了默认参数,结果发现滤完后的点云数量没怎么变。后来一查,原来是 setFilterLimitsNegative 默认是 false,也就是保留区间内的点。但我的本意是去掉区间内的点。所以,一定要确认你的逻辑是「保留」还是「剔除」。
另外,setFilterFieldName 可以填 "x"、"y"、"z",也可以填其他字段,比如强度值 "intensity"。没错,直通滤波不只能切坐标,还能切强度!
3.4 多轴联合滤波:这才是实战中的常态
单轴滤波太简单了,实际项目中我们往往需要多轴联合。比如,我想保留一个「长方体」区域内的点:x 在 [0, 5],y 在 [-2, 2],z 在 [0, 3]。
怎么做?很简单,连续调用三次直通滤波就行了。
// 第一次:沿 X 轴
pcl::PassThrough<pcl::PointXYZ> pass_x;
pass_x.setInputCloud(cloud);
pass_x.setFilterFieldName("x");
pass_x.setFilterLimits(0.0, 5.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_x(new pcl::PointCloud<pcl::PointXYZ>);
pass_x.filter(*cloud_x);
// 第二次:沿 Y 轴
pcl::PassThrough<pcl::PointXYZ> pass_y;
pass_y.setInputCloud(cloud_x); // 注意输入是上一次的结果
pass_y.setFilterFieldName("y");
pass_y.setFilterLimits(-2.0, 2.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xy(new pcl::PointCloud<pcl::PointXYZ>);
pass_y.filter(*cloud_xy);
// 第三次:沿 Z 轴
pcl::PassThrough<pcl::PointXYZ> pass_z;
pass_z.setInputCloud(cloud_xy);
pass_z.setFilterFieldName("z");
pass_z.setFilterLimits(0.0, 3.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xyz(new pcl::PointCloud<pcl::PointXYZ>);
pass_z.filter(*cloud_xyz);
这样,cloud_xyz 就是那个长方体区域内的点了。
你可能会问:为什么不一次性设三个条件?嗯,PCL 的 PassThrough 目前不支持多字段同时滤波,所以只能串行处理。不过别担心,串行三次的开销很小,几乎不影响性能。
我的习惯: 我会把多轴滤波封装成一个函数,传入点云和六个边界值(x_min, x_max, y_min, y_max, z_min, z_max),返回滤波后的点云。这样代码复用性高,也容易维护。
3.5 知识体系:直通滤波的核心逻辑
为了让你更直观地理解,我画了一张流程图。它展示了直通滤波从输入到输出的完整过程。
从这张图你可以看到,直通滤波的本质就是一个「空间判断器」。对每个点,检查它在指定轴上的坐标是否落在区间内。是就留着,不是就扔掉。就这么简单。
3.6 多轴联合滤波的另一种思路:条件滤波
如果你觉得串行调用三次太麻烦,PCL 还提供了 ConditionalRemoval 滤波器。它可以一次性设定多个条件。
// 创建条件对象
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
// 添加三个轴的条件
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, 0.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 5.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, -2.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 2.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::GT, 0.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(
new pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::LT, 3.0)));
// 执行条件滤波
pcl::ConditionalRemoval<pcl::PointXYZ> cond_rem;
cond_rem.setCondition(range_cond);
cond_rem.setInputCloud(cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cond(new pcl::PointCloud<pcl::PointXYZ>);
cond_rem.filter(*cloud_cond);
我个人其实更推荐用串行直通滤波。为什么?因为代码更直观,可读性更好。条件滤波虽然一次搞定,但条件多了以后,代码变得又长又乱,调试起来也麻烦。
总结一下: 直通滤波是点云预处理中最基础、最实用的工具。单轴裁剪用于快速去除无关区域,多轴联合用于精确提取感兴趣区域。记住,先做直通滤波,再做其他处理,这个顺序能帮你省下大量时间。
好了,这一章的内容就到这里。直通滤波虽然简单,但用好了,能解决很多实际问题。下一章我们会聊一个更有意思的滤波器——体素滤波,它能把密集的点云「变稀疏」,同时保留形状特征。到时候见。