2、系统架构:Carla的客户端-服务器架构、UE4渲染引擎与Carla插件、Python API与C++ API的分工、核心模块(World、Actor、Sensor、Map)介绍

好,咱们正式开始啃Carla的骨架——系统架构。

说实话,我第一次接触Carla时,也被它这套“客户端-服务器”的玩法震住了。你想想看,一个仿真平台,居然把渲染和逻辑拆成两个进程跑。这设计到底图啥?

嗯,咱们今天就把这事掰扯清楚。

2.1 客户端-服务器架构:为什么非要“分家”?

Carla的核心设计理念,就是仿真引擎与用户逻辑分离

服务器端(Server)负责:

  • 运行UE4渲染引擎
  • 管理物理世界
  • 控制所有Actor(车辆、行人、传感器)
  • 处理传感器数据流

客户端(Client)负责:

  • 通过API向服务器发送指令
  • 接收传感器数据(图像、激光雷达点云等)
  • 运行你的算法(感知、规划、控制)

我个人习惯把这种架构比作“导演和演员”。服务器是舞台和演员,客户端是导演。导演不下场演戏,但通过喊话(API调用)控制一切。

关键点:一个服务器可以同时连接多个客户端。这在分布式仿真中非常有用——比如你让一个客户端跑感知算法,另一个客户端跑路径规划,互不干扰。

我在项目中遇到过一个问题:客户端和服务器跑在不同机器上,网络延迟导致传感器数据时间戳对不上。后来怎么解决的?把服务器和客户端部署在同一台高性能工作站上,用localhost通信,延迟降到1ms以内。

避坑指南:我曾经因为防火墙设置,客户端死活连不上服务器。检查一下端口号——默认是2000,但如果你同时跑多个实例,记得用 --carla-rpc-port=2001 指定不同端口。

2.2 UE4渲染引擎与Carla插件:为什么选虚幻4?

Carla基于Unreal Engine 4(UE4),这可不是随便选的。

UE4的优势:

  • 照片级渲染质量——传感器数据更真实
  • 物理引擎成熟——碰撞检测、车辆动力学都靠谱
  • 蓝图系统——方便非程序员搭建场景

Carla在UE4上做了一层插件(Carla Plugin),这层插件干了三件事:

  1. 接管了UE4的Tick循环——让仿真步长可控,不再是游戏里的可变帧率
  2. 暴露了底层接口——比如控制天气、时间、交通流
  3. 实现了传感器模拟——摄像头、激光雷达、IMU等,都是插件里写的

说白了,Carla插件就是UE4和自动驾驶算法之间的“翻译官”。

注意:UE4的版本锁定很严格。Carla 0.9.13对应UE4 4.26,Carla 0.9.14对应UE4 4.27。别自己升级UE4版本,否则插件编译会炸。我试过,血的教训。

2.3 Python API与C++ API的分工:各司其职

Carla提供了两套API:Python和C++。你可能会问:为什么搞两套?直接统一不好吗?

嗯,这里有个现实考量。

特性 Python API C++ API
开发效率 高(快速原型) 低(编译慢)
运行性能 中等(有GIL限制) 高(原生速度)
适用场景 算法验证、数据采集、教学 高性能仿真、生产部署
学习曲线 平缓 陡峭

我个人习惯是:先用Python搭原型,验证逻辑没问题后,再转C++做性能优化。比如写一个传感器融合算法,Python版可能跑10帧,C++版能跑到60帧。

举个例子,Python里创建一个车辆:

import carla

client = carla.Client('localhost', 2000)
world = client.get_world()
blueprint = world.get_blueprint_library().find('vehicle.tesla.model3')
spawn_point = world.get_map().get_spawn_points()[0]
vehicle = world.spawn_actor(blueprint, spawn_point)

换成C++,代码量差不多,但需要处理内存管理和编译依赖:

#include <carla/client/Client.h>
#include <carla/client/World.h>

