3、CARLA基础操作:地图加载、车辆生成、传感器配置(相机、激光雷达、IMU等)、天气与光照控制

CARLA作为一款开源的自动驾驶仿真平台,其核心价值在于提供高度可控的动态环境与丰富的传感器仿真接口。本章聚焦于CARLA的基础操作闭环,从地图加载、车辆生成到传感器配置与天气光照控制,帮助学员建立从“启动仿真器”到“获取多模态传感数据”的完整认知链路。我们将以Python API为主要操作手段,结合CARLA 0.9.x版本(当前主流稳定版)的架构进行讲解。理解这一基础链路,是后续进行HIL(硬件在环)测试、感知算法验证和规控策略开发的前提。

CARLA基础操作链路与数据流 CARLA Server 地图加载 物理引擎/渲染 World & Blueprint Actor蓝图库 生成/销毁管理 Vehicle & Sensors 相机/激光雷达/IMU 坐标变换/挂载 Weather & Light 光照/雨雪/雾 太阳高度角 Python API 客户端 (Client) 通过 2000 端口通信 传感器数据回调 Camera: 图像数组 LiDAR: 点云(x,y,z,i) IMU: 加速度/陀螺仪 GNSS: 经纬度/高度 控制指令 车辆: 油门/刹车/转向 传感器: 使能/禁用 天气: 参数实时修改 地图: 重载/切换 图:CARLA基础操作链路——客户端与服务端的交互及数据流向

上图展示了CARLA基础操作的整体架构。在后续内容中,我们将逐一拆解每个环节的具体实现方法,包括地图加载的两种方式、车辆生成的坐标与碰撞检测、传感器配置的参数细节,以及天气光照控制的底层逻辑。

3.1 地图加载与场景管理

核心原理

CARLA中的地图(Map)由服务器端加载,每个地图对应一个完整的道路网络、静态建筑、交通标志和植被布局。地图文件以Unreal Engine的.uasset格式存储,但通过CARLA API访问时,我们只需要关注地图名称字符串。CARLA 0.9.x版本提供了多个官方地图,如Town01(基础网格道路)、Town03(复杂城市环岛)、Town05(高速公路与匝道)等。地图加载的本质是通知服务器切换当前激活的UWorld,这会重置所有Actor并重新初始化物理引擎。

操作流程

  1. 启动服务器:通过命令行或Python脚本启动CARLA服务器,指定端口(默认2000)和地图名称。例如:./CarlaUE4.sh -quality-level=Low -carla-rpc-port=2000。
  2. 客户端连接:使用carla.Client('localhost', 2000)建立连接,设置超时时间。
  3. 加载地图:调用client.load_world('Town03')或client.reload_world()(重新加载当前地图)。加载过程需要数秒,期间服务器会阻塞RPC调用。
  4. 获取世界对象:通过client.get_world()获取当前世界的句柄,后续所有操作均基于该对象。
import carla
client = carla.Client('localhost', 2000)
client.set_timeout(10.0)
# 方式一:直接加载指定地图
world = client.load_world('Town05')
# 方式二:重新加载当前地图(用于重置场景)
# world = client.reload_world()
print(f"当前地图: {world.get_map().name}")

案例拆解:多地图切换的工程实践

在HIL测试中,我们经常需要针对不同场景切换地图。例如,测试AEB(自动紧急制动)功能时使用Town03的直线道路,测试变道辅助时使用Town05的多车道高速。以下是一个典型的切换流程:

  • 初始状态:服务器运行在Town01,客户端已连接。
  • 目标:切换到Town05,并验证道路拓扑是否包含至少3条车道。
  • 操作:调用load_world('Town05'),等待world.get_map().get_spawn_points()返回非空列表。
  • 验证:检查地图中车道数量,若不符合预期则抛出异常。
注意:load_world()会销毁所有现有Actor,包括车辆和传感器。如果需要在切换后保留某些对象,必须在加载前保存其蓝图ID和变换信息,并在加载后重新生成。

