传感器与数据采集:机器人的“感官世界”

做机器人这行,我经常跟学生说一句话:地图建得准不准,七成看传感器。你算法写得再漂亮,传感器数据一塌糊涂,后面全是白搭。今天咱们就来聊聊机器人最常用的三种“感官”——激光雷达、深度相机和惯性测量单元。

我个人习惯把传感器分成两类:外部传感器(看世界的)和内部传感器(感受自己的)。激光雷达和深度相机属于前者,IMU属于后者。它们各有各的脾气,你得摸透了才能用好。

核心观点:没有完美的传感器,只有合适的融合方案。理解每种传感器的原理和局限,是做好SLAM的第一步。

2.1 激光雷达(LiDAR):精确的距离“尺子”

激光雷达的原理其实不复杂——发射激光束,测量反射回来的时间,算出距离。这就是所谓的ToF(Time of Flight)原理。你想想看,光速是恒定的,只要计时够准,距离就能算得够精确。

我在项目中遇到过最头疼的问题,就是激光雷达在玻璃墙面前的表现。激光直接穿透玻璃,打到后面的物体再反射回来,或者干脆被镜面反射到别处去。嗯,这里要注意:LiDAR在透明、镜面、吸光材质面前会“失明”。

2.1.1 数据格式:点云与激光扫描

激光雷达输出的数据,常见有两种格式:

  • 单线激光雷达:输出一维距离数组,每个角度对应一个距离值。比如RPLIDAR A1,每圈采集360个点,角度分辨率1°。
  • 多线激光雷达:输出三维点云,每个点包含(x, y, z)坐标和反射强度。比如Velodyne VLP-16,16根激光束同时扫描。

下面是一个典型的单线激光雷达数据片段(ROS的LaserScan消息格式):

# LaserScan消息示例
header:
  seq: 1234
  stamp: 1623456789.123
  frame_id: "laser_frame"
angle_min: -3.14159        # 起始角度(弧度)
angle_max: 3.14159         # 终止角度
angle_increment: 0.0174533 # 角度分辨率(约1°)
time_increment: 0.0
scan_time: 0.1             # 扫描周期(10Hz)
range_min: 0.15            # 最小有效距离
range_max: 12.0            # 最大有效距离
ranges: [0.5, 0.52, 0.48, ...]  # 距离数组(360个值)
intensities: [15, 12, 18, ...]   # 反射强度数组
避坑指南:我曾经在室外强光环境下调试LiDAR,发现测距值频繁跳变。后来才意识到,太阳光中的红外成分会干扰激光雷达的接收器。解决办法是加装滤光片,或者选择抗环境光能力更强的905nm波长LiDAR。

2.2 深度相机(RGB-D):看得见,摸得着

深度相机,说白了就是给普通相机加了个“测距”功能。它输出的每个像素不仅有颜色(RGB),还有深度值(D),所以叫RGB-D相机。

主流的深度相机有三种技术路线:

技术类型 原理 代表产品 优点 缺点
结构光 投射红外光斑,通过变形计算深度 Kinect v1, Intel RealSense D415 室内精度高,成本低 室外失效,有效距离短(~5m)
ToF 发射红外脉冲,测量飞行时间 Kinect v2, Azure Kinect 帧率高,室外可用 分辨率低,多机干扰
双目立体 两个相机视差计算深度 ZED, Intel RealSense D435i 室内外通用,无主动光 弱纹理区域失效,计算量大

2.2.1 从深度图到点云

深度相机输出的原始数据是一张深度图——每个像素值代表该点到相机的距离。要生成三维点云,需要做一步坐标变换:

