2、传感器基础:激光雷达工作原理(TOF、FMCW)、点云数据格式(PCD、BIN)、坐标系定义
大家好,我是你们这节课程的讲师。今天咱们聊聊激光雷达,这个BEV感知里最核心的传感器之一。说实话,我入行那会儿,激光雷达还是个稀罕物,价格贵得吓人。现在不一样了,国产方案百花齐放,但不管硬件怎么变,底层的原理和数据处理方式,始终是绕不开的基本功。
这一节,我会把激光雷达的两种主流测距原理、常见的点云文件格式,以及那个让无数新人头疼的坐标系定义,掰开揉碎了讲清楚。嗯,咱们直接开始。
核心要点: 激光雷达的本质,就是通过发射激光并接收回波,来精确测量目标物体的距离和角度。你想想看,这就像我们拿着一个手电筒在黑夜里扫射,通过光打到墙上再反射回来的时间,判断墙有多远。
2.1 激光雷达工作原理:TOF vs FMCW
目前市面上主流的激光雷达,测距原理就两派:TOF(飞行时间法)和 FMCW(调频连续波法)。我个人习惯把TOF叫做“脉冲派”,FMCW叫做“连续波派”。
2.1.1 TOF(飞行时间法)
TOF的原理非常直观。激光器发射一个极短的光脉冲,打到物体上后反射回来,被接收器捕获。我们只需要精确测量这个光脉冲从发射到接收的飞行时间 Δt,再乘以光速 c,除以2,就能得到距离 d。
d = (c * Δt) / 2
这里有个关键点:光速是 3×10⁸ m/s,所以时间测量必须非常非常准。1纳秒的误差,对应着15厘米的距离偏差。我在项目中遇到过,早期的一些TOF雷达在强光下测距会飘,就是因为背景光噪声干扰了时间测量的精度。
避坑指南: 我曾经调试过一个项目,发现点云里总有“鬼影”点。查了半天,原来是雷达的发射功率太高,打到近处的高反物体(比如路牌)上产生了二次反射。所以,TOF雷达的“多回波”处理能力很重要,一定要学会看厂商的Datasheet里有没有这个功能。
TOF的优势很明显:技术成熟、成本相对低、探测距离远。目前绝大多数量产车用的都是TOF方案。
2.1.2 FMCW(调频连续波法)
FMCW就有点不一样了。它不发射脉冲,而是发射一束频率随时间线性变化的连续激光。说白了,就是激光的频率像锯齿波一样,从低到高、再从高到低地扫。
当这束光打到物体上反射回来时,由于飞行时间的存在,回波信号的频率和当前发射信号的频率之间,会有一个频率差 Δf。这个 Δf 和距离 d 是成正比的。同时,如果物体有径向速度,还会产生一个多普勒频移,可以用来直接测速。
d = (c * Δf) / (2 * S) // S 是调频斜率
你想想看,FMCW天然就能测速,而且抗干扰能力极强。为什么?因为它是通过频率差来测距的,其他同频段的激光干扰很难恰好产生同样的频率差。我记得前两年有个客户非要拿FMCW雷达去做雨雾测试,效果确实比TOF好不少。
注意事项: FMCW虽然好,但它的信号处理链路非常复杂,需要高线性的激光器和精密的混频器。目前成本还是偏高,主要用在高端机器人或特殊场景。不过,我个人判断,未来3-5年,FMCW会逐渐下放到乘用车市场。
2.2 点云数据格式:PCD 与 BIN
聊完原理,咱们看看数据。激光雷达采集到的原始数据,经过处理后,就变成了我们熟悉的“点云”。每个点通常包含 (x, y, z, intensity) 四个信息。但在工程落地时,数据怎么存、怎么读,是个很实际的问题。
我见过太多新人,拿着PCD文件当宝贝,结果发现训练模型时根本跑不动。这里我给大家做个对比。
| 特性 | PCD 格式 | BIN 格式 |
|---|---|---|
| 文件结构 | 文本头 + 二进制/ASCII数据 | 纯二进制流 |
| 可读性 | 高(可以用文本编辑器打开看头文件) | 低(必须知道数据结构才能解析) |
| 存储效率 | 较低(头文件占用空间,ASCII模式更大) | 极高(无冗余信息) |
| 读取速度 | 较慢(需要解析头文件) | 极快(直接内存映射) |
| 常用场景 | 算法调试、可视化、数据交换 | 大规模数据集存储、模型训练 |
我个人习惯是:调试阶段用PCD,因为可以用PCL库直接可视化,方便找Bug。一旦进入训练或部署阶段,一律转成BIN格式。为什么?你想想看,一个64线雷达,一帧点云可能有十几万个点,用ASCII的PCD存,文件大小能翻3倍,读取时间更是慢得离谱。
这里给一个简单的Python读取BIN文件的示例,这是KITTI数据集的标准格式:
import numpy as np
def load_kitti_bin(bin_path):
"""
加载KITTI格式的BIN点云文件
每个点包含4个float32: x, y, z, reflectance
"""
points = np.fromfile(bin_path, dtype=np.float32).reshape(-1, 4)
return points
# 使用示例
points = load_kitti_bin('000000.bin')
print(f'点云点数: {points.shape[0]}')
print(f'前5个点: \n{points[:5]}')
避坑指南: 我曾经在读取一个第三方数据集时,发现点云坐标全乱了。后来一查,原来是BIN文件的字节序(Endianness)和我的机器不一致。所以,处理BIN文件时,一定要确认好数据是Little-Endian还是Big-Endian。KITTI用的是Little-Endian,也就是我们x86机器默认的。
2.3 坐标系定义:从传感器到世界
坐标系,这是BEV感知里最容易出错、也最容易被忽视的地方。说白了,就是每个点 (x, y, z) 到底是相对于谁的?
在自动驾驶中,我们至少会碰到三个坐标系:
- 激光雷达坐标系 (LiDAR Coordinate):原点在激光雷达的几何中心。通常,x轴指向车头前方,y轴指向左侧,z轴指向上方。这是点云最原始的坐标系。
- 车体坐标系 (Vehicle Coordinate):原点在车辆后轴中心(或质心)。x轴指向车头,y轴指向左侧,z轴指向上方。所有传感器最终都要统一到这个坐标系下。
- 世界坐标系 (World Coordinate):也叫全局坐标系。通常用UTM坐标或经纬度。用于多帧拼接和高精地图定位。
这里有个常见的坑:不同厂商的激光雷达,坐标系定义可能不一样。比如,有些雷达把y轴定义为车头方向,有些把x轴定义为车头方向。我建议,拿到一个新雷达后,第一件事就是去查它的用户手册,确认坐标系定义。
核心原则: 所有传感器数据,最终都要通过外参矩阵,变换到车体坐标系下。这个外参矩阵,就是激光雷达标定的核心输出。你想想看,如果坐标系没对齐,那BEV视角下的障碍物位置就是错的,后果不堪设想。
下面我用SVG画一张图,帮你理清这几个坐标系之间的关系。
嗯,这张图应该能帮你建立直观印象。在实际工程中,我们通常用齐次坐标来简化变换:
import numpy as np
def transform_point(p_lidar, extrinsic):
"""
p_lidar: 激光雷达坐标系下的点 [x, y, z, 1]
extrinsic: 4x4 外参矩阵 (LiDAR -> Vehicle)
返回: 车体坐标系下的点 [x, y, z, 1]
"""
p_vehicle = extrinsic @ p_lidar
return p_vehicle
# 示例:假设外参矩阵(单位矩阵表示无旋转无平移)
extrinsic = np.eye(4)
p_lidar = np.array([1.0, 2.0, 3.0, 1.0])
p_vehicle = transform_point(p_lidar, extrinsic)
print(f'变换后坐标: {p_vehicle[:3]}')
重要提醒: 坐标系定义一定要和标定工具保持一致。我曾经见过一个团队,标定用的坐标系是“x向右,y向前”,但算法代码里默认是“x向前,y向左”。结果就是,车明明在直行,BEV视角下却看到障碍物在漂移。这种低级错误,排查起来非常痛苦。
好了,这一节的内容就到这里。激光雷达的原理、数据格式、坐标系,这三块是后续所有标定和感知工作的基石。你把这些吃透了,后面学起来会轻松很多。