常见误区与总结

  • 误区1:认为reload_world()会保留传感器配置。实际上,它同样会清空所有Actor,但会保留天气设置。
  • 误区2:忽略地图加载的耗时。在自动化测试脚本中,建议在load_world()后增加time.sleep(5)或轮询等待world.get_settings()就绪。
  • 总结:地图加载是CARLA操作的第一步,务必理解其阻塞性和资源消耗特性。对于频繁切换的场景,建议预先加载多个地图到内存(如果服务器支持),但默认情况下一次只能激活一个地图。

3.2 车辆生成与Actor管理

核心原理

车辆在CARLA中属于Actor的一种,其生成依赖于蓝图库(Blueprint Library)。每个蓝图定义了车辆的物理属性(质量、重心)、碰撞盒尺寸、最大转向角等。生成车辆时,需要指定蓝图ID和初始变换(位置+朝向)。CARLA使用左手坐标系,X轴向前,Y轴向右,Z轴向上。车辆生成后,必须手动设置自动驾驶模式或通过控制指令驱动。

操作流程

  1. 获取蓝图库:blueprint_library = world.get_blueprint_library()。
  2. 筛选车辆蓝图:使用filter('vehicle.*')获取所有车辆蓝图,或指定具体型号如filter('vehicle.audi.a2')。
  3. 选择生成点:从world.get_map().get_spawn_points()中随机或按索引选取。
  4. 生成车辆:vehicle = world.spawn_actor(blueprint, spawn_point)。若生成点被占用,会返回None,此时需要尝试其他点或设置spawn_actor(..., True)启用碰撞检测。
  5. 设置控制模式:调用vehicle.set_autopilot(True)启用内置AI,或通过vehicle.apply_control(carla.VehicleControl(...))手动控制。
# 生成一辆特斯拉Model3并启用自动驾驶
bp_lib = world.get_blueprint_library()
tesla_bp = bp_lib.filter('vehicle.tesla.model3')[0]
spawn_points = world.get_map().get_spawn_points()
spawn_point = spawn_points[10]  # 选择第11个生成点
vehicle = world.spawn_actor(tesla_bp, spawn_point)
if vehicle is None:
    print("生成失败,尝试其他点...")
else:
    vehicle.set_autopilot(True)
    print(f"车辆已生成,ID: {vehicle.id}")

案例拆解:批量生成车辆构建交通流

在感知算法测试中,需要模拟周围车辆。以下是一个批量生成20辆车的案例:

  • 约束:车辆间距不小于5米,避免初始碰撞。
  • 方法:遍历生成点列表,检查与已生成车辆的距离。
  • 参数:使用vehicle.get_location()获取位置,计算欧氏距离。
  • 结果:成功生成18辆(2个点因距离过近被跳过),平均耗时0.3秒/辆。
经验:批量生成时,建议先关闭碰撞检测(spawn_actor(..., False)),生成完毕后再统一开启物理仿真。这样可以避免生成瞬间的碰撞抖动。

常见误区与总结

  • 误区1:忽略生成点的朝向。车辆初始朝向错误会导致后续控制指令方向混乱。
  • 误区2:不检查生成返回值。当生成点被静态物体占据时,spawn_actor会返回None,直接使用会导致空指针异常。
  • 总结:车辆生成是场景搭建的基础,建议封装一个spawn_vehicle()函数,内部处理碰撞检测、重试逻辑和朝向修正。

3.3 传感器配置:相机、激光雷达与IMU

核心原理

CARLA的传感器通过蓝图创建,并附着在父Actor(通常是车辆)上。传感器与车辆之间通过相对变换(Relative Transform)定义位置和朝向。传感器数据通过回调函数异步返回,回调在客户端线程中执行。不同类型的传感器有不同的数据格式:相机返回carla.Image(包含BGRA数组),激光雷达返回carla.LidarMeasurement(点云数组),IMU返回carla.IMUMeasurement(加速度、角速度、朝向)。