# 从深度图生成点云(伪代码)
def depth_to_pointcloud(depth_image, camera_intrinsics):
    fx = camera_intrinsics.fx  # 焦距x
    fy = camera_intrinsics.fy  # 焦距y
    cx = camera_intrinsics.cx  # 光心x
    cy = camera_intrinsics.cy  # 光心y
    
    points = []
    for v in range(height):
        for u in range(width):
            z = depth_image[v, u]  # 深度值
            if z == 0 or z > max_range:
                continue  # 无效点跳过
            x = (u - cx) * z / fx
            y = (v - cy) * z / fy
            points.append([x, y, z])
    return points

你可能会问:为什么深度图里会有0值?嗯,这是因为深度相机在某些区域测不到距离——比如太远的物体、吸光表面、或者边缘遮挡处。我建议在生成点云时,一定要做无效点过滤,否则后面建图会多出一堆“幽灵点”。

注意:RGB-D相机的深度图和彩色图通常存在视差,需要做对齐(registration)。我见过不少新手直接拿未对齐的数据做点云融合,结果地图全是重影。记得先调用相机SDK的align函数。

2.3 惯性测量单元(IMU):机器人的“内耳”

IMU就像人的内耳前庭系统,感知自身的加速度和角速度。它由加速度计和陀螺仪组成,有些还带磁力计。

我个人觉得,IMU是SLAM系统里最“狡猾”的传感器。为什么?因为它有漂移。加速度计积分得到速度,速度积分得到位置——每次积分都会累积误差。你想想看,一个静止的IMU,理论上加速度应该是0,但实际上它会输出一个微小的噪声。积分10秒后,位置误差可能已经漂了好几米。

2.3.1 IMU数据与里程计

IMU的原始数据长这样:

# IMU消息示例(ROS的Imu消息格式)
header:
  seq: 5678
  stamp: 1623456790.456
  frame_id: "imu_link"
orientation:                # 姿态(四元数)
  x: 0.0
  y: 0.0
  z: 0.0
  w: 1.0
angular_velocity:           # 角速度(rad/s)
  x: 0.01
  y: -0.02
  z: 0.005
linear_acceleration:        # 线加速度(m/s²)
  x: 0.12
  y: -0.08
  z: 9.81                   # 重力加速度

注意看,z轴的加速度是9.81——这是重力。做IMU里程计时,第一步就是去除重力分量,否则你算出来的位置会一直往天上飘。

IMU里程计的计算流程大致如下:

  1. 姿态更新:对角速度积分,得到当前姿态(四元数或欧拉角)
  2. 重力补偿:用姿态将加速度从机体坐标系转换到世界坐标系,减去重力
  3. 速度积分:对补偿后的加速度积分,得到速度
  4. 位置积分:对速度积分,得到位置
避坑指南:我曾经在无人机上只用IMU做纯惯性导航,结果30秒后位置误差超过10米。后来我学乖了——IMU只能做短时间(1-2秒)的位姿预测,必须配合其他传感器(LiDAR或相机)做修正。这就是后面要讲的传感器融合。

2.4 传感器数据融合:1+1 > 2

单独用LiDAR,建图精度高但帧率低(10Hz);单独用IMU,帧率高(200Hz)但漂移严重。把它们结合起来,就能取长补短。

下面这张图展示了多传感器融合的典型架构:

多传感器融合架构 激光雷达 (LiDAR) 深度相机 (RGB-D) IMU 点云配准 特征提取 预积分 传感器融合引擎 (扩展卡尔曼滤波 / 因子图优化) 位姿估计 + 地图构建 LiDAR提供精确结构 IMU提供高频预测

在实际项目中,我常用的融合策略是:LiDAR做前端匹配,IMU做运动预测,RGB-D提供纹理信息辅助回环检测。这样即使LiDAR在快速旋转时丢帧,IMU也能顶上去,保证位姿估计不中断。

总结一下:
  • LiDAR:精度高,适合建图,但怕玻璃和强光
  • RGB-D:信息丰富,适合特征匹配,但受距离和光照限制
  • IMU:高频但漂移,只能做短时预测
  • 三者融合,才能构建稳定可靠的SLAM系统