激光雷达+语义分割:动态障碍物祛除的数据链路全解
2026/9/15 7:54:06 网站建设 项目流程

移动机器人和自动驾驶领域做高精地图、静态建图或者多帧点云配准的朋友,应该都对“动态障碍物”这几个字有切肤之痛。去年我在做园区低速无人车的建图方案时,激光雷达扫描到的行人、自行车、过往车辆,直接让原本干净的路沿和墙面变成了半透明的“鬼影”,体素地图切出来全是拖出来的轨迹残影。后来换了一套“视觉语义分割引导激光点动态障碍物祛除”的流程,效果立竿见影。作为这个系列的第一篇,我先把数据侧的东西掰开揉碎讲清楚:传感器怎么布、标定怎么做、训练数据怎么攒、点云怎么和图像像素对齐,以及这一路上的坑在哪里。

这篇内容适合正在做多传感器融合感知、机器人建图、以及SLAM前后端优化的工程师。如果你是刚入门的学生,也能从中拿到一套可以照着配的数据处理基线。整个方案不需要多么昂贵的计算平台,核心是数据链路要对、时间同步要准、分割模型类别要覆盖你要祛除的动态物体,最后把“图像Mask”变成“点云Filter”这一步才算真正闭环。

1. 项目思路拆解:视觉语义分割到底怎么帮激光雷达“祛魅”

1.1 动态障碍物为什么必须祛除

先说一个容易被经验不足的开发者低估的事实:动态障碍物对建图的污染不是简单叠加几条杂线,而是会把错误信息扩散到后端。

我见过不少团队一开始觉得“只要点云够密、配准够多帧,动态点抹掉就行”。实际情况是,动态目标本身带有轮廓和反射强度,这些点在帧间匹配时会被当成可靠的几何约束,参与位姿图优化。于是建图轨迹被“拉着走”,墙被拉歪,柱子被拉斜,最后整个地图出现局部扭曲,这个时候你再去抱怨前端里程计精度低,其实已经入了动态点干扰的局。

动态障碍物祛除,望文生义就是把这些动态目标对应的激光点从点云中剔除,让后续的建图、定位、导航处理的都是“静态世界”信息。祛除的方式有很多种,按依赖信息分大致三类:

  • 纯几何方法:比如前后帧点云差分、占用栅格变化检测,依赖物体确实在动,并且场景没有太多重复结构。
  • 多视角几何+极线约束:比如DynaSLAM里的多视图一致性做法,利用相机帧间的重投影误差判断动态区域,但对光照变化和重复纹理比较敏感。
  • 语义引导方法:先用视觉语义分割把图像上的行人、车、动物等动态类别像素标出来,再将激光点投影到图像上打上语义标签,凡是落进动态类别的激光点直接剔除。

第三种方案的优势在于“一次训练,到处剔除”,不用管障碍物今天走得快还是慢、是直行还是转弯,语义模型直接告诉你这是不是动态类别。它天然契合我要做的视觉+激光雷达融合平台,所以最终选它作为主线。

1.2 视觉语义分割这条线路的取舍

视觉语义分割负责回答“图像里的每个像素属于哪个类别”,激光雷达则负责回答“空间里每个点的精确坐标”。两者结合的本质,是把图像的高层语义抽象和激光雷达的精确几何测量焊在一起。

选择这条路线,首先是精度层面的考虑。纯几何差分在车、行人密集的主干道场景非常容易失效,因为目标一直在可视范围内,几乎没有“背景帧”可以对比;而语义分割只要识别出这辆车,就能直接把它对应的像素全部屏蔽。其次是部署层面的考虑,现在稍好一点的工控机或者Jetson板子,跑一个轻量级分割网络的实时推理性能已经足够,不需要外接昂贵的GPU。

