第三节:传感器数据采集实战
好,咱们直接进入正题。传感器配置这块,说实话,是CARLA仿真里最容易被忽视、但又最容易出问题的地方。我记得刚带团队做自动驾驶仿真时,有个同事配了三天LIDAR,回放数据一看——点云全是乱的。后来发现是时间戳没对齐。嗯,这节课咱们就把这些坑一个个填上。
3.1 RGB相机配置与数据获取
RGB相机,说白了就是给自动驾驶系统装上一双「人眼」。但要注意,仿真里的相机和真实相机还是有区别的。
我个人习惯这样配置RGB相机:
# 创建RGB相机蓝图
camera_bp = 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_bp.set_attribute('sensor_tick', '0.05') # 20FPS
# 挂载到车辆上
camera_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)
这里有个细节——sensor_tick参数。它控制着传感器的采集频率。0.05秒就是20帧每秒。为什么不是0.033(30FPS)?我在项目中遇到过,帧率太高会导致数据量爆炸,而且很多下游算法根本吃不下。20FPS是个比较折中的选择。
3.2 深度相机:不只是「看得见」
深度相机和RGB相机最大的区别是什么?RGB告诉你「这是什么颜色」,深度相机告诉你「这玩意儿离我多远」。
配置深度相机时,我建议你注意两个关键参数:
depth_bp = blueprint_library.find('sensor.camera.depth')
depth_bp.set_attribute('image_size_x', '1024')
depth_bp.set_attribute('image_size_y', '768')
depth_bp.set_attribute('fov', '90')
depth_bp.set_attribute('sensor_tick', '0.05')
# 深度数据需要特殊处理
def process_depth_image(image):
# 将深度数据归一化到0-255范围
depth_data = np.array(image.raw_data)
depth_in_meters = depth_data[:,:,0] + depth_data[:,:,1] * 256 + depth_data[:,:,2] * 256 * 256
depth_in_meters = depth_in_meters / (256**3 - 1) * 1000 # 转换为米
return depth_in_meters
你想想看,深度数据其实是个三通道的RGB图像,但每个通道编码了不同的深度信息。我第一次用的时候直接拿RGB的方式去读,结果全是乱码。后来查文档才发现,需要把三个通道的值组合起来才能得到真实的距离。
3.3 LIDAR:点云数据的灵魂
LIDAR这块,说实话,是传感器配置里最复杂的。为什么?因为参数太多了。
我曾经踩过一个坑:配置了64线LIDAR,结果点云稀疏得跟16线似的。后来发现是points_per_second设得太低了。
lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
lidar_bp.set_attribute('channels', '64')
lidar_bp.set_attribute('range', '100')
lidar_bp.set_attribute('points_per_second', '2200000')
lidar_bp.set_attribute('rotation_frequency', '20')
lidar_bp.set_attribute('upper_fov', '15')
lidar_bp.set_attribute('lower_fov', '-25')
lidar_bp.set_attribute('sensor_tick', '0.05')
# 点云数据回调
def process_lidar_data(data):
points = np.frombuffer(data.raw_data, dtype=np.dtype('f4'))
points = np.reshape(points, (int(points.shape[0] / 4), 4))
# points的格式: [x, y, z, intensity]
return points
这里我解释一下几个关键参数:
- channels:线数。64线够用了,128线数据量太大,很多算法跑不动
- points_per_second:每秒点数。220万是64线的典型值
- rotation_frequency:旋转频率。20Hz意味着每秒转20圈
- upper_fov/lower_fov:垂直视场角。我习惯设成+15到-25度,这样能覆盖近处地面
3.4 IMU与GNSS:定位的「眼睛」
IMU和GNSS,说白了就是给车辆装了个「惯性导航系统」。IMU告诉你「我转了多少度、加速度多少」,GNSS告诉你「我在哪里」。
配置起来其实很简单:
# IMU配置
imu_bp = blueprint_library.find('sensor.other.imu')
imu_bp.set_attribute('sensor_tick', '0.01') # 100Hz,IMU需要高频
# GNSS配置
gnss_bp = blueprint_library.find('sensor.other.gnss')
gnss_bp.set_attribute('sensor_tick', '0.1') # 10Hz,GPS更新慢
gnss_bp.set_attribute('noise_alt_stddev', '0.5') # 模拟真实GPS噪声
这里有个关键点——IMU的频率一定要高。为什么?因为IMU是用来做短时间积分定位的。你想想看,如果IMU只有20Hz,那两帧之间车辆的运动就全靠猜了。我一般设到100Hz以上。
GNSS的噪声参数也很重要。真实GPS是有误差的,如果你把噪声设成0,那仿真出来的数据跟真实世界差距太大,算法训练出来到实车上就废了。
3.5 时间戳同步机制
好,终于到了最核心的部分——时间戳同步。这个问题,说实话,是传感器数据采集里最大的坑。
为什么需要同步?你想想看,如果RGB相机采集的是t=0.05秒的图像,LIDAR采集的是t=0.06秒的点云,那这两个数据就对应不上。车辆在0.01秒内可能已经移动了0.2米,这误差足以让融合算法崩溃。
CARLA提供了两种同步方式:
# 方式一:基于世界时间同步(推荐)
settings = world.get_settings()
settings.synchronous_mode = True # 开启同步模式
settings.fixed_delta_seconds = 0.05 # 固定步长
world.apply_settings(settings)
# 然后手动推进时间
world.tick() # 每次调用,所有传感器同时采集数据
# 方式二:基于传感器回调的时间戳对齐
def sensor_callback(data, sensor_name):
timestamp = data.timestamp # 这个时间戳是全局统一的
# 所有传感器的timestamp都是基于同一个时钟
print(f"{sensor_name}: {timestamp}")
我个人强烈建议使用同步模式。为什么?因为异步模式下,每个传感器的采集时间是独立的,你很难保证它们在同一时刻采集数据。同步模式下,每次world.tick()调用,所有传感器同时触发,时间戳完全一致。
时间戳对齐的代码实现:
# 创建一个数据结构来存储对齐后的传感器数据
class SensorDataBuffer:
def __init__(self):
self.buffer = {
'rgb': [],
'depth': [],
'lidar': [],
'imu': [],
'gnss': []
}
self.current_timestamp = None
def add_data(self, sensor_type, data):
timestamp = data.timestamp
# 只保留当前时间戳附近的数据
if self.current_timestamp is None:
self.current_timestamp = timestamp
# 时间差在0.01秒内的数据认为是同步的
if abs(timestamp - self.current_timestamp) < 0.01:
self.buffer[sensor_type].append(data)
# 检查是否所有传感器数据都到了
if all(len(self.buffer[key]) > 0 for key in self.buffer):
# 所有数据对齐成功,可以进行处理
self.process_aligned_data()
self.clear_buffer()
self.current_timestamp = None
这个缓冲区的设计思路很简单:每个传感器来了数据先存着,等所有传感器都到齐了,再统一处理。时间差控制在0.01秒以内,这个阈值可以根据你的实际需求调整。
3.6 知识体系总览
说了这么多,咱们用一张图来总结一下传感器数据采集的核心逻辑:
这张图把咱们这节课的内容串起来了。左边是传感器配置,右边是时间同步,中间是数据采集这个核心。你想想看,配置再完美,时间不同步也是白搭。反过来,时间同步做得再好,传感器参数没调对,数据质量也不行。两者缺一不可。
好了,这节课的内容就到这里。传感器配置和时间同步这块,说实话,需要多练。我建议你先把RGB和LIDAR跑通,再加IMU和GNSS,最后做时间同步。一步一步来,别着急。
可可精品资料库