3、Python API入门:Client与World、Actor生命周期、传感器数据获取、车辆控制

好,咱们进入正题。这一章是实操的基础,说白了就是让你学会怎么用Python跟Carla这个仿真世界对话。我刚开始接触Carla时,也对着文档发懵,后来发现核心就四个东西:Client、World、Actor、传感器。把这几个玩明白了,后面的场景复现就是搭积木的事。

3.1 Client与World:连接仿真世界

先说说Client。你可以把它理解成一把钥匙,用来打开Carla服务器的大门。我个人习惯在脚本开头就建立连接,像这样:

import carla

# 连接Carla服务器,默认端口是2000
client = carla.Client('localhost', 2000)
client.set_timeout(10.0)  # 设置超时,我一般给10秒

嗯,这里要注意。如果你连不上,八成是Carla没启动,或者端口被占了。我曾经在项目里遇到过,开了好几个Carla实例,端口冲突,折腾了半天才发现。

连上之后,就能拿到World对象了。World就是整个仿真世界的句柄,地图、天气、车辆、行人,全归它管。

world = client.get_world()

你想想看,有了world,你就能干很多事。比如获取当前地图:

print(world.get_map().name)  # 比如输出 "Town03"

或者改变天气:

weather = carla.WeatherParameters(
    cloudiness=80.0,
    precipitation=30.0,
    sun_altitude_angle=45.0
)
world.set_weather(weather)

为什么我要提天气?因为场景复现时,光照和雨雾对传感器数据影响很大。我建议你每次复现前,都显式设置一下天气,避免环境不一致。

小技巧: 如果你不确定当前地图有哪些可用天气参数,可以用 world.get_weather() 先打印出来看看。

3.2 Actor生命周期:创建、控制、销毁

Actor是Carla里所有动态物体的统称——车辆、行人、传感器,都是Actor。它的生命周期很简单:创建 -> 控制 -> 销毁。但很多新手会漏掉最后一步,导致内存泄漏。

先看怎么创建一辆车:

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

# 选一辆车,比如奥迪
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)

这里有个坑。我曾经在复现一个多车场景时,发现车辆生成后位置不对。后来排查发现,get_spawn_points() 返回的点是固定的,但有些点可能被占用了。所以生成前最好检查一下:

if spawn_point is None:
    print("生成点无效,换个点试试")

车辆生成后,怎么控制它?两种方式:

  • 自动驾驶模式:vehicle.set_autopilot(True),Carla内置的交通管理器会接管。
  • 手动控制: 通过 vehicle.apply_control() 发送油门、刹车、转向指令。

手动控制的代码长这样:

control = carla.VehicleControl()
control.throttle = 0.5   # 油门 50%
control.steer = -0.3     # 左转 30%
control.brake = 0.0      # 不刹车
vehicle.apply_control(control)

你想想看,场景复现时,我们通常需要精确控制车辆的轨迹,所以手动控制更常用。我会把控制指令写到一个列表里,按时间步逐帧发送。

最后,别忘了销毁。用完的Actor一定要清理:

vehicle.destroy()
警告: 如果你在循环里不断生成Actor而不销毁,Carla的内存会暴涨。我曾经在跑一个长时间测试时忘了这茬,结果仿真越来越卡,最后直接崩了。所以,养成好习惯:用完就销毁。

3.3 传感器数据获取:让车“看见”世界

传感器是Carla的灵魂。没有传感器,你的车就是个瞎子。常用的传感器有相机、激光雷达、GPS、IMU等。获取数据的方式都是类似的:先创建传感器,再绑定回调函数。

拿RGB相机举例:

# 创建相机蓝图
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')

# 安装到车上,位置在车顶
transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, transform, attach_to=vehicle)

# 定义回调函数,处理图像数据
def process_image(image):
    # image是carla.Image对象
    # 可以保存为文件,或者转成numpy数组
    image.save_to_disk(f'output/{image.frame}.png')

# 开始监听
camera.listen(process_image)

这里有个关键点:listen() 是异步的。也就是说,传感器数据是源源不断流进来的,你的主循环可以继续做其他事。我个人习惯在回调函数里把数据存到队列里,主循环再从队列里取,这样不会阻塞。

激光雷达的用法类似,只是数据类型不同:

lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
lidar_bp.set_attribute('channels', '32')
lidar_bp.set_attribute('range', '50')

lidar = world.spawn_actor(lidar_bp, transform, attach_to=vehicle)

def process_lidar(data):
    # data是carla.LidarMeasurement对象
    # 可以转成numpy数组做点云处理
    points = np.frombuffer(data.raw_data, dtype=np.dtype('f4'))
    points = np.reshape(points, (int(points.shape[0] / 4), 4))

lidar.listen(process_lidar)
重要: 传感器数据是仿真精度的关键。我建议你在场景复现时,把传感器的所有参数(分辨率、视场角、采样率)都记录下来,这样别人才能完全复现你的结果。

3.4 车辆控制:让车动起来

车辆控制这块,刚才已经提了一点。这里再深入说说。Carla的车辆控制模型挺真实的,不是简单的“给油门就走”。它考虑了转向延迟、轮胎摩擦、空气阻力等因素。

常用的控制参数:

参数 范围 说明
throttle 0.0 ~ 1.0 油门,0是松油,1是地板油
steer -1.0 ~ 1.0 转向,负值左转,正值右转
brake 0.0 ~ 1.0 刹车,0是不刹,1是急刹
hand_brake True / False 手刹,一般用于停车
reverse True / False 倒车,需要配合油门使用

举个例子,让车匀速直线行驶:

control = carla.VehicleControl()
control.throttle = 0.4
control.steer = 0.0
control.brake = 0.0

# 在仿真循环里持续发送
while True:
    vehicle.apply_control(control)
    world.tick()  # 推进仿真时间

你可能会问,为什么不用 set_autopilot(True)?因为场景复现需要精确控制。自动驾驶模式会自己决定路线,你没法保证每次跑的轨迹都一样。所以,手动控制才是复现的王道。

我的经验: 如果你需要让车辆按特定轨迹行驶,可以先用Carla的录制功能跑一遍,把控制指令记录下来,然后回放时逐帧发送。这样能保证100%复现。

好了,这一章的内容就这些。Client连接、World管理、Actor生命周期、传感器数据、车辆控制,这五个点你掌握了,Carla的Python API就算入门了。下一章我们会把这些东西串起来,做一个完整的场景复现案例。