4、传感器配置基础:传感器类型、参数标定与数据同步
做SLAM这么多年,我接触过的传感器种类少说也有几十种。说实话,每次拿到一个新传感器,第一件事不是看它的精度有多高,而是先搞清楚它的「脾气」——数据怎么读、频率稳不稳、标定参数对不对。这一章,我就把传感器配置这块的实战经验掰开揉碎了讲给你听。
4.1 传感器类型:各有各的「性格」
SLAM系统里常见的传感器就这四类:激光雷达、IMU、里程计、相机。它们各有各的强项,也各有各的短板。
4.1.1 激光雷达(LiDAR)
激光雷达说白了就是「用激光测距」。它发射激光束,打到物体上反射回来,通过时间差算出距离。我最早接触的是16线雷达,那时候觉得能扫出点云已经很神奇了。
常见的激光雷达类型:
- 机械式:旋转扫描,360度视野。Velodyne、Hesai是代表。优点是视野广,缺点是机械结构容易坏。
- 固态式:没有旋转部件,靠电子扫描。Livox、Ouster。体积小、寿命长,但视场角有限。
- MEMS式:微振镜扫描,介于机械和固态之间。性价比不错。
4.1.2 IMU(惯性测量单元)
IMU测量加速度和角速度。它不依赖外部信号,所以不会丢。但它的致命问题是——漂移。你想想看,加速度积分一次得速度,再积分一次得位置,误差会像滚雪球一样越滚越大。
IMU的关键参数:
- 加速度计量程:±2g、±4g、±6g、±8g、±16g。选大了精度低,选小了容易饱和。
- 陀螺仪量程:±125°/s、±250°/s、±500°/s、±1000°/s、±2000°/s。机器人一般用±500°/s够用。
- 零偏稳定性:这个参数决定了IMU的「底子」好不好。消费级IMU的零偏稳定性在10°/h左右,工业级能做到1°/h以下。
4.1.3 里程计(Odometry)
里程计通常指轮式编码器。它测量轮子转了多少圈,结合轮径算出移动距离。优点是简单可靠,缺点是轮子打滑就完蛋。
里程计的数据格式很简单:
// 典型的里程计消息
header:
seq: 1234
stamp: 1623456789.123
frame_id: "odom"
pose:
position: [1.234, 0.567, 0.0]
orientation: [0.0, 0.0, 0.382, 0.924]
twist:
linear: [0.5, 0.0, 0.0]
angular: [0.0, 0.0, 0.1]
4.1.4 相机(Camera)
相机提供最丰富的环境信息——颜色、纹理、形状。但它的弱点是受光照影响大,纯视觉SLAM在暗处或纹理缺失的地方容易崩。
相机类型:
- 单目:一个镜头,无法直接获取深度。需要靠运动估计。
- 双目:两个镜头,通过视差算深度。基线越长,测距越远。
- RGB-D:直接输出深度图。Intel RealSense、Microsoft Kinect是代表。室内好用,室外受红外干扰。
4.2 传感器参数标定:别让「误差」毁了你的系统
传感器买回来,参数都是厂家给的。但说实话,厂家参数只能当参考。每个传感器都有个体差异,温度变化、安装应力都会让参数偏移。所以标定是必须的。
4.2.1 内参标定
内参是传感器自身的参数。比如相机的焦距、畸变系数,IMU的零偏、尺度因子。
相机内参标定:
我习惯用张正友标定法。打印一张棋盘格,拍20张不同角度的照片,用OpenCV跑一遍就出来了。
# 相机内参标定示例(Python + OpenCV)
import cv2
import numpy as np
# 棋盘格尺寸
pattern_size = (9, 6) # 内角点数量
square_size = 0.025 # 每个格子边长(米)
# 准备标定板上的3D点
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
objp *= square_size
# 存储所有图像的点
objpoints = [] # 3D点
imgpoints = [] # 2D点
# 遍历所有标定图像
for fname in calibration_images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
objpoints.append(objp)
imgpoints.append(corners)
# 执行标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("相机内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
IMU内参标定:
IMU标定主要是确定零偏、尺度因子和轴间对齐误差。我常用的工具是imu_utils(ROS包)。把IMU静止放置2小时以上,采集数据后跑一遍标定程序。
# 使用imu_utils标定IMU
rosrun imu_utils imu_an.py --bagname imu_static.bag --imu_topic /imu/data
4.2.2 外参标定
外参是传感器之间的相对位姿。比如激光雷达和相机之间的旋转平移矩阵。这个标定做不好,融合出来的数据就是「两张皮」。
我常用的外参标定方法:
- 手动标定:找几个特征点,手动对齐。精度一般,但快。
- 自动标定:用标定板或自然特征,跑优化算法。精度高,但需要写代码。
- 在线标定:系统运行过程中自动估计外参。适合长期运行的机器人。
4.3 传感器数据帧率与同步:时间就是一切
SLAM系统里,时间同步比数据精度更重要。你想想看,如果激光雷达的数据是10Hz,IMU是200Hz,相机是30Hz,它们的时间戳对不上,融合出来的位姿就是错的。
4.3.1 帧率匹配
不同传感器的帧率差异很大。常见的帧率:
| 传感器类型 | 典型帧率 | 说明 |
|---|---|---|
| 激光雷达 | 10-20 Hz | 机械式一般10Hz,固态可以到20Hz |
| IMU | 100-1000 Hz | 消费级200Hz,工业级400Hz以上 |
| 里程计 | 50-200 Hz | 取决于编码器采样频率 |
| 相机 | 15-60 Hz | 视觉SLAM常用30Hz |
帧率不匹配怎么办?插值。比如IMU数据比激光雷达快,就把IMU数据插值到激光雷达的时间戳上。
// IMU数据插值到激光雷达时间戳
// 伪代码示例
IMUData interpolateIMU(IMUData imu1, IMUData imu2, double target_time) {
double dt = (target_time - imu1.timestamp) / (imu2.timestamp - imu1.timestamp);
IMUData result;
result.acc = imu1.acc + (imu2.acc - imu1.acc) * dt;
result.gyro = imu1.gyro + (imu2.gyro - imu1.gyro) * dt;
result.timestamp = target_time;
return result;
}
4.3.2 硬件同步 vs 软件同步
硬件同步:用一根PPS(秒脉冲)信号线把所有传感器的时间基准统一。精度可以达到微秒级。但需要硬件支持,布线也麻烦。
软件同步:各传感器各自记录时间戳,然后在算法层做对齐。精度取决于系统时钟的稳定性。Linux系统用clock_gettime()可以做到毫秒级。
4.3.3 时间戳管理
时间戳是传感器数据的「身份证」。管理好时间戳,后面融合就省心一半。
我的时间戳管理原则:
- 统一时基:所有传感器用同一个时钟源。ROS里就是/clock话题。
- 记录触发时间:不是接收时间,而是传感器实际采集的时间。很多相机驱动默认给的是接收时间,这个坑我踩过。
- 处理延迟:传感器数据从采集到到达CPU有延迟。IMU延迟最小(微秒级),相机延迟最大(几毫秒到几十毫秒)。
4.4 知识体系总览
下面这张图是我自己整理的传感器配置知识体系,你可以把它当作一个检查清单。每次搭建新系统时,对着这张图过一遍,基本不会漏东西。
嗯,传感器配置这块内容其实挺杂的。但只要你把「类型选择→参数标定→时间同步」这条主线抓住,剩下的都是细节问题。我在实际项目中,每次搭建新系统都会花至少半天时间专门做标定和同步测试——这个时间省不得。
可可精品资料库