当然代价也很明显。分割模型需要提前训练,并且数据标注成本不低;图像受光照、雨雾影响时分割精度会波动;相机和激光雷达标定误差或者时间同步误差,会让点云和图像像素错位,出现“该删的没删、不该删的被删”的尴尬局面。因此,真正做好这件事,60%的精力要花在数据和标定上,剩下40%才轮得到网络和策略。

1.3 数据篇要啃的三块硬骨头

既然标题叫“数据篇”,那这篇的核心任务非常明确——为后续的模型训练、点云投影和动态剔除打好数据基础。我把整个数据链路拆成三块,缺一不可。

第一块是传感器数据本身的质量:相机图像、激光雷达点云、惯性测量单元的数据都要有精确的时间戳和空间外参,否则后续所有投影都是白算。第二块是语义分割模型的监督数据:要么选好公开数据集做预训练和微调,要么自己采集数据做标注,这里涉及类别定义、标注规范和工具选型。第三块是投影数据对的构造与验证数据:也就是激光点和图像像素的对应关系、深度缓冲区、可视化校验结果,这步决定动态点剔除命令下得准不准。

下面每一节我按这个顺序展开,直接写当时验证过的参数和处理细节。

2. 数据采集与传感器同步:数据质量的地基

2.1 传感器选型与坐标系关系

我用的平台是一台园区巡检车,传感器布局很简单:一个前视工业相机装在挡风玻璃内侧偏上位置,一个16线机械式激光雷达装在车顶前方,另外有一个高精度组合导航提供姿态基准。相机和雷达之间的空间关系通过联合标定得到。

这里提醒一句:传感器选型会直接影响你后续标定的难度。我当时选的是分辨率1920×1080、全局快门的相机,低照度表现尚可;激光雷达是16线的,虽然密度比64线、128线低不少,但用于城市园区场景的建图足够。选全局快门主要是为了减少车辆颠簸或高速运动时图像行间畸变——卷帘快门在车运动时会把静止物体拍成斜的,投影到点云上就是一层系统误差。

坐标系上至少有四套需要理清:激光雷达坐标系、相机坐标系、图像像素坐标系、车体/组合导航坐标系。日常处理时,我习惯先把所有传感器外参统一到车体坐标系下,再单独维护一个“雷达→相机”的变换矩阵。这样后续加IMU、加轮速计都方便。这里不放全量矩阵推导,但你在做数据同步前,必须把下面这组关系写进配置文件里并反复确认:

  • T_base_lidar:雷达坐标系到车体坐标系外参
  • T_base_cam:相机坐标系到车体坐标系外参
  • T_cam_lidar = T_base_cam^{-1} * T_base_lidar:雷达坐标系到相机坐标系的变换,也就是投影用的外参矩阵

2.2 联合标定实操要点

雷达和相机联合标定,数据采集环节的核心目标是拿到足够多、分布足够均匀的标定板观测。我当时用的是带有黑白棋盘格的平面标定板,面积建议不小于1米×1米,这尺寸在16线雷达下能有十几条扫描线打在板上,角点拟合才不容易飞。

实际操作我分成五步:

  1. 固定车辆,打开数据采集程序,同时录制图像、点云和IMU原始数据。
  2. 把标定板放在车前方不同距离、不同俯仰和侧倾角度,保证板子在图像里完整可见,同时雷达扫描线能覆盖板面的大部分区域。
  3. 每换一个位姿,保持整车静止3到5秒,便于抽取稳定帧。
  4. 采集大约15到20个位姿,然后离线处理:用OpenCV检测棋盘格角点,再从点云中拟合标定板平面,通过PnP求解初始外参。
  5. 用非线性优化对外参做精调,目标函数是“标定板平面上的角点三维坐标,投影到图像后与图像检测角点之间的重投影误差最小”。

我在这一步踩过的最大坑是:只放远距离板子,导致角度约束不足,外参在Y轴方向有零点几度的偏差。后来改成近处、中距离、远处各放几组,并把标定板稍微旋转一个角度再采一组,最终重投影误差从4个像素左右降到了1.5像素以内。对16线雷达来说,这个精度已经能让远距离的点云投影到图像上基本贴合物体边缘。