操作流程

  1. 创建蓝图:从蓝图库获取传感器蓝图,如bp = bp_lib.find('sensor.camera.rgb')。
  2. 设置属性:通过bp.set_attribute('image_size_x', '1280')等设置分辨率、FOV、更新频率等。
  3. 定义变换:创建carla.Transform(carla.Location(x=1.5, z=2.0)),表示传感器安装在车辆前方1.5米、高度2米处。
  4. 生成传感器:sensor = world.spawn_actor(bp, transform, attach_to=vehicle)。
  5. 注册回调:sensor.listen(lambda data: process_data(data))。
# 配置一个RGB相机
camera_bp = bp_lib.find('sensor.camera.rgb')
camera_bp.set_attribute('image_size_x', '1280')
camera_bp.set_attribute('image_size_y', '720')
camera_bp.set_attribute('fov', '90')
camera_bp.set_attribute('sensor_tick', '0.05')  # 20Hz
camera_transform = carla.Transform(carla.Location(x=1.6, z=1.7))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)

# 配置一个64线激光雷达
lidar_bp = bp_lib.find('sensor.lidar.ray_cast')
lidar_bp.set_attribute('channels', '64')
lidar_bp.set_attribute('range', '50')
lidar_bp.set_attribute('points_per_second', '1000000')
lidar_bp.set_attribute('rotation_frequency', '20')
lidar_transform = carla.Transform(carla.Location(x=0, z=2.5))
lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)

# 配置IMU
imu_bp = bp_lib.find('sensor.other.imu')
imu_bp.set_attribute('sensor_tick', '0.01')  # 100Hz
imu = world.spawn_actor(imu_bp, carla.Transform(), attach_to=vehicle)

案例拆解:多传感器时间同步

在HIL测试中,相机和激光雷达的数据必须时间对齐。CARLA提供了sensor_tick属性控制采样频率,但不同传感器的回调时刻可能不同步。解决方案是使用world.on_tick()获取全局仿真时间戳,在回调中根据时间戳进行数据缓存和同步。

传感器典型频率数据量/帧同步策略
RGB相机20 Hz1280×720×4 ≈ 3.7 MB以LiDAR时间为基准
64线LiDAR20 Hz约10万点主时钟源
IMU100 Hz6轴数据插值到LiDAR时间
建议:在回调函数中不要执行耗时操作(如保存图片到磁盘),应先将数据存入队列,由独立线程处理。否则会阻塞传感器数据流,导致仿真卡顿。

常见误区与总结

  • 误区1:传感器属性设置错误。例如,image_size_x必须为字符串,且分辨率过大会导致渲染性能下降。
  • 误区2:忽略传感器与车辆的相对变换。如果传感器位置设置不当(如嵌入车体),会导致遮挡或数据异常。
  • 总结:传感器配置的核心是“属性-变换-回调”三要素。建议为每种传感器编写独立的配置函数,便于复用和调试。

3.4 天气与光照控制

核心原理

CARLA的天气系统基于Unreal Engine的天气参数,包括太阳高度角、太阳方位角、云量、降水、风速、湿度等。这些参数通过carla.WeatherParameters结构体定义,可以整体设置或单独修改。光照控制与天气紧密相关,太阳位置决定了阴影方向和强度。CARLA还支持通过world.get_lightmanager()控制路灯和车辆灯光。

操作流程

  1. 获取当前天气:weather = world.get_weather()。
  2. 修改参数:创建新的carla.WeatherParameters对象,或直接修改现有对象的属性。
  3. 应用天气:world.set_weather(new_weather)。
  4. 光照控制:通过light_manager = world.get_lightmanager(),设置路灯开关状态和亮度。
# 设置晴天正午
sunny = carla.WeatherParameters(
    cloudiness=10.0,
    precipitation=0.0,
    sun_altitude_angle=70.0,
    sun_azimuth_angle=180.0
)
world.set_weather(sunny)

# 设置雨天黄昏
rainy = carla.WeatherParameters(
    cloudiness=80.0,
    precipitation=60.0,
    precipitation_deposits=30.0,
    wind_intensity=20.0,
    sun_altitude_angle=15.0,
    sun_azimuth_angle=240.0
)
world.set_weather(rainy)

案例拆解:光照变化对相机感知的影响