auto client = carla::client::Client("localhost", 2000);
auto world = client.GetWorld();
auto blueprint = world.GetBlueprintLibrary()->Find("vehicle.tesla.model3");
auto spawn_point = world.GetMap()->GetSpawnPoints().front();
auto vehicle = world.SpawnActor(*blueprint, spawn_point);

我的建议:初学者先死磕Python API。等你需要做大规模并行仿真(比如同时跑100辆车)时,再考虑C++。别一开始就给自己上难度。

2.4 核心模块:World、Actor、Sensor、Map

这四个模块,是Carla的“四大天王”。你写任何仿真脚本,都绕不开它们。

2.4.1 World(世界)

World是Carla的根对象。它管理着:

  • 仿真状态(运行/暂停)
  • 天气和时间
  • 所有Actor的集合
  • 地图数据

获取World的代码很简单:

world = client.get_world()
world.set_weather(carla.WeatherParameters.ClearNoon)
world.tick()  # 推进一帧

我记得有一次调试,发现仿真时间总是不对。后来才意识到,world.tick() 默认步长是0.05秒(20Hz),但我的传感器设置的是30Hz。这就导致数据不同步。解决办法是手动设置步长:

settings = world.get_settings()
settings.fixed_delta_seconds = 0.03333  # 约30Hz
world.apply_settings(settings)

2.4.2 Actor(参与者)

Actor是仿真世界里所有“能动”的东西。包括:

  • 车辆(car、truck、motorcycle)
  • 行人(walker)
  • 传感器(camera、lidar、radar)
  • 交通标志(traffic_light、speed_limit)

每个Actor都有唯一的ID和Transform(位置+旋转)。你可以控制它的运动、速度、灯光等。

重要概念:Actor的生命周期由你管理。创建时用 spawn_actor,销毁时用 destroy。如果不销毁,服务器上会残留一堆“幽灵车”,拖慢性能。

2.4.3 Sensor(传感器)

Sensor是特殊的Actor。它不移动,但会“感知”环境并返回数据。

常用传感器:

  • RGB Camera:返回图像数据(numpy数组)
  • LIDAR:返回点云数据(numpy数组)
  • IMU:返回加速度和角速度
  • GNSS:返回GPS坐标

传感器通过回调函数工作。你注册一个回调,每次传感器有数据时,Carla就会调用它:

camera_bp = blueprint_library.find('sensor.camera.rgb')
camera = world.spawn_actor(camera_bp, transform, attach_to=vehicle)

def process_image(image):
    array = np.frombuffer(image.raw_data, dtype=np.uint8)
    array = array.reshape((image.height, image.width, 4))
    # 你的图像处理逻辑

camera.listen(lambda data: process_image(data))

嗯,这里要注意:回调函数里别做耗时操作。我曾经在回调里写了个深度学习推理,结果帧率从30fps掉到5fps。后来改成用队列缓存数据,另起线程处理。

2.4.4 Map(地图)

Map管理着仿真世界的道路网络。它包含:

  • 道路拓扑(车道线、交叉口、人行道)
  • 生成点(Spawn Points)——车辆可以出现的位置
  • 路点(Waypoints)——用于路径规划

获取地图信息:

map = world.get_map()
spawn_points = map.get_spawn_points()
waypoint = map.get_waypoint(vehicle.get_location())

我个人觉得Map模块最实用的功能是路点导航。你可以从一个路点出发,沿着车道往前走,生成一条路径:

waypoint = map.get_waypoint(location)
for i in range(10):
    waypoint = waypoint.next(2.0)[0]  # 向前走2米
    # 记录waypoint.transform

坑点waypoint.next() 返回的是一个列表,因为路口处可能有多个分支。记得取第一个元素,或者根据你的需求选择。

好了,系统架构这块就聊到这。说白了,Carla的设计思路很清晰:服务器管渲染和物理,客户端管算法和逻辑,API是桥梁,四大模块是积木。你只要理解了这层关系,后面写任何仿真脚本都不会迷路。