第二章:CARLA入门——架构解析、核心概念与启动

好,咱们正式开始动手了。

上一章我们聊了CARLA和SUMO能干什么,这一章,咱们直接进入CARLA的世界。我个人习惯是,学一个新工具,先别急着敲代码,先把它的骨架摸清楚。你想想看,如果连服务器怎么启动、世界怎么创建都不清楚,后面写再多的代码也是空中楼阁。

所以这一章,我会带你拆解CARLA的架构,讲清楚那几个核心概念,然后手把手把服务器跑起来。嗯,咱们一步步来。

2.1 CARLA架构解析:客户端-服务器模式

CARLA采用的是典型的客户端-服务器(Client-Server)架构。说白了,就是有一个后台程序负责渲染画面、模拟物理、管理所有车辆行人,这个叫服务器(Server)。然后你写的Python脚本,或者我们后面要用的SUMO,都是客户端(Client),它们向服务器发指令,比如“在坐标(100, 200)生成一辆车”、“给我返回当前所有红绿灯的状态”。

为什么要这么设计?

我刚开始接触时也觉得麻烦,为什么不能像玩游戏一样直接打开就玩?后来在项目中遇到一个场景:我需要同时控制10辆车做编队测试。如果每辆车都开一个独立的仿真窗口,资源根本扛不住。但客户端-服务器模式下,我只需要一个服务器,然后开10个客户端脚本分别控制不同的车,服务器统一管理物理碰撞和渲染。效率高太多了。

核心要点:

  • 服务器(Server):负责仿真世界的运行,包括物理引擎、渲染、传感器数据生成。通常在一台高性能机器上运行。
  • 客户端(Client):通过Python API向服务器发送命令,控制仿真逻辑。可以运行在同一台机器,也可以通过网络连接远程服务器。
  • 通信协议:基于TCP/IP,客户端和服务器通过定义好的协议交换数据。

我的小技巧:

调试阶段,我习惯把服务器和客户端开在同一台机器上,用localhost连接,省去网络延迟的干扰。等算法稳定了,再部署到远程服务器上跑大规模仿真。

2.2 核心概念:World、Actor、Sensor

这三个概念,是CARLA的基石。你后面写的每一行代码,几乎都离不开它们。我一个个讲清楚。

2.2.1 World(世界)

World就是整个仿真环境的容器。它包含了地图、天气、交通标志、所有动态物体(车辆、行人)等等。你可以把它理解成一个“大管家”,负责管理一切。

在代码里,我们通过客户端来获取当前的World对象:

import carla

# 连接服务器
client = carla.Client('localhost', 2000)
client.set_timeout(10.0)

# 获取世界对象
world = client.get_world()

拿到world之后,你就可以做很多事情了:改变天气、加载不同的地图、获取所有交通灯的状态。我记得有一次做雨雾天气下的感知测试,就是通过world对象直接设置天气参数,省去了手动配置的麻烦。

2.2.2 Actor(参与者)

Actor是World里所有能动的东西的总称。车辆、行人、自行车、交通标志、甚至摄像头,都是Actor。每个Actor都有一个唯一的ID,你可以通过这个ID来追踪和控制它。

生成一个Actor的典型流程是这样的:

# 获取蓝图库
blueprint_library = world.get_blueprint_library()

# 选择一辆车(比如奥迪A2)
vehicle_bp = blueprint_library.find('vehicle.audi.a2')

# 指定生成位置
spawn_point = world.get_map().get_spawn_points()[0]

# 生成车辆
vehicle = world.spawn_actor(vehicle_bp, spawn_point)

这里有个坑,我曾经踩过:生成位置不能重叠。如果你指定的spawn_point已经被其他Actor占用了,spawn_actor会返回None。所以,我一般会先检查一下spawn_point是否可用,或者用try-except捕获异常。

注意:

每个Actor都有生命周期。用完之后,记得调用destroy()方法销毁它,否则服务器上会残留一堆“幽灵车”,影响后续仿真性能。

2.2.3 Sensor(传感器)

Sensor是CARLA里最强大的部分之一。它模拟了真实世界中的各种传感器:摄像头、激光雷达、雷达、GPS、IMU等等。每个Sensor本质上也是一个Actor,所以生成方式和车辆一样。

但Sensor有个特殊的地方:它会持续产生数据。你需要注册一个回调函数来处理这些数据。比如,获取摄像头图像:

# 获取摄像头蓝图
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')

# 将摄像头附着在车辆上
camera = world.spawn_actor(camera_bp, carla.Transform(carla.Location(x=1.5, z=2.4)), attach_to=vehicle)

# 定义回调函数
def process_image(image):
    # 这里可以保存图像、显示图像、或者传给算法
    image.save_to_disk('_out/%06d.png' % image.frame)

# 开始监听
camera.listen(process_image)

嗯,这里要注意:回调函数不要做太重的计算。如果你在回调里做图像处理,会拖慢整个仿真循环。我一般只在回调里做数据缓存,然后另开一个线程去处理。

2.3 启动CARLA服务器

终于到了动手环节。启动服务器其实很简单,但有几个细节需要注意。

2.3.1 命令行启动

在CARLA的安装目录下,找到CarlaUE4.sh(Linux)或CarlaUE4.exe(Windows)。直接双击或者用命令行运行:

# Linux
./CarlaUE4.sh

# Windows
CarlaUE4.exe

你会看到一个3D渲染窗口弹出来,里面是一个小镇的俯视图。这就说明服务器启动成功了。

2.3.2 常用启动参数

直接启动虽然简单,但很多时候我们需要定制化。比如,你想在无头模式(没有图形界面)下运行,或者指定端口号。这时候就需要加参数了:

参数 说明 示例
-quality-level=Low 低画质模式,适合性能较差的机器 ./CarlaUE4.sh -quality-level=Low
-carla-rpc-port=3000 指定RPC端口(默认2000) ./CarlaUE4.sh -carla-rpc-port=3000
-carla-streaming-port=3001 指定传感器数据流端口(默认2001) ./CarlaUE4.sh -carla-streaming-port=3001
-RenderOffScreen 无头模式,不显示渲染窗口 ./CarlaUE4.sh -RenderOffScreen

我个人最常用的是-quality-level=Low-RenderOffScreen。为什么?因为我在服务器上跑大规模仿真时,不需要看画面,只看数据。低画质能省下不少GPU资源,让仿真跑得更快。

2.3.3 验证服务器是否启动成功

服务器启动后,你可以写一个简单的Python脚本来验证连接:

import carla

try:
    client = carla.Client('localhost', 2000)
    client.set_timeout(5.0)
    world = client.get_world()
    print("连接成功!当前地图:", world.get_map().name)
except Exception as e:
    print("连接失败:", e)

如果输出类似连接成功!当前地图: Town01,那就说明一切正常。

避坑指南:

我曾经遇到过一个问题:服务器启动后,客户端连接一直超时。排查了半天,发现是防火墙把2000端口给拦了。所以,如果你连不上,先检查一下端口是否开放。另外,如果你改了端口号,客户端连接时也要对应修改。

2.4 本章小结

这一章我们干了三件事:

  • 理解了CARLA的客户端-服务器架构,知道谁负责渲染,谁负责控制。
  • 掌握了World、Actor、Sensor这三个核心概念,以及它们之间的关系。
  • 学会了启动CARLA服务器,并验证连接是否成功。

下一章,我们会正式进入Python API的世界,写第一个控制车辆的程序。嗯,到时候你会发现,有了这一章的基础,后面的代码写起来会顺手很多。