3、Carla基础操作:世界与蓝图、生成车辆与行人、控制传感器、获取仿真数据、简单交通流模拟

好,咱们正式开始动手操作Carla。这一章是基础中的基础,但也是整个仿真流程的骨架。说白了,你后面所有花里胡哨的标注、自动标注、模型训练,都得建立在这些操作之上。我个人习惯,每次开新项目,都会先把这一套流程跑一遍,确保环境没问题。

3.1 世界与蓝图:Carla的「上帝视角」

Carla里的「世界」(World)是什么?你可以把它理解成整个仿真宇宙的容器。它管理着地图、天气、时间,还有所有你生成出来的东西。而「蓝图」(Blueprint)呢,就是物体的「出生证明」——它定义了车辆长什么样、行人穿什么衣服、传感器有什么参数。

先看怎么拿到这个世界:

import carla

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

# 获取当前世界
world = client.get_world()

嗯,这里要注意。如果你同时开了多个Carla窗口,get_world()拿到的可能是你最后激活的那个。我在项目中遇到过,调试半天发现车怎么不动了,结果连错了世界。

蓝图库怎么用?很简单:

# 获取所有可用的蓝图
blueprint_library = world.get_blueprint_library()

# 筛选出所有车辆蓝图
vehicle_bps = blueprint_library.filter('vehicle.*')

# 挑一辆你喜欢的车
bp = blueprint_library.find('vehicle.tesla.model3')

你想想看,这个蓝图库其实就是一个巨大的超市货架。你想要什么车、什么颜色的行人、什么型号的摄像头,都能在这里找到。我个人建议,刚开始做实验时,用filter方法缩小范围,别一次性把几千个蓝图全打印出来,眼睛会花。

3.2 生成车辆与行人:让世界活起来

有了蓝图,下一步就是「生」出来。Carla里生成物体用的是spawn_actor方法。你需要指定蓝图和位置。

import random

# 获取一个随机生成点
spawn_points = world.get_map().get_spawn_points()
spawn_point = random.choice(spawn_points)

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

这里有个坑,我踩过好几次。Carla的生成点(Spawn Point)是地图预定义的,不是随便一个坐标都能用。如果你用自定义坐标,很可能车直接卡在地底下或者飞到天上。我曾经为了省事,直接写死坐标,结果车悬在半空中,轮子还在转,看着特别诡异。

生成行人稍微复杂一点:

# 获取行人蓝图
walker_bp = random.choice(blueprint_library.filter('walker.pedestrian.*'))

# 行人需要特殊的位置(通常在人行道上)
walker_spawn_point = carla.Transform()
walker_spawn_point.location = world.get_random_location_from_navigation()

# 生成行人
walker = world.spawn_actor(walker_bp, walker_spawn_point)

为什么行人要用get_random_location_from_navigation()?因为行人不能站在马路中间啊。这个方法会自动帮你找一个合法的步行区域。说白了,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')

# 把摄像头装在车顶前方
camera_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)

注意这个attach_to参数。它把传感器「绑定」到了车上。车动,传感器跟着动。你想想看,如果你把摄像头固定在世界坐标里,车一开出去,摄像头就拍不到车了,那还仿什么真。

激光雷达的配置更讲究:

lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
lidar_bp.set_attribute('channels', '64')
lidar_bp.set_attribute('range', '100')
lidar_bp.set_attribute('points_per_second', '1000000')

lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.4))
lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)

这里channels是线数,64线比16线稠密得多,但计算量也大。我在项目中遇到过,为了追求点云密度,把points_per_second设得太高,结果仿真帧率直接掉到个位数。嗯,这里要平衡。

3.4 获取仿真数据:把「看到的」存下来

传感器装好了,怎么拿数据?Carla用的是回调函数机制。传感器一有数据,就会调用你注册的函数。

import numpy as np

# 定义回调函数
def process_camera_data(image):
    # 把图像数据转成numpy数组
    array = np.frombuffer(image.raw_data, dtype=np.uint8)
    array = array.reshape((image.height, image.width, 4))
    # 去掉alpha通道
    array = array[:, :, :3]
    # 保存图片
    image.save_to_disk(f'output/{image.frame:06d}.png')

# 注册回调
camera.listen(process_camera_data)

激光雷达的数据处理类似:

def process_lidar_data(point_cloud):
    # 点云数据格式:x, y, z, intensity
    points = np.frombuffer(point_cloud.raw_data, dtype=np.float32)
    points = points.reshape(-1, 4)
    # 保存为npy文件
    np.save(f'output/lidar_{point_cloud.frame:06d}.npy', points)

lidar.listen(process_lidar_data)

这里有个细节。Carla的图像数据是BGRA格式的,不是常见的RGB。我第一次用的时候,直接显示出来,颜色全不对,折腾了半天才发现是通道顺序的问题。你如果要用OpenCV显示,记得用cv2.COLOR_BGRA2BGR转换一下。

3.5 简单交通流模拟:让车自己跑起来

单辆车静止不动,那叫摆拍。真正的仿真需要交通流。Carla提供了Traffic Manager,专门管理车辆的行为。

# 创建Traffic Manager
traffic_manager = client.get_trafficmanager(8000)

# 设置全局速度限制(km/h)
traffic_manager.global_percentage_speed_difference(10.0)

# 让车辆自动驾驶
vehicle.set_autopilot(True, traffic_manager)

就这么简单?对,就这么简单。Traffic Manager会自动控制车辆的油门、刹车、转向,还会遵守红绿灯、避让行人。你想想看,如果你要自己写一套交通流逻辑,那得写到猴年马月去。

批量生成车辆并开启自动驾驶:

# 生成10辆车
for i in range(10):
    bp = random.choice(vehicle_bps)
    spawn_point = random.choice(spawn_points)
    vehicle = world.spawn_actor(bp, spawn_point)
    vehicle.set_autopilot(True, traffic_manager)

这里要注意,生成点不能重复。如果两个车生成在同一个位置,它们会「融合」在一起,物理引擎会爆炸。我遇到过,两辆车重叠后,直接弹射到天上去了,画面特别喜感。

行人的交通流稍微不同,需要手动控制:

# 让行人沿着导航路径行走
walker_controller = world.spawn_actor(
    blueprint_library.find('controller.ai.walker'),
    carla.Transform(),
    attach_to=walker
)

walker_controller.start()
walker_controller.go_to_location(
    world.get_random_location_from_navigation()
)

行人的AI控制器和车辆的Traffic Manager是两套系统。我刚开始以为行人也能用set_autopilot,结果报错了。嗯,Carla里行人和车辆的控制逻辑是分开的,别搞混。

小技巧: 如果你想让交通流更真实,可以调整Traffic Manager的参数。比如set_percentage_speed_difference控制速度变化范围,set_global_distance_to_leading_vehicle控制跟车距离。我一般会把跟车距离设小一点,这样车流更密集,更接近城市路况。
注意: 生成大量车辆和行人会消耗大量显存和CPU。我建议先从小规模开始(5-10辆车),确认流程没问题后再增加数量。另外,记得在循环结束时销毁所有Actor,否则Carla服务器会越来越卡。

好了,这一章的内容就这些。你掌握了世界与蓝图、生成物体、控制传感器、获取数据、交通流模拟,基本上Carla的核心操作你就都摸到了。下一章我们会把这些基础组合起来,做一个完整的仿真数据采集流水线。