激光雷达原理与点云处理:建图的数据基石

说实话,很多新手一上来就调 SLAM 参数,结果地图建得乱七八糟。为什么?因为连传感器怎么工作的都没搞明白。激光雷达就是 SLAM 的眼睛,你连眼睛的脾气都不了解,怎么可能建出好地图?

这一章,我们就来聊聊激光雷达的工作原理,以及怎么处理它吐出来的原始点云数据。嗯,这部分做好了,后面的建图才能顺风顺水。

激光雷达工作原理:它到底是怎么「看」世界的?

激光雷达,说白了就是「激光 + 雷达」。它发射一束激光,打到物体上反射回来,通过计算时间差来测距。这个原理其实很简单,跟蝙蝠用超声波差不多,只不过激光雷达用的是光速级别的激光。

我当年第一次拆开一个 16 线激光雷达时,看到里面旋转的机械结构,才真正理解它的工作方式。每个激光发射器对应一个「线」,16 线就是 16 个激光器同时工作,每转一圈就能扫出 16 圈的点云。

核心公式:距离 = (光速 × 飞行时间) / 2

光速 ≈ 3×10⁸ m/s,所以时间测量精度直接决定了测距精度。1 纳秒的误差,对应约 15 厘米的距离偏差。

常见的激光雷达类型,我整理了一个表格,方便你对比:

类型 工作原理 典型代表 优缺点
机械旋转式 激光器整体旋转扫描 Velodyne VLP-16 视野广(360°),但有机械磨损
固态(MEMS) 微振镜反射激光 Livox Horizon 寿命长,但视场角较小
固态(Flash) 一次发射覆盖整个视场 LeddarTech 无运动部件,但探测距离有限
混合固态 旋转棱镜 + 固定激光器 RoboSense RS-LiDAR 平衡了寿命和视场角

我的经验:做室内机器人建图,机械旋转式还是最靠谱的选择。虽然它转起来有点吵,但 360° 的视野在 SLAM 里太重要了。固态雷达视野窄,容易丢特征,建图容易崩。

点云滤波:为什么原始数据不能直接用?

激光雷达吐出来的原始点云,其实挺「脏」的。你想想看,激光打到玻璃上会穿透,打到镜子上会反射到别处,下雨天还会被雨滴反射回来。这些异常点如果不处理,建图时就会出现各种鬼影和噪点。

我曾经在一个玻璃幕墙的办公楼里做测试,结果地图上凭空多出了一堵「墙」。后来才发现,那是玻璃反射造成的虚像点。从那以后,我每次建图前都会先做点云滤波。

直通滤波:快速裁剪无用区域

直通滤波,名字听着高大上,其实就是「切一刀」。你设定一个范围,在这个范围之外的点全部扔掉。

举个例子:你的机器人高度是 1 米,那地面以下和天花板以上的点云基本没用。直接切掉,数据量能减少 30% 以上。

// PCL 直通滤波示例
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud_in);
pass.setFilterFieldName("z");        // 沿 Z 轴裁剪
pass.setFilterLimits(0.1, 1.5);      // 保留 0.1m ~ 1.5m 范围
pass.filter(*cloud_filtered);

注意:直通滤波的阈值不能设得太死。我见过有人把 Z 轴下限设成 0.2m,结果机器人爬个小坡,地面点全被切掉了,建图直接失败。留点余量,比如设成 0.05m。

体素滤波:降采样但不丢结构

体素滤波是点云处理里最常用的降采样方法。它的思路很简单:把空间划分成一个个小立方体(体素),每个体素里只保留一个点(通常是重心点)。

这样做的好处是:点云密度均匀了,数据量大幅减少,但物体的几何结构基本保留。我一般会把体素大小设在 0.05m ~ 0.1m 之间,具体看场景。

// PCL 体素滤波示例
pcl::VoxelGrid<pcl::PointXYZ> voxel;
voxel.setInputCloud(cloud_in);
voxel.setLeafSize(0.05f, 0.05f, 0.05f);  // 5cm 体素
voxel.filter(*cloud_filtered);

体素大小怎么选?我总结了一个经验表:

场景 推荐体素大小 说明
室内小房间 0.02m ~ 0.05m 细节多,需要保留特征
室内大空间 0.05m ~ 0.10m 平衡精度和速度
室外空旷 0.10m ~ 0.20m 点云稀疏,降采样力度可以大
实时建图 0.08m ~ 0.15m 优先保证帧率

我的习惯:先设一个较大的体素(比如 0.1m)跑一遍,看看建图效果。如果特征匹配不够,再逐步减小。别一上来就用 0.01m,那跟没滤波一样,CPU 直接拉满。

点云处理流程:从原始数据到可用数据

在实际项目中,我不会只用一种滤波。通常是一个组合拳:先直通滤波去掉无用区域,再体素滤波降采样,最后可能再加一个统计滤波去掉离群点。

下面这张图,是我自己总结的点云预处理流程:

点云预处理流程 原始点云 直通滤波 体素滤波 统计滤波 各步骤作用 ① 直通滤波:去除地面、天花板等无用区域 ② 体素滤波:降采样,减少数据量,均匀化点云密度 ③ 统计滤波:去除离群噪点,平滑点云 最终输出:干净、轻量、结构完整的点云,直接喂给建图算法

统计滤波:揪出那些「不靠谱」的点

统计滤波的原理很有意思:它假设点云中每个点的邻域距离服从高斯分布。如果一个点离它的邻居太远(超过均值 + 若干倍标准差),就判定为离群点,直接扔掉。

我习惯把标准差倍数设成 1.0 ~ 2.0 之间。设得太小,会把正常点也误删;设得太大,离群点又滤不干净。

// PCL 统计滤波示例
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud_in);
sor.setMeanK(50);          // 每个点考虑 50 个邻居
sor.setStddevMulThresh(1.0); // 标准差倍数阈值
sor.filter(*cloud_filtered);

避坑指南:我曾经在一个工厂车间里做测试,环境里有很多金属表面,激光反射特别强,导致点云里出现了大量「飞点」。统计滤波设了 2.0 倍标准差都滤不干净。后来我把 meanK 从 50 调到了 100,才把那些飞点干掉。记住:环境越复杂,meanK 就要越大。

滤波顺序有讲究吗?

有,而且很重要。我的建议是:先直通,再体素,最后统计。

为什么?直通滤波先砍掉大量无用点,体素滤波降采样时计算量就小很多。统计滤波放在最后,是因为体素滤波已经让点云均匀了,统计滤波的效果会更好。

你想想看,如果先做统计滤波,点云里还有几百万个点,每个点都要算 50 个邻居的距离,那计算量得多大?

小结

这一章我们聊了激光雷达的工作原理,以及三种最常用的点云滤波方法。直通滤波负责「切」,体素滤波负责「缩」,统计滤波负责「清」。三者配合,就能把原始点云处理成干净、轻量、结构完整的数据。

嗯,数据基础打好了,下一阶段我们才能放心地把它喂给建图算法。记住一句话:点云处理的质量,直接决定了建图的上限。别在这个环节偷懒。