激光雷达点云算法做到后期,你会发现真正卡脖子的往往不是某个模型有多新颖,而是从一帧原始点云到最终可用的障碍物信息,这中间每一步都在跟噪声、延迟和不确定打交道。我最早啃这类算法时,最头疼的不是看不懂论文,而是论文里轻描淡写的一句话,落地时能让你调上两周参数。这篇内容我打算把激光雷达算法的关键链路掰开揉碎讲一遍,从点云预处理、地面分割、聚类识别一路讲到多传感器对齐和工程落地的坑,偏实操,尽量让新手能跟上,也让有基础的人能查漏补缺。
如果你正在做智能驾驶感知、机器人导航,或者准备面试激光雷达算法相关岗位,这篇内容应该能帮你把零散的知识点串成一条线。我尽量不堆公式,但涉及原理的地方会给出必要的计算思路,毕竟只知道“这么调”不知道“为什么这么调”,换个传感器型号你又会懵。
1. 激光雷达点云到底在描述什么
很多人第一次拿到激光雷达点云数据时,第一反应是打开可视化工具看一眼,觉得“哇,好酷”,然后就开始跑模型了。这一步其实跳得太快。你至少要花半天时间搞懂点云的数据结构,否则后面每一个环节都会以莫名其妙的方式报错。
1.1 一帧点云背后是完整的扫描机制
激光雷达的工作原理是发射激光束并接收反射回波,通过飞行时间计算距离。但一帧点云不是同时拍出来的,它是激光头在电机带动下旋转扫描一圈过程中陆续采集到的点的集合。以常见的机械旋转式雷达为例,转速通常是10Hz到20Hz,也就是每帧扫描周期在50毫秒到100毫秒之间。
这意味着一个很关键的事实:在一帧之内,自车已经往前开了很远。如果车速是72km/h,也就是每秒20米,那么100毫秒内车辆移动了2米。放在点云上看,静止的栏杆、路灯都会出现明显拖影。这种畸变对于下游聚类和目标检测是致命的,所以几乎所有正经的自动驾驶系统,在做点云处理之前都会插入一个去畸变环节。
理解了这一点,你才能明白为什么激光雷达点云处理和相机图像处理有本质区别:图像是全局快门或卷帘快门曝光,点云则是逐点扫描的流式数据。每一帧里的每个点都带自己的时间戳,处理时要考虑这个时间维度。
1.2 点云的稀疏性与坐标系约定
激光雷达点云天然是稀疏的。一台64线雷达,一帧大概只有十几万个点,其中还有大量地面点。128线雷达会多一些,但相比相机的几百万像素仍然是数量级的差距。稀疏性带来两个直接后果:远距离目标上的点数锐减,以及对小物体的检出率大幅下降。所以业界常说激光雷达感知是“近处靠点云,远处靠猜测”,本质上就是稀疏性决定的。
另一个容易被忽略的点是坐标系约定。不同厂商的雷达,点云坐标系定义有差异。有的雷达X轴朝前,有的朝左,有的Y轴朝前。你从驱动拿到点云后,第一件事应该是确认坐标系,并把点云统一到自车坐标系。否则后面做ROI裁剪、坐标投影全部会错,而且这种错非常隐蔽,你可能跑完整个pipeline才发现某个方向的目标位置全部偏移。
我在某次移植算法时,因为没仔细看雷达手册的坐标系定义,结果所有目标的X和Y坐标对调了,整个车道级定位全部废掉。排查了整整一个下午,最后发现是坐标系理解错误。这种问题你用可视化工具对着点云和实景看几秒就能发现,但前提是你要有“坐标系可能不统一”这根弦在脑子里。
1.3 点云属性不止是XYZ坐标
除了XYZ坐标,激光雷达点云通常还带有强度值(intensity)和通道号(ring)。强度值反映反射表面的材质特性,白色车牌、金属栏杆、树叶的强度差异很明显。很多算法直接用强度值做特征,比如车道线检测、路沿检测,效果比纯几何特征好得多。
Ring通道号在多线雷达里很有用,它告诉你这个点来自哪条扫描线。地面分割、射线跟随这些经典算法都会用到ring信息。工业级点云处理框架里的很多模块,输入都在说“organized point cloud”,就是利用ring和方位角把点云恢复成类似图像的排列结构。
我一直建议做激光雷达算法的人,不管初期多赶进度,都要花时间把点云的这种组织方式搞清楚。因为后面的地面分割、聚类、目标检测,几乎所有算法都在利用点云的空间结构,而不是把点云简单当成无序点集。
2. 点云预处理:一套行云流水的必备流程
预处理这一步做得好不好,直接决定最终感知结果的质量。很多模型本身效果不差,差在喂进去的数据充满了噪声和畸变。我习惯把预处理分成三条主线:去畸变、地面分割、降采样与ROI裁剪。
2.1 运动畸变校正:必做的第一件事
去畸变的思路并不复杂。你从IMU或组合导航拿到自车在当前帧的线速度、角速度,然后根据每个点的时间戳,把它补到统一坐标系下。经典做法是取扫描中间时刻作为基准时刻,逐点计算相对位移并修正。
假设雷达扫描周期是T,点云里每个点的时间戳是t_i,基准时间取中值T/2。如果这段极短时间内自车做匀加速运动,那么对点P_i的修正量约等于:
delta_P = -v * (t_i - T/2) - 0.5 * a * (t_i - T/2)^2
实际工程里角速度和线速度都来自IMU的积分,会有漂移,所以更稳妥的方案是把IMU和GPS组合解算后的位姿序列做插值,用插值出的位姿去修正每个点。这样即使车辆在转弯,点云畸变也能被较好地校正。
我在一个低速清扫车项目里曾跳过这一步,因为车辆时速只有15公里,一帧内移动才20多厘米,肉眼几乎看不出问题。结果做障碍物测距时,误差始终在30厘米以上,后来才发现是点云畸变导致近处目标位置波动。低速场景同样不能省,只是阈值可以放宽松一些。
2.2 地面分割:算法选型与参数陷阱
地面分割是点云处理里最有门道的环节之一。原因在于,地面点占了点云总量的30%以上,如果不分割掉,后续聚类会把地面和障碍物连成一片,而且地面点对目标检测特征提取也完全没有帮助。
经典的地面分割算法有两类思路。第一类是把空间划分为网格,在每个网格里做平面拟合,RANSAC(随机采样一致性算法)是最常见的工具。它迭代地随机选三个点拟合平面,然后统计内点数,最终保留内点最多的平面作为地面。这个方法在平地上效果很好,参数也直观:迭代次数、点到平面距离阈值。
第二类利用ring信息做射线跟随,从雷达中心向外逐线扫描地面点。这类方法天然对坡道、起伏地形更鲁棒,因为它跟随的是“射线方向”而不是全局平面。近些年很多开源方案都采用这种方式,实际效果也确实更稳。
参数陷阱主要在这里:RANSAC的距离阈值设太小,会把坡道、起伏处的地面漏掉;设太大,又会把低矮路肩、减速带当成地面。我一般把阈值放在0.1米到0.3米之间,再配合地面点法向量的约束一起过滤。另外迭代次数不是越大越好,在点云量很大的情况下,100次迭代已经能逼近最优平面,超过500次纯粹浪费算力。
2.3 降采样与ROI裁剪:影响性能和精度的平衡术
点云降采样最常用的是体素网格滤波。把空间划分成固定大小的立方体,每个立方体内取一个代表点。体素大小是个核心参数:取0.2米时,一帧16线雷达的点云可以从3万点降到1万点级别,聚类的速度会快很多;但体素太大会丢失细长物体,比如电线杆、行人的手臂,这些恰恰是感知最不能丢的目标。
我的经验是,体素大小控制在0.1米到0.2米之间,对于乘用车场景比较合适。如果你做的是矿区或者港口这种超大尺度场景,体素可以适当放大,否则远处点云根本不够算。
ROI裁剪也要注意。不是所有点都值得处理,自车后方、超高处的点基本可以忽略。把ROI设置成前方150米、左右各30米、高度略高于车顶,能过滤掉50%以上的无关点。有人会担心ROI太小漏掉侧向来车,所以左右宽度不要压缩太狠,至少保留到相邻车道外缘。
预处理这套流程做完,点云会干净很多。我经常跟同事说,感知模块的效果不是靠模型撑起来的,预处理把垃圾扔干净了,下游哪怕用最传统的聚类也能跑得很好。
3. 目标提取与识别:从聚类到深度学习的选型逻辑
预处理之后的点云,是滤除地面、压缩过噪声的“干净点云”。接下来要回答一个问题:哪些点属于同一个物体。这一步有两种主流路线,传统几何聚类和基于深度学习的方案。选型的逻辑不是技术越新越好,而是看你的场景约束和算力条件。
3.1 传统聚类:欧式聚类和DBSCAN怎么选
欧式聚类逻辑很简单:如果两个点在欧氏距离上小于阈值,就归为一个簇。它的关键在于距离阈值怎么定。阈值太大,会把并排停着的两辆车连成一个簇;阈值太小,一辆长货车中间的点会断成好几截。
我试过一套经验值:近处用0.3米,远处可以放宽到0.8米。因为激光雷达的角分辨率固定,距离越远,相邻点的物理间距越大。如果全局用同一个阈值,远处目标会过度分割。
DBSCAN可以看作欧式聚类的进阶版,它能识别噪点,不需要预设簇的数量,而且通过密度可达关系能处理形状不规则的目标。但如果点云密度分布极不均匀,DBSCAN的全局eps参数也会很难调。我在点云密度波动比较大的场景下,更倾向于把区域切分后分别做聚类。
聚类之后还需要做后处理:计算每个簇的最小包围盒、中心点、速度(多帧关联后)。有了这些,你才能给下游规划模块输出结构化结果。传统聚类方案的优点是完全可解释,每个参数的物理意义都能说清楚,也很容易调试。缺点是对稀疏远处目标几乎没有感知能力。
3.2 深度学习方案:PointPillars为什么是默认选择
在车载场景里,PointPillars几乎是落地最多的点云3D检测方案。它的核心思路是把点云沿着俯视图切分成柱体,每个柱体内提取特征,再通过2D卷积处理,最后输出3D检测框。
PointPillars的优势在于快。它不需要像VoxelNet那样做复杂的三维稀疏卷积,整个网络可以跑到几十毫秒级别,配合嵌入式芯片也能勉强实时的。而基于原始点的PointNet++系列虽然理论上精度更高,但点云量大的时候参与计算的点数无法控制,延迟很容易超标。
我用PointPillars踩过的最大的坑是体素尺寸Pillar Size。论文默认是宽0.16米、长0.16米,在KITTI数据集上表现不错,但如果你换了雷达,点云密度变化,这个参数需要重新标定。体素太小,每个体素里的点太少,特征稀疏;体素太大,两个相近目标会被分进同一个柱体,检测框就会粘连。
另外,深度学习检测框的朝向预测往往不稳定,尤其是面对卡车这种接近对称形状的目标。解决方案是给Loss加一个朝向惩罚项,或者在后处理里加一层基于点云主方向的反向校验。我强烈建议不管用什么检测模型,输出后都保留原始点云簇的准星和尺寸信息,用来做框的一级修正,这个便宜且有效。
3.3 目标检测和聚类的互补关系
很多刚入行的朋友会问:“我都上深度学习了,为什么还要保留传统聚类的结果?”这个问题特别好。实际路测中你会发现,深度学习检测对训练集里没见过的目标非常容易漏检,比如异形施工机械、临时堆放的路障。传统聚类没有语义概念,它只看几何紧密度,反而能稳定框住这些未知物体。
所以严肃的自动驾驶系统里,往往是两条腿走路:聚类负责把几何上紧凑的点云物体全部找出来,深度学习负责给这些物体打标签、做精细框。聚类漏掉的稀疏远距离目标,交给深度学习补上。两边的结果合流后做融合逻辑,最终输出统一的目标列表。
这种异构融合方案的好处是冗余度高。某天模型突然退化或者遇到极端场景,至少几何聚类还能兜底。我在一次雨夜路测中,模型几乎全程失灵,全靠聚类结果撑着没出安全问题。这件事情之后,我再也不迷信任何单一的感知手段。
4. 多传感器对齐:激光雷达不应当是孤岛
激光雷达算法做得越深,越会发现传感器融合不可避免。相机给你纹理和颜色,雷达给你深度和几何,导航给你姿态参考。但多套传感器放在车上,如果不做时空对齐,数据就是各说各话。
4.1 时间同步:没有同步,所有对齐都是空谈
时间同步可以说是多传感器融合里最容易翻车的环节。简单地给多套传感器打同一个“帧开始时间”远远不够。相机图像是瞬间曝光,但激光雷达一帧扫描跨几十毫秒,IMU的输出频率又可能到几百赫兹,这些数据要到统一时间坐标系才有意义。
工程上常用的方案分两层。第一层是硬件上的脉冲同步或PTP(精确时间协议)授时,让各传感器的时钟基准一致,误差控制在微秒级别。第二层是软件上的时间戳插值,比如你想知道相机曝光时刻雷达的位置和姿态,就把IMU输出的位姿序列做线性插值或者更高阶的样条插值。
我见过太多项目在时间同步上凑合,最后做联合标定时投影误差飘忽不定。时间同步的投入产出比非常高,优先级应该排在调模型之前。你甚至可以先不做复杂的同步,但至少要让每个传感器都使用同一台授时设备的时间。
4.2 外参标定:激光雷达到相机的投影为什么总对不上
外参标定是雷达和相机坐标系的刚性变换矩阵。标定的常规做法是,在车前放一块标定板,提取点云中的标定板平面和图像中的标定板角点,通过多帧观测联合优化出R和T。这个流程本身不复杂,难在初始值的确定和后续保持。
标定板平面提取要在点云里先找到标定板的准确位置,如果雷达线束不够密,板面上的点数太少,拟合出的平面方向就会偏差很大。一个常见技巧是标定板要放得足够近、足够大,让点云在它上面至少扫过几十条线,否则算出来的外参大概率偏一点,投影就会歪。
外参标定不是标一次就一劳永逸的。车辆长期振动、螺丝松动都会让外参漂移。所以量产车上通常会有在线标定的持续监控,一旦发现投影误差超过阈值,就触发重新标定或者提示维保。我在自研系统里做了一个简单的验证流程:每隔一段时间让车辆绕着一个已知尺寸的立方体转一圈,用投影后的包围框误差来评估外参是否还健康。
这里补充一个投影公式:点云中的点P_lidar,要先通过外参变换到相机坐标系,P_cam = R * P_lidar + t,再用相机的内参矩阵K投影到像素平面,[u, v, 1]^T = K * P_cam。注意P_cam必须除以Z分量才能得到像素坐标,这个Z分量其实也有物理意义,它就是该目标离相机的深度。
4.3 投影融合实操:点云给相机补偿深度和距离
相机和雷达融合最常见的一个应用是图像点云深度融合:为图像中的每个像素补上一个深度距离。有了这一步,下游感知模块就可以在图像上做检测后直接映射到三维空间。
实操里有一个容易被忽略的坑:激光雷达点云稀疏,不能保证每个像素都有深度值。常用的解决方案是深度插值和深度补全。深度插值在近处可行,但在物体边缘容易“越界”,把背景的深度插到前景物体上。更稳的做法是对图像分割后逐物体做平面拟合,再为物体内像素赋值。
投影之后还要做遮挡处理。雷达点云被前方车辆遮挡时,后方目标的点云会消失,这是物理限制,算法无法破解。但可以通过多帧融合、存储短暂的历史点云来恢复部分被遮挡信息。这一招在城市路口特别有用,能减少鬼影目标的出现频率。
写到这里我不禁想强调:多传感器融合不是把多种感知结果简单堆到一起,而是让每个传感器发挥它不可替代的优势,再在更高层做互相校验。激光雷达的“孤岛”效应一旦被打破,整个感知系统会变得更稳固。
5. 工程落地中的坑与排查实录
算法在笔记本上跑通只是第一步,搬到车上、在真实道路上长期跑才是真正的考验。这一节我总结几个高频问题,每个都是我亲手排查过的,参数和思路都偏保守,适合直接参考。
5.1 高频问题速查表
我整理了一张问题速查表,基本覆盖了从点云预处理到目标输出的常见坑。表格写起来简单,背后的定位过程往往要耗掉半天时间,但收益是之后你遇到同类型问题,能秒级定位。
| 问题现象 | 可能原因 | 优先排查思路 | 常用对策 |
|---|---|---|---|
| 点云中地面目标有前后拖影 | 运动畸变未校正 | 检查是否开启IMU去畸变 | 引入IMU位姿插值修正 |
| 远处目标被过度分割 | 聚类阈值太小 | 查看阈值随距离衰减曲线 | 采用距离自适应阈值 |
| 近处目标粘连成一个簇 | 聚类阈值太大或地面未滤净 | 可视化簇间点云密度 | 收紧阈值,调整地面阈值 |
| 检测框朝向反复跳动 | 模型朝向回归不稳定 | 检查Loss是否包含朝向惩罚 | 添加方向损失,后处理主方向校验 |
| 相机和雷达投影错位 | 外参漂移或标定不准确 | 检查标定板投影误差 | 重新标定,增加在线校验 |
| 下雨天点云噪声激增 | 雨滴造成噪点回波 | 观察噪声点的强度特征 | 开启针对小雨滴的噪点滤除 |
| 高架桥下点云大面积缺失 | 信号被遮挡 | 确认物理覆盖盲区 | 多帧叠加,相机语义补全 |
5.2 一个雨夜实测的复盘
有一次雨夜测试,车辆刚上高架,感知模块输出的障碍物数量突然暴增,大量目标在每一帧之间跳变。一开始我怀疑是聚类阈值受雨滴噪点影响,翻了一遍参数发现阈值没变。后来用可视化工具按帧播放,才发现罪魁祸首是远处车辆的尾灯反光,在地上形成了一大片强反射噪点,这些点在地面分割时没有被滤除,因为它们的强度值和地面点差异太大。
解决办法分两路:一是在地面分割的阶段,对强度和高度同时做约束,地面点虽然高度低,但强度上不能出现异常亮斑;二是加入一个简单的噪点过滤器,对强度异常高的孤立点直接剔除。这个案例说明,算法的每个模块都不是孤立的,地面分割的失效会引爆全链路的问题。
5.3 资源受限场景下的优化经验
嵌入式芯片上的算力是宝贵的。点云处理模块越早过滤掉无效点,后续算法跑得越快。我习惯把整个pipeline做逻辑分层:ROI裁剪一定放到最前面,其次是地面分割,然后是降采样,最后才进入聚类的计算密集区。
聚类阶段还有一个很有效的优化:把点云按网格索引进树结构,近邻搜索直接查索引,而不是用暴力遍历。在处理一万个点级别的簇时,这一步能让聚类耗时从几十毫秒压到几个毫秒。
另一个我屡试不爽的思路是降低点云更新的频率。对高线数雷达,可以抽帧处理,比如每两帧做一次完整的目标检测,中间帧只做目标追踪和预测。这样感知延迟增加不到一帧时间,但算力压力减半。
我最后的建议是,做激光雷达算法一定要建立自己的可视化调试工具链。点云是三维数据,光靠打印坐标数值很难发现空间关系的问题。哪怕只是用纯Python写一个简单的俯视图投影工具,也能帮你省下大把排查时间。
这套链路从点云理解、预处理、目标提取、多传感器对齐到工程落地,每一步都有独立的技巧和陷阱,但也有共通的原则:让数据干净、让时间对齐、让特征可解释。你把这几个原则内化了,换任何传感器的雷达算法工作都不至于从零开始。