3、Carla世界基础:加载地图、设置天气、控制时间步长、理解世界坐标系

好,我们正式开始动手操作Carla了。这一章,我带你搞定仿真世界的底层配置。说白了,就是告诉Carla:「我要在什么样的路上跑,天是什么颜色,时间怎么流动,以及我的车到底在哪儿。」

这些基础操作,你后面每个测试用例都会用到。我刚开始用Carla的时候,就是在这几个地方踩了不少坑。嗯,咱们一个一个来。

3.1 加载地图:选对战场

Carla自带了好几个地图。每个地图的复杂度、道路类型、场景元素都不一样。我个人习惯把地图分成三类:

地图名称 特点 适用场景
Town01 基础网格状道路,简单直路+十字路口 新手入门、基础功能验证
Town03 有环岛、有高架、有复杂路口 中等复杂度测试、变道场景
Town05 高速公路+城市混合,车道多、车流大 高速场景、多车交互、回归测试
Town10HD 高清城市,建筑细节丰富,有行人 视觉感知测试、行人避让

加载地图的代码很简单,但有个细节我提醒你:

import carla

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

# 加载地图
world = client.load_world('Town03')

# 或者重新加载当前地图(重置所有状态)
client.reload_world()
注意: 我曾经在项目里直接用了 load_world 来重置场景,结果发现所有车辆、传感器状态都丢了。后来才搞清楚:load_world 是彻底换地图,reload_world 是重置当前地图。这两个别搞混了。

你想想看,如果你的回归测试脚本里,每次跑完用例都 load_world 一次,那地图加载时间就够你喝一壶的。我建议:只在切换地图时用 load_world,平时重置用 reload_world

3.2 设置天气:别让老天爷捣乱

天气对自动驾驶感知的影响,不用我多说吧?大雾、雨天、黄昏,这些场景必须覆盖。Carla的天气系统很灵活,你可以直接选预设,也可以自己调参数。

先看预设天气:

# 获取天气预设列表
weather_presets = [
    carla.WeatherParameters.ClearNoon,
    carla.WeatherParameters.CloudyNoon,
    carla.WeatherParameters.WetNoon,
    carla.WeatherParameters.WetCloudyNoon,
    carla.WeatherParameters.MidRainyNoon,
    carla.WeatherParameters.HardRainNoon,
    carla.WeatherParameters.SoftRainNoon,
    carla.WeatherParameters.ClearSunset,
]

# 设置天气
world.set_weather(carla.WeatherParameters.ClearNoon)

但说实话,预设天气有时候不够用。比如你想模拟「刚下过雨但地面还没干透」这种状态,就得自己调参数:

# 自定义天气
weather = carla.WeatherParameters(
    cloudiness=30.0,        # 云量 0-100
    precipitation=0.0,      # 降水量 0-100
    precipitation_deposits=60.0,  # 地面积水 0-100
    wind_intensity=10.0,    # 风速 0-100
    sun_altitude_angle=45.0 # 太阳高度角 -90到90
)
world.set_weather(weather)
小技巧: 我习惯在回归测试脚本里,把天气参数写进配置文件。比如 weather_config.yaml 里定义好「晴天」「小雨」「大雾」三档。这样测试用例只需要读配置,不用改代码。

为什么会特别关注地面积水?因为积水会导致车道线反光、反射倒影,很多视觉算法在这上面翻过车。我在做AEB测试时,就遇到过因为积水反射导致误制动的情况。嗯,这个坑你得记住。

3.3 控制时间步长:让仿真跑得稳

时间步长,说白了就是仿真世界「滴答」一下,现实世界过了多久。这个参数直接影响你的仿真精度和速度。

Carla有两种模式:

  • 同步模式(Synchronous Mode):你控制每一步。你调用 world.tick(),仿真才前进一个步长。适合需要精确控制的场景,比如回归测试。
  • 异步模式(Asynchronous Mode):Carla自己跑,你只管读数据。适合可视化演示,但数据时序不可控。

我个人强烈建议:做仿真测试,必须用同步模式。为什么?

# 设置同步模式
settings = world.get_settings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05  # 50ms一个步长,即20FPS
world.apply_settings(settings)