如果你不想自己写标定流程,现在开源工具也很成熟,比如Autoware的Calibration Tools、lidar_camera_calibration这类ROS包,基本流程都是采数据、提特征、优化求解。区别在于开源工具对位姿数量和质量的要求更高,我建议把“多距离、多角度、板面完整可见”作为铁律。

2.3 时间同步:比标定更容易被忽略的误差源

空间外参对齐之后,最影响数据质量的就是时间同步。相机和雷达如果各用各的时钟,哪怕只差50毫秒,在车速30km/h的情况下,同一个物体在图像和点云里就会错开零点四米左右。这个错位直接导致你分割出的“车”的像素Mask,和雷达扫描到的“车”的点完全不重合,祛除逻辑根本没法用。

我用的方案是硬件同步为主:组合导航输出PPS脉冲和GPRMC时间戳,相机采用外部触发模式,激光雷达也接收PPS同步信号,所有传感器都以同一时刻为基准。整条采集链路里,每个传感器消息都打上主控统一维护的时间戳,ROS的message_filters再用ApproximateTime同步策略做近邻匹配。

如果你的设备不支持外部触发,退而求其次的软件同步也能做,但必须额外加一个“时间戳插值”步骤。做法是在处理点云时,用当前图像时间戳附近两帧雷达数据,按时间比例线性插值出关键帧的点云位姿。这个方案精度逊于硬件同步,但至少能消除明显的时间跳变。

判断同步是否正常的办法很朴素:把车辆停在路边,让一个行人贴着车侧走过,同步录制图像和点云,然后在可视化窗口里同时播放。如果行人脚底的位置始终压在他头顶那几根扫描线上,说明同步基本没问题;如果人的图像和点云一前一后“跟着游”,那就是时间戳偏差大了。

3. 语义分割模型的训练数据从哪来

3.1 公开数据集怎么选、怎么用

动态障碍物祛除需要分割模型认出来的类别,主要是行人、自行车/摩托车、小汽车、卡车、公交车,以及可能出现的动物或婴儿车。如果纯靠自采数据从零训练,成本太高。合理路径是:用公开数据集预训练,再用自采数据微调。

做得比较顺的公开数据集方案如下表:

数据集传感器特点标注内容是否适合预训练
Cityscapes车载相机,城市道路为主像素级语义分割,19类适合,分辨率高、标注质量高
KITTI相机+激光雷达目标检测、分割等,视觉分割标注较粗适合做迁移验证,且自带点云
BDD100K车载相机,道路多样语义分割、可驾驶区域等适合补充夜间/雨天场景
Mapillary Vistas全球各地街景像素级分割,类别丰富分类很全,但部分类别粒度偏细
SemanticKITTI激光雷达点云语义标注点云逐点类别不能直接训练图像分割,但可用于点云先验/验证

我自己的经验是:先拿Cityscapes训练一个基础模型,再把目标场景的图片拉进去做半精度微调。公开数据集里的场景和你的园区环境差异越大,微调的重要性越高。比如Cityscapes里基本没有园区常见的锥桶、地锁、机械臂,如果不额外补样本,模型很可能把它们当成背景,结果全被保留成了“静态障碍物”。

3.2 自采数据标注规范与工具

自采数据标注是数据篇里体力活最多的一环,也是决定分割模型上限的一环。如果不打算雇标注团队,那务必要制定一套精简的类别清单,别一上来就学Cityscapes搞19类。你要做的只是区分“动态”和“静态”,所以类别建议控制在6到8个:

  • 背景/道路/建筑/植被等静态类别
  • 行人
  • 两轮车(含自行车、电动车、摩托车)
  • 小汽车
  • 卡车/大客车
  • 其他动态物(如动物、移动锥桶,可选)

