4. 激光雷达标定与配置

好,咱们进入第四讲。激光雷达这东西,买回来插上电就能用吗?

说实话,能转、能出数据,但离“好用”还差得远。我见过太多新手,把雷达往车上一装,跑个 Nav2 就抱怨定位不准、地图有重影。其实八成问题出在标定和配置上。

这一讲,咱们就聊聊怎么把雷达“伺候”好。

4.1 帧率与角度分辨率:一对冤家

先问个问题:你希望雷达转得快一点,还是慢一点?

转得快,帧率就高。比如 10Hz,每秒出 10 帧点云。这对动态避障有好处,机器人跑起来反应快。但代价是什么?角度分辨率会下降。

为什么?我解释一下。机械式雷达内部有个电机,带着激光头旋转。如果电机转一圈的时间是 0.1 秒(10Hz),那在这一圈里,激光头能发射的激光脉冲总数是固定的。比如每秒能发 2 万个脉冲,那 10Hz 下,每圈就是 2000 个脉冲。这 2000 个脉冲均匀分布在 360° 上,角度分辨率就是 360/2000 = 0.18°。

如果你把帧率降到 5Hz,电机转一圈要 0.2 秒。同样每秒 2 万个脉冲,每圈就能有 4000 个脉冲。角度分辨率就提升到了 0.09°。

你看,帧率和分辨率,说白了就是鱼和熊掌。

核心结论:

  • 高帧率(10Hz+):适合动态场景、快速移动的机器人。角度分辨率会牺牲一些。
  • 高分辨率(0.1°以下):适合建图、静态场景扫描。帧率会低一些。

我个人习惯是:做 SLAM 建图时用 5Hz,跑导航时切到 10Hz。当然,这取决于你的雷达型号。有些雷达支持动态切换,有些需要重启驱动。

4.2 畸变校正原理:别让运动毁了你的点云

这是个大坑。我刚开始做机器人时,就栽在这上面。

想象一下:雷达在旋转扫描。它从 0° 扫到 180° 需要 50 毫秒(10Hz 下)。在这 50 毫秒里,你的机器人如果正在转弯,那这帧点云里的每个点,其实是在不同时刻、不同位姿下采集的。

结果就是——一堵笔直的墙,在点云里变成了弯曲的。这就是运动畸变。

注意:运动畸变在低速场景下不明显,但一旦机器人速度超过 0.5m/s 或角速度较大,畸变就会严重影响建图和定位精度。

怎么校正?原理其实不复杂。

核心思路是:把每个激光点的时间戳找出来,然后根据这段时间内机器人的运动(里程计或 IMU 数据),把每个点“反算”到同一个参考时刻(通常是这一帧的起始时刻或中间时刻)。

举个例子:

  1. 雷达在 t0 时刻发出第一个激光脉冲,测得距离 d0,角度 θ0。
  2. 在 t1 时刻发出第二个脉冲,测得 d1,角度 θ1。
  3. 如果机器人在这段时间内移动了 Δx、Δy、Δθ,那第二个点其实是在一个偏移后的坐标系下测得的。
  4. 校正就是:把第二个点的坐标,通过 Δx、Δy、Δθ 变换回 t0 时刻的坐标系下。

嗯,这里要注意:校正依赖外部里程计数据。如果里程计不准,校正效果反而更差。我曾经在一个项目里,IMU 数据噪声太大,校正后点云比没校正还乱。后来换了更好的 IMU 才解决。

我的经验:如果暂时没有好的里程计,可以先用“匀速模型”做近似校正。假设机器人在一帧时间内匀速运动,用上一帧和当前帧的位姿差来插值。虽然不完美,但比不校正强很多。

4.3 配置参数文件:把一切写进 YAML

好了,理论讲完,咱们来点实际的。在 ROS2 里,激光雷达的配置通常写在一个 YAML 文件里。我拿一个典型的配置举例:

# lidar_config.yaml
laser_frame_id: "laser_link"

# 帧率设置
scan_frequency: 10.0  # Hz,我通常设 10

# 角度分辨率
angle_increment: 0.0174533  # 弧度,约 1 度。如果想更精细,改成 0.0087266(0.5 度)

# 扫描范围
angle_min: -3.14159  # -180°
angle_max: 3.14159   # 180°

# 距离范围
range_min: 0.1   # 米,太近的不要
range_max: 30.0  # 米,太远的不可靠

# 畸变校正开关
enable_motion_distortion_correction: true

# 畸变校正依赖的里程计话题
odom_topic: "/odom"

# 时间戳补偿
time_offset: 0.0  # 秒,如果雷达和主控时间不同步,可以微调

这个文件怎么用?通常在雷达驱动节点的 launch 文件里加载:

# lidar_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='sick_scan2',  # 以 SICK 雷达为例
            executable='sick_generic_can',
            name='lidar_node',
            parameters=['lidar_config.yaml'],
            output='screen'
        )
    ])

你看,就这么简单。但有几个参数我特别想提醒你:

  • laser_frame_id:一定要和你的 TF 树对得上。我见过有人写成 "laser",但 TF 里是 "laser_link",结果点云全飞了。
  • time_offset:这个参数容易被忽略。如果雷达和主控之间有时延(比如通过 USB 转串口),可以在这里补偿。我曾经调了 0.02 秒,效果立竿见影。
  • range_max:别设太大。很多雷达标称 30 米,但实际在室内 10 米外就噪声很大。设小一点,反而能减少误检测。

4.4 知识体系总览

说了这么多,咱们用一张图把这一章的核心逻辑串起来:

激光雷达标定与配置核心逻辑 帧率与角度分辨率 鱼和熊掌,需权衡 畸变校正原理 运动补偿,反算位姿 配置参数文件 YAML 驱动一切 高帧率 → 动态避障 高分辨率 → 精细建图 依赖里程计/IMU 匀速模型可做近似 frame_id 必须匹配 TF range_max 别设太大 最终目标:输出高质量、低畸变的点云数据 让 Nav2 的定位和规划模块“吃得饱、吃得好”

4.5 避坑指南

最后,分享几个我踩过的坑:

  • 帧率设太高,CPU 扛不住:20Hz 的点云,处理起来 CPU 占用率直接飙到 80%。后来我降到 10Hz,一切正常。别盲目追求高帧率。
  • 畸变校正开了,但里程计没发布:驱动会一直等 /odom 话题,导致雷达节点卡死。检查一下你的里程计是否在正常运行。
  • 角度分辨率设得太细:比如 0.01°,一帧点云有 36000 个点。传输和处理都慢。除非你有特殊需求,否则 0.5° 到 1° 就够用了。

嗯,这一讲就到这里。配置参数看似琐碎,但每一个参数背后都有它的物理意义和工程权衡。理解了这些,你就能根据实际场景灵活调整,而不是死记硬背。

一句话总结:激光雷达的标定与配置,本质是在帧率、分辨率、计算资源之间找平衡。畸变校正是锦上添花,但前提是里程计要靠谱。