2、Carla环境搭建与Python API入门

好,咱们直接进入正题。Carla这个模拟器,说白了就是给自动驾驶算法提供一个「练车场」。你想想看,总不能直接在真车上调试感知算法吧?那代价太大了。所以Carla就成了我们这帮做感知算法部署的人,最常用的仿真平台之一。

我个人习惯,在开始任何项目之前,先把环境搞得干干净净。这一步要是乱了,后面全是坑。嗯,咱们一步步来。

2.1 Carla安装与配置

Carla的安装其实不复杂,但有几个关键点要注意。我最早接触Carla的时候,直接去GitHub上拉了个源码自己编译,结果折腾了一整天。后来发现,对于咱们做算法部署的人来说,直接用预编译包是最省事的。

系统要求:Carla对显卡有一定要求。至少需要6GB显存,推荐8GB以上。操作系统建议Ubuntu 20.04或Windows 10/11。

安装步骤其实就这几步:

  1. 下载预编译包:去Carla官网或者GitHub Release页面,下载对应版本的压缩包。我一般用0.9.13或0.9.14版本,比较稳定。
  2. 解压:找个空间大的盘,直接解压。注意路径不要有中文。
  3. 启动服务器:进入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生成自定义场景,以及如何批量采集数据。到时候会用到这一章的基础知识,所以建议你把代码都跑一遍,亲手感受一下。

课后练习:试着同时获取相机和激光雷达的数据,并打印出它们的时间戳。看看两个传感器的时间戳是否一致?如果不一致,差了多少毫秒?