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 数据同步模块(时间戳对齐) HDF5数据存储 每个传感器数据都携带时间戳、传感器ID、坐标系信息

说白了,传感器数据采集就三步:挂载配置、监听回调、同步存储。每一步都有坑,但踩过一次就记住了。

我个人建议,刚开始做的时候,先只配Camera和LIDAR,把这两个的数据对齐搞明白了,再加RADAR和GNSS/IMU。一口吃不成胖子,传感器多了,问题也多了。

可可精品资料库