这样标注员(或者你自己)需要判断的语义范围很窄,标注速度会快很多,模型的类间混淆也小。标注工具我用过LabelMe、CVAT,还有国内常用的精灵标注助手。个人最推荐CVAT,因为它支持在线协同、自动分割预标注、关键帧插值,对连续视频帧做语义分割标注能省一半时间。预标注功能特别适合我们这个场景:先用已有的公开模型对视频帧跑一遍伪Mask,再把明显错误的类别修正掉。

标注规范有几条要写进文档里,防止前后标准不一致:

  • 被遮挡超过50%的目标可以不标,避免模型被极不完整的轮廓误导。
  • 车窗玻璃、透明雨伞这类带通透性的物体,语义标签应该覆盖整个可见区域,包括透明部分,因为激光雷达很可能打到透过玻璃的内部结构。
  • 动态目标离开画面边缘时,只标画面内可见部分,不要脑补轮廓。
  • 雨天、逆光、夜间帧单独保存并标注,作为困难样本补充训练。

3.3 类别定义直接影响动态祛除上限

很多人忽略一个点:动态障碍物祛除的上限,其实在类别定义时就定死了。如果类别清单里没有“卡车”,那就算分割模型能完美区分所有像素,卡车对应的激光点也依然会留在点云里。这一点和“清理”很像:扫把扫得再快,如果垃圾筒没对准,效果也是零。

所以我在设计类别时,会把“车辆”拆成小汽车、卡车/大客车、两轮车,而不是全合并成“车”。拆开的好处是,后处理里可以针对不同类别做不同策略。比如小汽车在停车场里经常处于静止状态,静态车对建图来说是有效特征;但巡检场景里路上跑的车基本都是动态,直接全删问题不大。这时候如果只有一个“车”的类别,你就没法区分“停着的车”和“开着的车”,只能连静态车一起删,损失地图特征。

针对这类问题,我的做法是在语义Mask的基础上叠加一个“运动先验”:连续帧中同一语义实例的位置变化超过阈值,才认定它是动态物体,否则保留其点。这样语义分割只负责“认出这是车”,运动状态由多帧几何信息判定,两者配合既不会误删路边停靠车辆,又能把真正跑动的车祛除干净。这个逻辑放到了后面的数据预处理管线里,效果比单纯删除所有“车”类像素要好得多。

4. 激光点与图像像素对齐的完整实现

4.1 三维激光点投影到二维图像的公式推导

数据链路从“图像分割”到“点云剔除”,中间最关键的环节就是把三维激光点投影到二维图像上。这个投影本质上是坐标系的连环变换:雷达坐标系→相机坐标系→图像坐标系→像素坐标系。

第一步,把雷达点从雷达坐标系变换到相机坐标系:

P_cam = T_cam_lidar * P_lidar

其中P_lidar是齐次坐标形式的三维点,T_cam_lidar是4×4外参矩阵,包含旋转和平移。这一步做完,所有点都处于“以相机光心为原点,Z轴指向前方”的相机坐标系下。

第二步,用相机内参矩阵K做透视投影。设K为:

K = [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]

那么像素坐标(u, v)满足:

u = fx * X_cam / Z_cam + cx v = fy * Y_cam / Z_cam + cy

注意这里一定要判断Z_cam是否大于0。Z_cam小于等于0的点位于相机后方,投影出来会产生镜像,必须过滤掉。

4.2 投影代码与深度缓冲过滤

投影本身不难,难在投影之后怎么处理遮挡。如果直接把所有雷达点都投到图像上画Mask,会出现一个严重问题:车道后方被前车挡住的地面点,也会投影到前车的像素范围里,因为雷达和相机视角存在视差。这时候如果按照“投影像素落在动态Mask里就删点”的粗暴逻辑,你会把一面很远的背景墙当成动态点给删了。