# 手动推进仿真
for i in range(100):
    world.tick()
    # 在这里读取传感器数据、做决策
    # 每一步都是确定的,可复现的

核心要点: 同步模式下,同样的代码、同样的初始条件,跑出来的结果完全一样。这是回归验证的基石。你想想看,如果每次跑结果都不一样,你怎么判断这次改代码是修了bug还是引入了新bug?

固定步长选多少?我一般这么选:

  • 0.05秒(20FPS):大多数场景够用,计算量适中
  • 0.025秒(40FPS):需要高精度碰撞检测时用,比如AEB测试
  • 0.1秒(10FPS):只做路径规划验证,不关心细节时用
注意: 同步模式下,如果你忘了调 world.tick(),仿真就卡死了。我曾经在调试一个多车协同场景时,忘了给其中一辆车发tick指令,结果整个仿真卡了半小时。嗯,从那以后我都在脚本开头加一个超时保护。

3.4 理解世界坐标系:别让车跑偏了

Carla用的是UE4的坐标系,跟一般的自动驾驶坐标系有点不一样。你得记住这个:

  • X轴:向前(车头方向)
  • Y轴:向右
  • Z轴:向上

单位是厘米?不,是米。Carla里所有距离单位都是米。

获取车辆位置和朝向:

# 获取车辆的位置和朝向
vehicle = world.get_actors().filter('vehicle.*')[0]
transform = vehicle.get_transform()

location = transform.location  # carla.Location(x, y, z)
rotation = transform.rotation  # carla.Rotation(pitch, yaw, roll)

print(f"位置: x={location.x:.2f}, y={location.y:.2f}, z={location.z:.2f}")
print(f"朝向: yaw={rotation.yaw:.2f}°")

这里有个容易搞混的地方:yaw角。Carla里yaw=0表示正X方向,逆时针为正。也就是说:

  • yaw=0:朝正X
  • yaw=90:朝正Y(向右)
  • yaw=-90:朝负Y(向左)
  • yaw=180:朝负X(向后)

我在做路径规划测试时,经常需要把全局路径点转换成车辆坐标系。这时候就要用到 get_transform() 的逆矩阵:

# 将世界坐标点转换到车辆坐标系
world_point = carla.Location(x=100, y=50, z=0)
vehicle_transform = vehicle.get_transform()

# 获取车辆坐标系下的相对位置
relative_point = vehicle_transform.inverse_transform(world_point)
print(f"相对位置: x={relative_point.x:.2f}, y={relative_point.y:.2f}")
经验之谈: 我建议你在测试脚本里,每次设置完车辆初始位置后,都打印一下坐标和朝向。肉眼确认一下,别让车一开始就扎进绿化带里。这种事我干过不止一次。

3.5 实战组合:一个完整的初始化流程

好了,把上面这些串起来,就是一个标准的仿真初始化流程。我一般这样写:

def init_simulation(map_name='Town03', weather_preset='ClearNoon', dt=0.05):
    # 1. 连接并加载地图
    client = carla.Client('localhost', 2000)
    client.set_timeout(10.0)
    world = client.load_world(map_name)
    
    # 2. 设置天气
    weather_map = {
        'ClearNoon': carla.WeatherParameters.ClearNoon,
        'RainyNoon': carla.WeatherParameters.MidRainyNoon,
        'Sunset': carla.WeatherParameters.ClearSunset,
    }
    world.set_weather(weather_map.get(weather_preset, carla.WeatherParameters.ClearNoon))
    
    # 3. 设置同步模式和时间步长
    settings = world.get_settings()
    settings.synchronous_mode = True
    settings.fixed_delta_seconds = dt
    world.apply_settings(settings)
    
    # 4. 等待一帧,让世界稳定下来
    world.tick()
    
    return client, world

这个函数,我每个测试脚本都会复用。你把它放在一个 carla_utils.py 里,后面写测试用例就省事多了。

嗯,这一章的内容就这些。你先把地图加载、天气设置、时间步长、坐标系这几个基础打牢。下一章,我们开始往世界里放车、放传感器,真正跑起来。