2. 直通滤波:原理与实现、基于坐标轴的裁剪、在道路场景中的应用
2.1 直通滤波到底在干什么?
直通滤波,名字听着挺唬人,其实原理特别简单。
说白了,就是一刀切。
你想象一下,你面前有一堆散乱的点云数据。这些点分布在三维空间里,有X、Y、Z三个坐标值。直通滤波做的事情就是:指定一个坐标轴,设定一个范围,在这个范围之外的点,全部砍掉。
比如我只想要Z轴在-1米到1米之间的点,那其他高度的点,统统不要。就这么直接。
核心思想: 沿着某个坐标轴方向,设置一个「通过」的区间。区间内的点保留,区间外的点剔除。
我在做车载激光雷达项目时,经常用这招。为什么呢?因为原始点云数据量太大了,动不动几十万个点。直接处理太慢。先用直通滤波把明显没用的区域切掉,后续算法跑起来就快多了。
2.2 原理与实现:代码怎么写?
直通滤波的实现,本质上就是一个循环判断。遍历每个点,检查它的某个坐标值是否在阈值范围内。
我个人习惯用PCL库来做,它已经封装好了。但为了让你理解本质,我先给你看一个最朴素的实现思路:
// 伪代码:直通滤波核心逻辑
function passthrough_filter(point_cloud, axis, min_limit, max_limit):
filtered_cloud = empty_cloud
for each point in point_cloud:
// 根据axis选择对应的坐标值
if axis == 'x':
value = point.x
else if axis == 'y':
value = point.y
else if axis == 'z':
value = point.z
// 判断是否在范围内
if value >= min_limit and value <= max_limit:
filtered_cloud.add(point)
return filtered_cloud
你看,逻辑就这么几行。但实际工程中,有几个坑需要注意。
避坑指南: 我曾经在项目里直接用PCL的直通滤波,没注意坐标系方向。结果把路面点全切掉了,留下了一堆空中飞行的噪点。后来才发现,我的激光雷达坐标系Z轴朝上,但我设的阈值是反的。所以,一定要先搞清楚你的传感器坐标系定义。
下面是PCL库中直通滤波的标准用法:
#include <pcl/filters/passthrough.h>
// 创建点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
// 假设cloud已经加载了数据
// 创建直通滤波对象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud); // 设置输入点云
pass.setFilterFieldName("z"); // 设置滤波轴:z轴
pass.setFilterLimits(-1.5, 0.5); // 设置范围:-1.5米到0.5米
// pass.setFilterLimitsNegative(true); // 可选:取反,保留范围外的点
pass.filter(*cloud_filtered); // 执行滤波
这里有个小技巧:setFilterLimitsNegative这个参数,默认是false。如果设为true,就变成了「保留范围之外的点」。我在做障碍物检测时,有时候需要剔除地面附近的点,就会用这个取反功能。
2.3 基于坐标轴的裁剪:不只是切一刀
直通滤波可以沿着X、Y、Z三个轴分别切。但实际应用中,我们经常需要组合使用。
举个例子。你想想看,一个道路场景,我们通常只关心前方一定范围内的障碍物。太远的点没用,太高的点可能是树冠或天桥,太低的是地面。
所以,常见的做法是:先沿Z轴切掉地面和天空,再沿Y轴切掉左右两侧的建筑物,最后沿X轴切掉太远的点。
这就相当于用三个平面,在三维空间里切出一个长方体区域。这个长方体,就是我们的感兴趣区域(ROI)。
我的经验: 多轴组合滤波时,顺序很重要。我一般先切Z轴,因为地面点和天空点数量最多,先去掉它们能大幅减少后续计算量。然后再切Y轴和X轴。这个顺序不是绝对的,但你可以试试,效果确实不一样。
下面是一个多轴组合滤波的代码示例:
// 第一步:Z轴滤波 - 去掉地面和天空
pcl::PassThrough<pcl::PointXYZ> pass_z;
pass_z.setInputCloud(cloud);
pass_z.setFilterFieldName("z");
pass_z.setFilterLimits(-1.0, 2.5); // 保留高度-1米到2.5米的点
pass_z.filter(*cloud_filtered_z);
// 第二步:Y轴滤波 - 去掉左右两侧
pcl::PassThrough<pcl::PointXYZ> pass_y;
pass_y.setInputCloud(cloud_filtered_z);
pass_y.setFilterFieldName("y");
pass_y.setFilterLimits(-10.0, 10.0); // 保留左右各10米内的点
pass_y.filter(*cloud_filtered_y);
// 第三步:X轴滤波 - 去掉太远的点
pcl::PassThrough<pcl::PointXYZ> pass_x;
pass_x.setInputCloud(cloud_filtered_y);
pass_x.setFilterFieldName("x");
pass_x.setFilterLimits(0.0, 50.0); // 保留前方0到50米的点
pass_x.filter(*cloud_filtered);
2.4 在道路场景中的应用:实战案例
好了,理论讲完了,咱们来看一个真实的道路场景应用。
我记得有一次做城市道路的自动驾驶项目。车辆在城市里跑,周围环境特别复杂。有行人、自行车、其他车辆、路边的树木、路灯杆、还有远处的建筑物。
原始点云数据长这样:
- 总点数:约12万个
- 范围:X轴 -50米到100米,Y轴 -40米到40米,Z轴 -2米到30米
- 包含大量地面点、远处建筑物点、天空中的噪点
直接做障碍物检测?太慢了,而且误检率很高。我的做法是先用直通滤波做预处理。
第一步:确定ROI范围
根据车辆行驶场景,我设定了以下阈值:
| 坐标轴 | 最小值 | 最大值 | 说明 |
|---|---|---|---|
| X(前进方向) | -5米 | 60米 | 车后方5米到前方60米 |
| Y(左右方向) | -15米 | 15米 | 左右各15米,覆盖3-4个车道 |
| Z(高度方向) | -1.5米 | 3.0米 | 地面以下1.5米到车顶以上 |
第二步:执行滤波
按照刚才说的顺序,先Z后Y再X。滤波完成后,点云数量从12万降到了约3万个。减少了75%的数据量。
第三步:效果验证
滤波后的点云,只保留了道路范围内的障碍物点。远处的建筑物、天空中的噪点、大部分地面点都被去掉了。后续的聚类和障碍物检测,速度明显提升,误检率也降低了。
关键数据: 在这个项目中,直通滤波让后续算法的处理时间从120ms降到了35ms,帧率从8fps提升到了28fps。效果非常明显。
2.5 知识体系总览
为了让你更直观地理解本章的内容结构,我画了一张图:
2.6 一些补充建议
最后,再分享几个我在实际项目中积累的小经验:
- 阈值不要设得太死。道路场景中,车辆会有颠簸,激光雷达也会有安装误差。留一点余量,比如Z轴下限设成-1.5米而不是-1.0米,能避免把有用的点切掉。
- 先做直通滤波,再做其他滤波。比如体素滤波(降采样)或统计滤波(去噪点)。直通滤波能快速减少数据量,为后续处理减轻负担。
- 调试时可视化很重要。我每次调参都会把滤波前后的点云显示出来,肉眼看一下效果。光看数字有时候会骗人。
一个小技巧: 如果你不确定阈值该设多少,可以先采集一段数据,用可视化工具看一下点云在三个轴上的分布范围。比如用PCL的CloudViewer,或者直接用Python的open3d。看一眼,心里就有数了。
嗯,直通滤波的内容就这些。它虽然简单,但用好了,能解决很多实际问题。下次你处理点云数据时,不妨先想想:能不能用直通滤波先切一刀?
可可精品资料库