解决办法是加一个深度缓冲区,也就是“Z-buffer”。对所有投影到同一像素位置的激光点,只保留距离相机最近的点的语义标签,其余点视为被遮挡,不参与判断。从代码层面看,大概是这样:

import numpy as np # points: (N, 3) 雷达坐标系坐标 # T_cam_lidar: (4, 4) 外参,雷达坐标系 -> 相机坐标系 # K: (3, 3) 相机内参 def project_points_to_image(points, T_cam_lidar, K, img_size): H, W = img_size n = points.shape[0] ones = np.ones((n, 1)) pts_lidar_h = np.hstack([points, ones]) # (N, 4) pts_cam = (T_cam_lidar @ pts_lidar_h.T).T # (N, 4) X = pts_cam[:, 0] Y = pts_cam[:, 1] Z = pts_cam[:, 2] valid = Z > 0.1 # 过滤相机后方和过近点 X, Y, Z = X[valid], Y[valid], Z[valid] u = K[0, 0] * X / Z + K[0, 2] v = K[1, 1] * Y / Z + K[1, 2] u = np.round(u).astype(int) v = np.round(v).astype(int) # 初始化深度图和索引图 depth_buffer = np.full((H, W), np.inf) index_buffer = np.full((H, W), -1, dtype=int) for i in range(len(u)): if 0 <= u[i] < W and 0 <= v[i] < H: if Z[i] < depth_buffer[v[i], u[i]]: depth_buffer[v[i], u[i]] = Z[i] index_buffer[v[i], u[i]] = i return u, v, valid, index_buffer, depth_buffer

有了Z-buffer之后,每个像素位置只会保留“从相机看过去最近的那个激光点”,然后再判断这个点对应的语义类别,就基本不会出现“前方动态车辆背后的地面点被误删”的错判了。这一步虽然简单,但很多人第一次实现时都会漏掉,建议直接写进代码里当固定流程。

4.3 动态类别筛选与点云剔除策略

图像分割模型输出的是一个逐像素类别ID图,我们把投影后落在各个像素上的激光点取出来,再按照类别ID查表,就能判断该点是不是动态点。类别ID到“是否动态”的映射表,我单列了一个JSON配置,方便随时调:

{ "static_classes": [0, 1, 2, 3, 4], "dynamic_classes": [5, 6, 7, 8] }

实际筛选时,我的策略不是“动态类全删”,而是“动态类点删除,但保留两类例外”。第一类是距离过远的点,当激光点距离超过60米时,图像分割模型在该区域的精度已经很低,而且远处的动态障碍物对建图影响有限,删不删无所谓,我选择直接保留,避免远距离误删。第二类是带有强烈反射强度的点,因为雷达打到车辆牌照、金属护栏时,反射强度值会异常高,这类点在后续配准中往往是很好的特征,如果它不在动态Mask核心区域,我会保留。

剔除后的点云再送入后续建图模块前,我还会做一次统计滤波,把孤立点去掉。因为动态目标边缘和背景之间,总会残留一些“半动态点”,它们可能是分割边界模糊导致的误判,也可能是部分遮挡时的残段。统计滤波对这类点有很好的平滑作用,能让建图前端看到干净得多的输入。

5. 数据质量评估与常见问题排查

5.1 标定误差造成“点影分离”怎么发现

标定误差在使用阶段非常隐蔽,因为你不容易直接看到“错多少像素”。最直观的验证方法是把雷达点按深度着色后叠加到图像上,肉眼判断物体边缘处点云是否贴合。行人站在车前2米时,头部点云应该正好落在图像行人的头部;车侧面的点云应该贴合车身腰线,而不是漂浮在半空或嵌进车里。

如果发现点云整体像图像某个方向偏移,多半是平移外参不准;点云在图像中心区域贴合、边缘区域发散,多半是旋转外参不准;点云随距离增大越来越偏,基本可以确定是标定数据采集时姿态约束不够。这类问题只能回到2.2节重新标定,别指望在后处理里硬调。

