4. 传感器数据采集:Camera、LIDAR、RADAR、GNSS/IMU传感器配置与数据获取
传感器配置这块,说实话是CARLA仿真里最磨人的环节之一。我刚开始做自动驾驶仿真时,以为传感器就是挂上去就能用,结果发现数据对齐、时间戳同步、坐标系转换这些坑,一个比一个深。
今天咱们就把Camera、LIDAR、RADAR、GNSS/IMU这四类传感器,从配置到数据获取,掰开了讲清楚。
4.1 传感器挂载与基础配置
在CARLA里,传感器不是直接创建的,得先有车,再往车上挂。我习惯用蓝图库来生成传感器,这样参数可控。
# 获取车辆蓝图
vehicle_bp = world.get_blueprint_library().find('vehicle.tesla.model3')
spawn_point = world.get_map().get_spawn_points()[0]
vehicle = world.spawn_actor(vehicle_bp, spawn_point)
# 挂载RGB相机
camera_bp = world.get_blueprint_library().find('sensor.camera.rgb')
camera_bp.set_attribute('image_size_x', '1920')
camera_bp.set_attribute('image_size_y', '1080')
camera_bp.set_attribute('fov', '90')
camera_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)
嗯,这里要注意:attach_to参数决定了传感器是跟着车走的。如果你不传这个参数,传感器就飘在原地了——我刚开始就犯过这个错,车开走了,相机还在原地拍空气。
4.2 Camera:不只是拍张图那么简单
Camera传感器是感知系统的眼睛。但很多人只关注图像本身,忽略了时间戳和坐标系。
核心要点:Camera数据必须包含时间戳、传感器ID、图像尺寸、FOV信息。这些元数据在后续多传感器融合时至关重要。
我一般用回调函数来接收数据,这样能保证实时性:
def camera_callback(image, sensor_id):
# 保存原始数据
image.save_to_disk(f'_out/{sensor_id}_{image.frame:06d}.png')
# 提取时间戳
timestamp = image.timestamp
# 转换为numpy数组做后续处理
array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
array = np.reshape(array, (image.height, image.width, 4))
# 去掉alpha通道
array = array[:, :, :3]
camera.listen(lambda image: camera_callback(image, 'camera_front'))
为什么我要强调时间戳?因为在实际项目中,Camera的帧率和LIDAR的扫描频率不一样,没有精确的时间戳,你根本没法做数据对齐。我曾经在高速场景下吃过这个亏,图像和点云差了50毫秒,结果目标检测框全偏了。
4.3 LIDAR:点云数据的正确打开方式
LIDAR传感器在CARLA里配置起来稍微复杂点。关键参数有:通道数、每秒钟点数、水平视场角、垂直视场角。
| 参数 | 典型值 | 说明 |
|---|---|---|
| channels | 32 / 64 | 激光雷达线束数,越高点云越密 |
| points_per_second | 1,300,000 | 每秒采集点数,影响数据量 |
| range | 100m | 最大探测距离 |
| rotation_frequency | 10Hz | 旋转频率,通常10-20Hz |
lidar_bp = world.get_blueprint_library().find('sensor.lidar.ray_cast')
lidar_bp.set_attribute('channels', '64')
lidar_bp.set_attribute('points_per_second', '1300000')
lidar_bp.set_attribute('range', '100')
lidar_bp.set_attribute('rotation_frequency', '10')
lidar_bp.set_attribute('upper_fov', '15')
lidar_bp.set_attribute('lower_fov', '-25')
lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.0))
lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)
小技巧:如果你做多传感器融合,建议把LIDAR的旋转频率和Camera的帧率设为整数倍关系。比如LIDAR 10Hz,Camera 20Hz,这样每两帧图像对应一帧点云,对齐起来方便很多。
4.4 RADAR:被很多人忽略的关键传感器
说实话,RADAR在CARLA里用的人不多。但实际量产车上,RADAR是标配。它不怕雨雾,能直接测速度,这是Camera和LIDAR做不到的。
RADAR的数据结构比较特殊,每个点包含:距离、方位角、速度、强度。我习惯把这些数据整理成结构化格式:
def radar_callback(data):
radar_data = []
for detection in data:
point = {
'distance': detection.depth,
'azimuth': detection.azimuth, # 水平角
'altitude': detection.altitude, # 俯仰角
'velocity': detection.velocity, # 径向速度
'intensity': detection.intensity
}
radar_data.append(point)
# 转成numpy数组方便处理
radar_array = np.array([(p['distance'], p['azimuth'],
p['velocity'], p['intensity'])
for p in radar_data])
配置RADAR时,有个参数特别关键——points_per_second。这个值设得太低,目标检测不到;设得太高,CPU扛不住。我一般设5000-10000,够用又不卡。
4.5 GNSS/IMU:定位数据的灵魂
GNSS和IMU通常是一起用的。GNSS提供绝对位置,IMU提供姿态和加速度。CARLA里这两个传感器是分开的,但数据必须融合使用。
# GNSS配置
gnss_bp = world.get_blueprint_library().find('sensor.other.gnss')
gnss_bp.set_attribute('noise_alt_stddev', '0.0005')
gnss_bp.set_attribute('noise_lat_stddev', '0.0005')
gnss_bp.set_attribute('noise_lon_stddev', '0.0005')
gnss_bp.set_attribute('noise_alt_bias', '0.0')
# IMU配置
imu_bp = world.get_blueprint_library().find('sensor.other.imu')
imu_bp.set_attribute('noise_accel_stddev_x', '0.01')
imu_bp.set_attribute('noise_accel_stddev_y', '0.01')
imu_bp.set_attribute('noise_gyro_stddev', '0.001')
注意:GNSS的噪声参数不要设成0。完全无噪声的定位数据在真实场景中不存在。我见过有人把噪声全关了,结果算法在真车上跑得一塌糊涂——因为真车GNSS有误差,算法没学过带噪声的数据。
4.6 多传感器数据同步:最头疼的问题
好了,现在四个传感器都配好了。但问题来了——它们各自的数据到达时间不一样。Camera是20Hz,LIDAR是10Hz,RADAR是12Hz,GNSS是5Hz。怎么对齐?
我的做法是:用一个全局时间戳作为基准,所有传感器数据都带上这个时间戳。然后在后处理时,按时间戳做最近邻插值。
class SensorDataBuffer:
def __init__(self):
self.buffer = {
'camera': [],
'lidar': [],
'radar': [],
'gnss': [],
'imu': []
}
def add_data(self, sensor_type, timestamp, data):
self.buffer[sensor_type].append({
'timestamp': timestamp,
'data': data
})
def get_synced_data(self, target_timestamp, window=0.05):
"""获取目标时间戳附近50ms内的数据"""
synced = {}
for sensor_type in self.buffer:
# 找最近的数据
closest = min(self.buffer[sensor_type],
key=lambda x: abs(x['timestamp'] - target_timestamp))
if abs(closest['timestamp'] - target_timestamp) < window:
synced[sensor_type] = closest['data']
return synced
这个窗口大小怎么选?我一般设50ms。为什么?因为人眼能感知的延迟是100ms左右,50ms以内基本感觉不到。但如果你做的是高速场景下的紧急制动,窗口得缩到20ms以内。
4.7 数据存储与回放
采集到的数据怎么存?我推荐用HDF5格式,一个文件搞定所有传感器数据。结构大概这样:
/sensor_data/
/camera/
/frame_000001/ (图像数据 + 时间戳 + 内参)
/frame_000002/
...
/lidar/
/frame_000001/ (点云数据 + 时间戳 + 位姿)
/frame_000002/
...
/radar/
/frame_000001/ (雷达点 + 时间戳)
...
/gnss/
/frame_000001/ (经纬度 + 时间戳)
...
/imu/
/frame_000001/ (加速度 + 角速度 + 时间戳)
...
这样存的好处是:回放时按帧索引,想取哪一帧就取哪一帧,所有传感器数据都是对齐好的。
个人经验:数据采集时,一定要同时保存车辆的ground truth状态(位置、速度、朝向)。这样后面做算法评估时,你才知道传感器数据对应的真实场景是什么。我早期没保存这个,结果算法跑完了都不知道对不对,还得重新跑一遍仿真。
4.8 本章知识体系
下面这张图总结了传感器数据采集的核心流程:
说白了,传感器数据采集就三步:挂载配置、监听回调、同步存储。每一步都有坑,但踩过一次就记住了。
我个人建议,刚开始做的时候,先只配Camera和LIDAR,把这两个的数据对齐搞明白了,再加RADAR和GNSS/IMU。一口吃不成胖子,传感器多了,问题也多了。
可可精品资料库