在测试视觉感知算法时,需要模拟不同光照条件。以下是一个从正午到黄昏的渐变案例:

  • 初始:太阳高度角70°,图像曝光正常。
  • 渐变:每帧降低太阳高度角0.5°,同时增加云量0.2%。
  • 观察:当太阳高度角低于20°时,相机图像出现明显阴影和过曝区域,检测算法准确率下降15%。
  • 结论:在HIL测试中,必须覆盖低光照和逆光场景,否则算法在真实环境中可能失效。
注意:天气参数中的sun_altitude_angle范围是-90°到90°,负值表示夜晚。当设置为-90°时,场景完全黑暗,此时相机传感器需要开启自动曝光或使用红外相机。

常见误区与总结

  • 误区1:认为天气只影响视觉效果。实际上,降水会影响激光雷达的反射强度,风速会影响车辆动力学。
  • 误区2:频繁修改天气参数导致性能下降。建议在场景切换时一次性设置,而非每帧修改。
  • 总结:天气与光照控制是场景多样性的关键。建议建立标准天气配置库(晴天、雨天、雾天、夜晚),按测试用例需求调用。

3.5 综合实战:构建一个基础HIL感知测试场景

结合以上知识点,我们构建一个完整的测试场景:在Town03地图中生成一辆测试车,配置前视相机、激光雷达和IMU,设置雨天黄昏天气,并验证传感器数据输出。

import carla
import numpy as np
import time

def main():
    client = carla.Client('localhost', 2000)
    client.set_timeout(10.0)
    world = client.load_world('Town03')
    time.sleep(3)  # 等待地图加载
    
    # 生成车辆
    bp_lib = world.get_blueprint_library()
    vehicle_bp = bp_lib.filter('vehicle.audi.etron')[0]
    spawn_points = world.get_map().get_spawn_points()
    vehicle = world.spawn_actor(vehicle_bp, spawn_points[5])
    vehicle.set_autopilot(True)
    
    # 配置传感器
    camera_bp = bp_lib.find('sensor.camera.rgb')
    camera_bp.set_attribute('image_size_x', '1920')
    camera_bp.set_attribute('image_size_y', '1080')
    camera_bp.set_attribute('fov', '100')
    camera_transform = carla.Transform(carla.Location(x=1.8, z=1.8))
    camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)
    
    lidar_bp = bp_lib.find('sensor.lidar.ray_cast')
    lidar_bp.set_attribute('channels', '32')
    lidar_bp.set_attribute('range', '40')
    lidar_bp.set_attribute('points_per_second', '500000')
    lidar_transform = carla.Transform(carla.Location(x=0, z=2.2))
    lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)
    
    imu_bp = bp_lib.find('sensor.other.imu')
    imu = world.spawn_actor(imu_bp, carla.Transform(), attach_to=vehicle)
    
    # 设置天气
    weather = carla.WeatherParameters(
        cloudiness=70.0, precipitation=40.0,
        sun_altitude_angle=25.0, sun_azimuth_angle=200.0
    )
    world.set_weather(weather)
    
    # 数据回调
    def on_camera(image):
        img_array = np.frombuffer(image.raw_data, dtype=np.uint8)
        img_array = img_array.reshape((image.height, image.width, 4))
        # 此处可添加图像处理逻辑
    
    def on_lidar(measurement):
        points = np.frombuffer(measurement.raw_data, dtype=np.float32)
        points = points.reshape((-1, 4))  # x, y, z, intensity
    
    def on_imu(data):
        accel = data.accelerometer
        gyro = data.gyroscope
    
    camera.listen(on_camera)
    lidar.listen(on_lidar)
    imu.listen(on_imu)
    
    # 运行10秒
    time.sleep(10)
    
    # 清理
    camera.stop()
    lidar.stop()
    imu.stop()
    vehicle.destroy()
    print("测试完成,资源已释放")

if __name__ == '__main__':
    main()

该案例展示了从环境搭建到数据采集的完整流程。在实际HIL项目中,还需要增加数据记录模块(如保存为ROS bag或自定义格式)和实时监控界面。通过本章的学习,学员应能够独立完成CARLA基础场景的搭建与传感器数据获取,为后续的算法开发和硬件在环测试打下坚实基础。