5.2 时间戳漂移导致的拖影现象

时间不同步的典型表现是“拖影”:行人在图像里的位置已经变化了,雷达点却还停留在几十毫秒前的位置,于是行人点云在图像上形成一串拖出来的尾巴。这个现象在低速场景下不明显,车辆或者行人动作一快就非常扎眼。

排查思路是观察多组数据:如果车辆静止时没有任何拖影,车辆行进时拖影变重,那基本是各传感器时钟漂移而非标定问题。解决手段优先修复硬件同步;实在不行就在软件层给相机帧时间戳加一个固定延迟,比如补偿20毫秒,然后重新看拖影是否消失,反复迭代到一个最小值。

5.3 分割模型误检漏检的影响与兜底

分割模型不可能100%正确,误检和漏检都会传导到点云剔除结果上。漏检好理解:没把动态目标像素分割出来,对应激光点就不会被删,动态残影留在点云里;误检则更危险,模型把背景当成动态目标,墙面和地面点被大片删除,地图直接出现“空洞”。

兜底方案我用了三层。第一层在分割后处理上,对Mask做连通域分析和面积阈值过滤,小于一定像素面积的小块动态区域视为噪声,不参与点云剔除。第二层在点云侧,动态点必须满足“在语义Mask内且深度与最近像素深度一致”,如果不一致就保留,这能挡住不少由遮挡或投影误差引发的误删。第三层在时间维,对连续帧做语义Mask的投票融合,如果同一个像素区域只在某一帧被判为动态,前后帧都是静态,那这一帧的判定大概率是误检,单帧不执行删除。

5.4 评价指标与可视化调试工具

做数据篇的收尾验证,不能只看一两张图觉得“看起来还行”。我习惯用三个量化指标评估动态祛除质量:

指标含义参考目标
动态点删除率被正确识别的动态激光点数 / 真值动态点数大于90%
静态点保留率保留下来的静态激光点数 / 真值静态点数大于98%
误删点占比被删除的静态点数 / 总删除点数小于5%

真值怎么来?选一段车辆静止、场景里只有一个行人来回走动的数据,手动标出静态背景点,再用标准流程跑一遍动态祛除,对比前后点云就能算出上述指标。可视化调试工具方面,我推荐先把投影结果保存成带alpha通道的RGB图,动态Mask用红色叠加显示,激光点按“保留/删除”用绿/红着色,这样一帧一帧翻看视频,很快就能定位到是哪一环节出了问题。

6. 我个人最想强调的一次现场教训

讲了这么多方法,最后分享一个我自己栽过的跟头,可能比前面所有公式都更有参考价值。

项目测试到中期时,我发现祛除效果忽好忽坏,晴天上午测完一切正常,下午推到一片树荫下,动态车辆的点云就开始大量残留。排查了一整天才找到原因:树荫下光线骤降,相机自动曝光把图像整体拉亮,暗部噪声放大,分割模型把深色车辆的部分像素误归到了“背景”类;偏偏那台车正好是深灰色,和柏油路颜色接近,模型彻底“隐身”了它。

这件事给我的教训是:视觉语义分割对光照的敏感度远高于我的预期,而数据篇里所有的采集、标注和同步工作,如果不在设计阶段就把光照变化纳入考虑,后面的算法再努力也是和物理极限硬碰硬。后来我在数据采集规范里写死了一条:每个采集时段必须覆盖晴天、阴天、树荫、逆光、傍晚五个光照条件,并按比例混合进训练集,才彻底解决了同类问题。

如果你正在做类似的多传感器融合项目,我会建议你从第一天就把“光照变化”当做一个正式的数据维度来管理,而不是等到模型上线后再补样本。数据篇的工程,本质上就是老老实实把每一个会影响自动化判断的因素,提前变成可控的输入。这个原则,不只适用于动态障碍物祛除,也适用于你手上任何依赖真实世界数据的感知系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询