3、Carla传感器详解:RGB相机、深度相机、语义分割相机、Lidar、Radar的配置与数据读取
各位同学,欢迎来到第三章。前面我们把Carla的环境搭好了,也跑通了第一个Hello World脚本。今天咱们来点硬核的——传感器。
说实话,传感器是自动驾驶的「眼睛」。没有它们,算法再牛也是白搭。我在做感知部署的时候,踩过最多的坑就是传感器配置不对,数据读出来全是乱的。今天咱们就把Carla里最常用的五种传感器讲透:RGB相机、深度相机、语义分割相机、Lidar和Radar。
3.1 传感器基础:蓝图与配置
在Carla里,所有传感器都是通过蓝图(Blueprint)生成的。你可以把蓝图理解成「传感器模板」,里面定义了传感器的型号、参数、挂载位置等。
我个人习惯先把传感器蓝图库拉出来看看,心里有个数:
# 获取所有传感器蓝图
blueprint_library = world.get_blueprint_library()
sensor_blueprints = blueprint_library.filter('sensor.*')
for bp in sensor_blueprints:
print(bp.id)
运行后你会看到类似这样的输出:
sensor.camera.rgb
sensor.camera.depth
sensor.camera.semantic_segmentation
sensor.lidar.ray_cast
sensor.other.radar
嗯,这就是我们今天要搞定的五个家伙。
bp.get_attributes() 查看所有可调参数。
3.2 RGB相机:最基础的视觉传感器
RGB相机就是咱们平时用的彩色摄像头。在感知任务里,它负责提供最直观的视觉信息——车道线、交通灯、行人、车辆,全靠它。
配置RGB相机其实很简单,但有几个参数我建议你重点关注:
| 参数 | 说明 | 推荐值 |
|---|---|---|
| image_size_x | 图像宽度 | 1280 |
| image_size_y | 图像高度 | 720 |
| fov | 视场角(度) | 90 |
| sensor_tick | 采集频率(秒) | 0.05(即20FPS) |
来看一段完整的配置代码:
# 创建RGB相机蓝图
rgb_bp = blueprint_library.find('sensor.camera.rgb')
rgb_bp.set_attribute('image_size_x', '1280')
rgb_bp.set_attribute('image_size_y', '720')
rgb_bp.set_attribute('fov', '90')
rgb_bp.set_attribute('sensor_tick', '0.05')
# 挂载到车辆前挡风玻璃位置
transform = carla.Transform(carla.Location(x=1.5, z=2.4))
rgb_camera = world.spawn_actor(rgb_bp, transform, attach_to=vehicle)
# 定义回调函数,处理图像数据
def process_rgb_image(image):
# 将原始数据转为numpy数组
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 去掉alpha通道,保留RGB
rgb_array = array[:, :, :3]
# 这里就可以做后续处理了,比如保存或送入模型
cv2.imshow('RGB Camera', rgb_array)
cv2.waitKey(1)
# 启动传感器监听
rgb_camera.listen(lambda image: process_rgb_image(image))
注意看那个 image.raw_data,它返回的是BGRA格式的字节流。我刚开始做的时候直接当RGB用,结果颜色全乱了。后来才发现需要先转成numpy数组,再取前三个通道。
image.raw_data 的原始字节,没做reshape,结果图像是歪的。记住:一定要用 np.frombuffer 转成数组,再 reshape 成 (height, width, 4) 的形状。
3.3 深度相机:给图像加上距离信息
深度相机和RGB相机长得一模一样,但它的每个像素存储的不是颜色,而是距离值。说白了,就是告诉你「这个物体离我有多远」。
配置方式和RGB几乎一样,只是蓝图名字不同:
depth_bp = blueprint_library.find('sensor.camera.depth')
depth_bp.set_attribute('image_size_x', '1280')
depth_bp.set_attribute('image_size_y', '720')
depth_bp.set_attribute('fov', '90')
depth_bp.set_attribute('sensor_tick', '0.05')
depth_camera = world.spawn_actor(depth_bp, transform, attach_to=vehicle)
关键区别在于数据读取。深度图像的数据需要做归一化处理,才能得到真实的距离值(单位:米)。
def process_depth_image(image):
# 原始数据是BGRA格式,但深度信息编码在R、G、B通道中
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 提取深度信息:Carla用RGB编码深度
# 公式:depth = R + G * 256 + B * 256 * 256
depth_in_meters = array[:, :, 2] * 1.0 + \
array[:, :, 1] * 256.0 + \
array[:, :, 0] * 256.0 * 256.0
depth_in_meters = depth_in_meters / (256.0 * 256.0 * 256.0 - 1.0)
depth_in_meters = depth_in_meters * 1000.0 # 转为米
# 显示深度图(归一化到0-255)
depth_display = np.clip(depth_in_meters / 100.0, 0, 1) * 255
depth_display = depth_display.astype(np.uint8)
cv2.imshow('Depth Camera', depth_display)
cv2.waitKey(1)
你想想看,为什么Carla要用RGB三个通道来编码深度?其实是为了节省带宽。一个32位的浮点数需要4个字节,而用RGB三个8位整数就能表示0到16777215的范围,精度也够用。
3.4 语义分割相机:让每个像素都有「身份」
语义分割相机是我个人最喜欢的一个传感器。它输出的图像里,每个像素都对应一个语义标签——比如行人、车辆、道路、建筑等。说白了,它帮你把图像里的东西「分类」好了。
配置代码和前面几乎一样:
seg_bp = blueprint_library.find('sensor.camera.semantic_segmentation')
seg_bp.set_attribute('image_size_x', '1280')
seg_bp.set_attribute('image_size_y', '720')
seg_bp.set_attribute('fov', '90')
seg_bp.set_attribute('sensor_tick', '0.05')
seg_camera = world.spawn_actor(seg_bp, transform, attach_to=vehicle)
数据读取时,每个像素的值是一个整数,代表物体类别。Carla预定义了多种类别,比如:
| 标签值 | 类别 | 颜色(R,G,B) |
|---|---|---|
| 0 | 未标注 | (0, 0, 0) |
| 1 | 建筑 | (70, 70, 70) |
| 4 | 行人 | (220, 20, 60) |
| 10 | 车辆 | (0, 0, 142) |
| 12 | 道路 | (128, 64, 128) |
读取并可视化语义分割图像:
def process_seg_image(image):
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 语义标签在R通道中
seg_labels = array[:, :, 2]
# 为了可视化,将标签映射为彩色
# Carla提供了内置的颜色映射函数
image.convert(carla.ColorConverter.CityScapesPalette)
seg_array = np.frombuffer(image.raw_data, dtype=np.uint8)
seg_array = seg_array.reshape((image.height, image.width, 4))
seg_rgb = seg_array[:, :, :3]
cv2.imshow('Semantic Segmentation', seg_rgb)
cv2.waitKey(1)
注意那个 convert(carla.ColorConverter.CityScapesPalette),它会自动把标签值映射成Cityscapes数据集的配色方案。我在做模型训练时,经常用这个来生成伪标签,省了不少标注的功夫。
3.5 Lidar:自动驾驶的「3D眼睛」
Lidar(激光雷达)是自动驾驶里最重要的传感器之一。它通过发射激光束,测量周围物体的距离和形状,生成3D点云数据。
Carla里默认的Lidar是64线束的,但你可以根据需要调整:
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', '1000000') # 每秒点数
lidar_bp.set_attribute('rotation_frequency', '20') # 旋转频率(Hz)
lidar_bp.set_attribute('upper_fov', '10') # 上视场角(度)
lidar_bp.set_attribute('lower_fov', '-30') # 下视场角(度)
# 挂载到车顶
lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.5))
lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)
Lidar的数据读取和相机不太一样,它返回的是点云数据:
def process_lidar_data(point_cloud):
# 点云数据包含:x, y, z, intensity
# 每个点4个float32值
points = np.frombuffer(point_cloud.raw_data, dtype=np.float32)
points = points.reshape((-1, 4))
# 提取坐标和强度
x = points[:, 0]
y = points[:, 1]
z = points[:, 2]
intensity = points[:, 3]
# 可视化(简单投影到2D)
# 这里只做演示,实际会用Open3D或Matplotlib
print(f"接收到 {len(points)} 个点")
print(f"X范围: {x.min():.2f} ~ {x.max():.2f}")
print(f"Y范围: {y.min():.2f} ~ {y.max():.2f}")
print(f"Z范围: {z.min():.2f} ~ {z.max():.2f}")
lidar.listen(lambda data: process_lidar_data(data))
为什么Lidar的数据是float32?因为每个点需要存储三维坐标和反射强度,用浮点数才能保证精度。我在做点云检测时,经常需要过滤掉地面点(z值接近0的点),不然算力都浪费在地面上了。
points_per_second 和 rotation_frequency。比如每秒100万个点,旋转频率20Hz,那么每帧就是5万个点。如果场景里物体多,点数会略有波动。
3.6 Radar:毫米波雷达的「速度感知」
Radar(毫米波雷达)和Lidar不同,它不仅能测距离,还能测速度。说白了,它能告诉你「那个物体正在以多快的速度靠近你」。
配置Radar:
radar_bp = blueprint_library.find('sensor.other.radar')
radar_bp.set_attribute('horizontal_fov', '30') # 水平视场角(度)
radar_bp.set_attribute('vertical_fov', '10') # 垂直视场角(度)
radar_bp.set_attribute('range', '100') # 探测距离(米)
radar_bp.set_attribute('points_per_second', '1500') # 每秒探测点数
radar_transform = carla.Transform(carla.Location(x=1.5, z=1.0))
radar = world.spawn_actor(radar_bp, radar_transform, attach_to=vehicle)
Radar的数据结构比较特殊,每个探测点包含:距离、方位角、仰角、速度:
def process_radar_data(radar_data):
# 每个点包含:距离、方位角、仰角、速度、信噪比
# 数据格式:[[azimuth, altitude, depth, velocity, intensity], ...]
points = np.frombuffer(radar_data.raw_data, dtype=np.float32)
points = points.reshape((-1, 5))
for point in points:
azimuth = point[0] # 方位角(弧度)
altitude = point[1] # 仰角(弧度)
depth = point[2] # 距离(米)
velocity = point[3] # 速度(米/秒,正数表示靠近)
intensity = point[4] # 信噪比
# 只关注靠近的物体(速度为正)
if velocity > 0 and depth < 50:
print(f"前方 {depth:.1f} 米处有物体,速度 {velocity:.1f} m/s")
radar.listen(lambda data: process_radar_data(data))
Radar的velocity字段很有意思。正值表示物体正在靠近你,负值表示远离。我在做前向碰撞预警时,就靠这个来判断是否需要刹车。
3.7 多传感器同步采集
在实际部署中,你不可能只用一个传感器。多个传感器同时工作,数据怎么同步?
我的做法是用一个全局回调函数,把所有传感器的数据收集起来,统一处理:
class SensorDataCollector:
def __init__(self):
self.rgb_image = None
self.depth_image = None
self.seg_image = None
self.lidar_data = None
self.radar_data = None
self.frame_count = 0
def collect_rgb(self, image):
self.rgb_image = image
self.frame_count += 1
def collect_depth(self, image):
self.depth_image = image
def collect_seg(self, image):
self.seg_image = image
def collect_lidar(self, data):
self.lidar_data = data
def collect_radar(self, data):
self.radar_data = data
def process_all(self):
if self.rgb_image is not None:
# 处理所有传感器数据
print(f"第 {self.frame_count} 帧数据已收集")
# 这里可以调用你的感知算法
collector = SensorDataCollector()
# 启动所有传感器
rgb_camera.listen(lambda image: collector.collect_rgb(image))
depth_camera.listen(lambda image: collector.collect_depth(image))
seg_camera.listen(lambda image: collector.collect_seg(image))
lidar.listen(lambda data: collector.collect_lidar(data))
radar.listen(lambda data: collector.collect_radar(data))
# 主循环
while True:
collector.process_all()
time.sleep(0.05) # 20FPS
你可能会问:这些传感器的时间戳能对齐吗?Carla内部会为每个传感器数据打上时间戳,但不同传感器的采集时刻可能略有差异。如果你需要严格同步,可以用Carla的 world.on_tick() 回调来触发采集。
3.8 本章小结
好了,今天的内容就到这里。我们讲了五种传感器的配置和数据读取:
- RGB相机:彩色图像,注意BGRA转RGB
- 深度相机:距离信息,注意RGB编码解码
- 语义分割相机:像素级标签,用Cityscapes配色可视化
- Lidar:3D点云,注意过滤地面点
- Radar:距离+速度,注意速度的正负含义
下一章,我们会把这些传感器数据喂给深度学习模型,做目标检测和语义分割。到时候你会发现,今天学的这些配置和读取方法,是后面所有算法的基础。
嗯,今天就到这里。有问题随时问我,咱们下章见。