2、Carla环境搭建与Python API入门
好,咱们直接进入正题。Carla这个模拟器,说白了就是给自动驾驶算法提供一个「练车场」。你想想看,总不能直接在真车上调试感知算法吧?那代价太大了。所以Carla就成了我们这帮做感知算法部署的人,最常用的仿真平台之一。
我个人习惯,在开始任何项目之前,先把环境搞得干干净净。这一步要是乱了,后面全是坑。嗯,咱们一步步来。
2.1 Carla安装与配置
Carla的安装其实不复杂,但有几个关键点要注意。我最早接触Carla的时候,直接去GitHub上拉了个源码自己编译,结果折腾了一整天。后来发现,对于咱们做算法部署的人来说,直接用预编译包是最省事的。
系统要求:Carla对显卡有一定要求。至少需要6GB显存,推荐8GB以上。操作系统建议Ubuntu 20.04或Windows 10/11。
安装步骤其实就这几步:
- 下载预编译包:去Carla官网或者GitHub Release页面,下载对应版本的压缩包。我一般用0.9.13或0.9.14版本,比较稳定。
- 解压:找个空间大的盘,直接解压。注意路径不要有中文。
- 启动服务器:进入Carla文件夹,运行
./CarlaUE4.sh(Linux)或CarlaUE4.exe(Windows)。
小技巧:我第一次启动时,发现画面卡得不行。后来发现是没加渲染参数。建议用 ./CarlaUE4.sh -quality-level=Low 启动,能省不少资源。
启动成功后,你会看到一个3D城市界面。这时候别急着操作,先确认服务器跑起来了。怎么确认?看终端输出,最后几行应该有 Listening for client connections on port 2000 这样的信息。
2.2 Python API基础
Carla的Python API,说白了就是一套控制模拟器的接口。你通过Python代码,可以控制车辆、获取传感器数据、设置天气等等。我刚开始学的时候,觉得API文档写得有点绕,但用熟了就会发现,其实核心就几个对象。
先安装Python API包:
# 进入Carla的PythonAPI目录
cd PythonAPI/carla/dist
# 安装对应Python版本的egg文件
pip install carla-0.9.13-cp38-cp38-linux_x86_64.whl
安装完成后,在Python里导入试试:
import carla
print(carla.__version__) # 如果能打印出版本号,说明安装成功
注意:Python版本一定要和Carla的egg文件匹配。我见过太多人因为Python版本不对,导入失败。建议用Python 3.7或3.8。
2.3 客户端与服务器通信
Carla采用的是客户端-服务器架构。服务器负责渲染和物理模拟,客户端负责发送指令和接收数据。说白了,服务器就是「车」,客户端就是「驾驶员」。
建立连接的代码很简单:
import carla
# 创建客户端,连接到本地服务器的2000端口
client = carla.Client('localhost', 2000)
# 设置超时时间,防止连接卡死
client.set_timeout(10.0)
# 获取世界对象
world = client.get_world()
print("连接成功!世界名称:", world.get_map().name)
这里有个坑,我必须要说一下。我曾经在项目里忘了设置超时时间,结果服务器没启动,客户端就一直卡在那里,整个脚本都僵住了。所以 set_timeout 这一步,千万别省。
连接成功后,你就能通过 world 对象来操控整个模拟世界了。比如获取地图、生成车辆、设置天气等等。
2.4 获取传感器数据
这才是咱们做感知算法部署的重头戏。Carla提供了多种传感器,比如相机、激光雷达、毫米波雷达、GPS等。获取传感器数据的方式,是通过「蓝图」来生成传感器,然后绑定回调函数。
我举个例子,获取RGB相机数据:
import carla
import numpy as np
# 连接服务器
client = carla.Client('localhost', 2000)
client.set_timeout(10.0)
world = client.get_world()
# 获取蓝图库
blueprint_library = world.get_blueprint_library()
# 找到相机蓝图
camera_bp = blueprint_library.find('sensor.camera.rgb')
# 设置相机参数
camera_bp.set_attribute('image_size_x', '800')
camera_bp.set_attribute('image_size_y', '600')
camera_bp.set_attribute('fov', '90')
# 生成相机并附着到车辆上
# 先找一辆车(或者自己生成一辆)
vehicle_bp = blueprint_library.filter('vehicle.*')[0]
spawn_point = world.get_map().get_spawn_points()[0]
vehicle = world.spawn_actor(vehicle_bp, spawn_point)
# 定义相机安装位置(车顶)
camera_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)
# 定义回调函数,处理图像数据
def process_image(image):
# 将原始数据转为numpy数组
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
# 去掉alpha通道
array = array[:, :, :3]
print("获取到一帧图像,尺寸:", array.shape)
# 绑定回调
camera.listen(process_image)
# 让车辆动起来
vehicle.set_autopilot(True)
# 保持运行
import time
time.sleep(10)
# 清理
camera.stop()
vehicle.destroy()
camera.destroy()
避坑指南:我曾经在回调函数里做大量计算,结果导致帧率骤降。后来发现,回调函数里尽量只做数据拷贝,复杂的处理放到另一个线程里去做。这样才不会拖累传感器数据的采集。
获取激光雷达数据也是类似的套路:
# 找到激光雷达蓝图
lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
# 设置参数
lidar_bp.set_attribute('channels', '32')
lidar_bp.set_attribute('range', '50')
lidar_bp.set_attribute('points_per_second', '100000')
# 生成并附着
lidar = world.spawn_actor(lidar_bp, camera_transform, attach_to=vehicle)
# 回调处理点云数据
def process_lidar(data):
points = np.frombuffer(data.raw_data, dtype=np.float32)
points = points.reshape((-1, 4)) # x, y, z, intensity
print("获取到一帧点云,点数:", points.shape[0])
lidar.listen(process_lidar)
这里有个关键点,你想想看,传感器数据是异步到达的。也就是说,你没法保证图像和点云是同一时刻的。所以在做多传感器融合时,一定要做时间戳对齐。我一般会在回调函数里记录 data.timestamp,然后根据时间戳来匹配数据。
2.5 常用传感器参数速查表
| 传感器类型 | 蓝图名称 | 关键参数 | 输出数据格式 |
|---|---|---|---|
| RGB相机 | sensor.camera.rgb | image_size_x, image_size_y, fov | BGRA图像 (4通道) |
| 深度相机 | sensor.camera.depth | image_size_x, image_size_y | 深度图 (单通道) |
| 语义分割相机 | sensor.camera.semantic_segmentation | image_size_x, image_size_y | 标签图 (单通道) |
| 激光雷达 | sensor.lidar.ray_cast | channels, range, points_per_second | 点云 (Nx4) |
| 毫米波雷达 | sensor.other.radar | horizontal_fov, vertical_fov, range | 雷达目标列表 |
| GPS | sensor.other.gnss | 无特殊参数 | 经纬度坐标 |
2.6 小结
这一章的内容,说白了就是让你能把Carla跑起来,并且能拿到传感器数据。我个人觉得,最难的部分其实是理解客户端-服务器的通信机制,以及回调函数的异步处理方式。只要这两个概念搞清楚了,后面的内容就顺了。
嗯,下一章咱们会深入讲讲如何用Carla生成自定义场景,以及如何批量采集数据。到时候会用到这一章的基础知识,所以建议你把代码都跑一遍,亲手感受一下。
课后练习:试着同时获取相机和激光雷达的数据,并打印出它们的时间戳。看看两个传感器的时间戳是否一致?如果不一致,差了多少毫秒?