4、传感器入门:传感器类型概览、RGB相机、深度相机、语义分割相机
做自动驾驶仿真,传感器就是你的「眼睛」。没有传感器,车就是瞎子。CARLA 里内置了好几种传感器,今天咱们就把最常用的三种相机摸透:RGB 相机、深度相机、语义分割相机。
我个人习惯,刚开始学的时候,先别急着调参。先搞清楚每个传感器「看到了什么」,再想「怎么用」。你想想看,连数据长啥样都不知道,调参数不是瞎蒙吗?
4.1 传感器类型概览
CARLA 的传感器系统,说白了就是一个「挂载」机制。你把传感器绑在车上,它就开始往外吐数据。常见的传感器有这些:
| 传感器类型 | 输出数据 | 典型用途 |
|---|---|---|
| RGB 相机 | 彩色图像 (H×W×3) | 视觉感知、目标检测 |
| 深度相机 | 深度图 (H×W×1) | 距离估计、避障 |
| 语义分割相机 | 标签图 (H×W×1) | 场景理解、车道线识别 |
| LIDAR | 点云 (N×3) | 3D 感知、建图 |
| 雷达 | 目标列表 | 速度测量、远距检测 |
嗯,这里要注意:每种传感器都有自己的「蓝图 ID」,创建的时候别搞混了。我在项目中遇到过有人把深度相机的蓝图写成了 RGB 的,结果跑了一下午才发现数据不对——全是彩色图,根本没有深度信息。
4.2 RGB 相机——最直观的「眼睛」
RGB 相机就是咱们平时用的摄像头。它输出的是三通道彩色图像,每个像素有 R、G、B 三个值。说白了,你看到的是什么颜色,它就输出什么颜色。
创建 RGB 相机的代码很简单:
# 创建 RGB 相机蓝图
blueprint = world.get_blueprint_library().find('sensor.camera.rgb')
# 设置图像分辨率
blueprint.set_attribute('image_size_x', '800')
blueprint.set_attribute('image_size_y', '600')
# 设置视野角度
blueprint.set_attribute('fov', '90')
# 设置感光度(影响亮度)
blueprint.set_attribute('sensor_tick', '0.1') # 每 0.1 秒采集一帧
挂载到车上之后,你需要写一个回调函数来接收数据:
def process_rgb_image(image):
# 将原始数据转为 numpy 数组
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4)) # 注意:CARLA 返回的是 RGBA
# 去掉 Alpha 通道,只保留 RGB
rgb_array = array[:, :, :3]
# 显示或保存
cv2.imshow('RGB Camera', rgb_array)
cv2.waitKey(1)
[:, :, :3] 切掉。我第一次用的时候没注意,直接显示出来颜色全乱了,排查了半天才发现是通道顺序的问题。
4.3 深度相机——给图像加上「距离」
深度相机输出的不是彩色图,而是一张灰度图。每个像素的值代表该点到相机的距离。越近越亮,越远越暗。
创建深度相机:
blueprint = world.get_blueprint_library().find('sensor.camera.depth')
blueprint.set_attribute('image_size_x', '800')
blueprint.set_attribute('image_size_y', '600')
blueprint.set_attribute('fov', '90')
深度数据的处理方式有点不一样。CARLA 把深度值编码到了 RGB 三个通道里,需要解码才能得到真实的距离:
def process_depth_image(image):
# 将深度数据转为 numpy
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 解码深度值(CARLA 的深度编码方式)
depth = array[:, :, 2] + array[:, :, 1] * 256 + array[:, :, 0] * 256 * 256
depth = depth / (256 * 256 * 256 - 1) # 归一化到 [0, 1]
depth = depth * 1000 # 转换为米(最大 1000 米)
# 显示深度图
cv2.imshow('Depth Camera', depth / 100) # 除以 100 让图像可见
cv2.waitKey(1)
image.save_to_disk() 保存深度图,结果发现保存出来的图片全是黑的。后来才明白,深度值需要做归一化处理才能正常显示。直接用原始数据保存,值域太大,显示出来就是一片黑。
为什么会这样?因为深度值的范围是 0~1000 米,而图像像素值只有 0~255。不压缩的话,大部分像素都落在 255 以上,自然就全白了或者全黑了。
4.4 语义分割相机——让车「看懂」场景
语义分割相机是我个人觉得最酷的传感器。它输出的图像里,每个像素不是颜色,而是一个标签编号。比如:
- 0 = 未标记
- 1 = 道路
- 2 = 人行道
- 3 = 建筑
- 4 = 车辆
- 5 = 行人
- ... 等等
创建语义分割相机:
blueprint = world.get_blueprint_library().find('sensor.camera.semantic_segmentation')
blueprint.set_attribute('image_size_x', '800')
blueprint.set_attribute('image_size_y', '600')
blueprint.set_attribute('fov', '90')
处理语义分割数据时,CARLA 同样用了编码技巧。每个像素的标签值被编码到了 RGB 通道里:
def process_semantic_image(image):
# 转为 numpy
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 解码标签(和深度解码类似)
semantic = array[:, :, 2] + array[:, :, 1] * 256 + array[:, :, 0] * 256 * 256
semantic = semantic.astype(np.uint8) # 只取低 8 位,就是标签值
# 显示(需要映射为彩色才能看)
# CARLA 提供了内置的调色板
colored = image.colorize() # 直接调用这个方法就能得到彩色标签图
colored.save_to_disk('semantic_output.png')
image.colorize() 方法,CARLA 会自动用预定义的调色板给每个标签上色,这样你就能看到彩色的语义分割图了。
嗯,这里有个坑:语义分割的标签值范围是 0~255,但 CARLA 内部用了 24 位编码。如果你直接取 array[:, :, 0] 作为标签,得到的是错误的。一定要用上面那个解码公式,或者直接用 image.colorize() 省事。
4.5 三种相机的对比与选择
三种相机各有各的用处。我一般这样选:
- RGB 相机:做目标检测、车道线识别、交通标志识别。说白了,就是模仿人眼看到的画面。
- 深度相机:做避障、距离估计、3D 重建。你想想看,如果车不知道前面障碍物有多远,怎么刹车?
- 语义分割相机:做场景理解、可行驶区域检测。它告诉车「哪里是路,哪里是墙,哪里是人」。
在实际项目中,我经常把 RGB 和语义分割结合起来用。RGB 提供纹理信息,语义分割提供类别信息,两者互补。比如检测行人时,RGB 能看清人的样子,语义分割能精确知道人的轮廓边界。
4.6 本章知识体系
下面这张图帮你理清三种相机的核心逻辑:
三种传感器,三种「看世界」的方式。RGB 看颜色,深度看距离,语义看类别。把它们组合起来,你的自动驾驶车就有了「火眼金睛」。
好了,这一章就到这里。记住:多动手,多观察。把三种相机都跑一遍,看看同一个场景下它们输出的数据长什么样。实践出真知,这话一点不假。