4、车辆与传感器管理:生成车辆、配置传感器、读取数据

好,咱们进入正题。这一章聊的是Carla仿真里最基础、也最核心的操作——怎么把车和传感器搭起来。

我个人习惯把这一块叫做「搭积木」。你想想看,仿真环境里没有车,那测什么?没有传感器,车就是个瞎子。所以,生成车辆、挂载传感器、再读取数据,这三步是闭环测试的起点。

4.1 生成车辆——别小看这一步

生成一辆车,代码就几行。但我见过不少新手在这栽跟头。

import carla

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

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

# 选一辆车
vehicle_bp = blueprint_library.filter('model3')[0]

# 找个出生点
spawn_points = world.get_map().get_spawn_points()
spawn_point = spawn_points[0]

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

嗯,这里要注意:spawn_points 列表可能为空。我在项目中遇到过,地图加载不完全时,出生点列表就是空的。所以,我建议你加个判断:

if not spawn_points:
    print("没有出生点,换个地图试试?")
    exit()

另外,filter('model3') 返回的是列表,取 [0] 之前最好确认一下长度。我曾经因为蓝图名写错,列表为空,程序直接崩了。后来我习惯先打印一下:

print(blueprint_library.filter('model3'))

4.2 配置传感器——相机、激光雷达、IMU

车有了,接下来装传感器。Carla里传感器也是蓝图,生成方式跟车辆一样。

4.2.1 相机

相机分好几种:RGB、深度、语义分割。最常用的是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 参数决定了传感器是固定在车上,还是固定在世界上。如果你不传这个参数,传感器就飘在空中,车一走它就留在原地了。我刚开始做的时候犯过这个错,跑了一圈回来发现相机还在起点……

4.2.2 激光雷达

激光雷达配置稍微复杂一点,参数多。

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

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

参数怎么调?我个人的经验是:

参数 说明 推荐值
channels 线数,16/32/64 32(平衡性能与精度)
range 探测距离(米) 50-100
points_per_second 每秒点数 100000-500000
rotation_frequency 旋转频率(Hz) 10-20

说白了,点云密度越高,数据越精确,但计算压力也越大。我一般先用32线、50米范围做调试,跑通了再调高。

4.2.3 IMU

IMU最简单,参数少。

imu_bp = blueprint_library.find('sensor.other.imu')
imu = world.spawn_actor(imu_bp, carla.Transform(), attach_to=vehicle)

IMU不需要特殊位置,直接装在车身上就行。它会返回加速度、角速度、朝向等信息。

4.3 传感器数据读取——回调函数是关键

传感器生成后,怎么拿到数据?Carla用的是回调机制。说白了,就是传感器每产生一帧数据,就自动调用你写好的函数。

# 相机回调
def camera_callback(image):
    # image 是 carla.Image 对象
    array = np.frombuffer(image.raw_data, dtype=np.uint8)
    array = array.reshape((image.height, image.width, 4))
    # 去掉alpha通道
    rgb_array = array[:, :, :3]
    cv2.imshow('Camera', rgb_array)
    cv2.waitKey(1)

camera.listen(camera_callback)

激光雷达的回调类似:

def lidar_callback(point_cloud):
    # point_cloud 是 carla.LidarMeasurement 对象
    points = np.frombuffer(point_cloud.raw_data, dtype=np.float32)
    points = points.reshape(-1, 4)  # x, y, z, intensity
    # 这里可以做点云处理
    print(f"收到 {len(points)} 个点")

lidar.listen(lidar_callback)

IMU的回调:

def imu_callback(imu_data):
    # imu_data 包含加速度、角速度、朝向
    acc = imu_data.accelerometer
    gyro = imu_data.gyroscope
    compass = imu_data.compass
    print(f"加速度: {acc}, 角速度: {gyro}")

imu.listen(imu_callback)

重要提醒: 回调函数里不要做耗时操作。比如保存图片到磁盘、做复杂的点云处理,这些会阻塞回调,导致数据丢失。我建议在回调里只做数据拷贝,处理逻辑放到另一个线程里。

小技巧: 如果你需要同步获取多个传感器的数据,可以用队列。每个回调把数据塞进队列,主循环里统一取出来处理。这样能保证数据的时间对齐。

注意: 传感器监听会持续占用CPU。如果你只是做单步测试,记得在不需要时调用 sensor.stop()sensor.destroy()。否则仿真会越来越卡。

4.4 完整示例——把车和传感器串起来

最后,给一个完整的代码骨架。你直接复制就能跑:

import carla
import numpy as np
import cv2

def main():
    client = carla.Client('localhost', 2000)
    client.set_timeout(10.0)
    world = client.get_world()

    # 生成车辆
    blueprint_library = world.get_blueprint_library()
    vehicle_bp = blueprint_library.filter('model3')[0]
    spawn_points = world.get_map().get_spawn_points()
    if not spawn_points:
        return
    vehicle = world.spawn_actor(vehicle_bp, spawn_points[0])

    # 配置相机
    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_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
    camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)

    # 配置激光雷达
    lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
    lidar_bp.set_attribute('channels', '32')
    lidar_bp.set_attribute('range', '50')
    lidar_transform = carla.Transform(carla.Location(x=0, z=2.0))
    lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)

    # 配置IMU
    imu_bp = blueprint_library.find('sensor.other.imu')
    imu = world.spawn_actor(imu_bp, carla.Transform(), attach_to=vehicle)

    # 回调函数
    def camera_callback(image):
        array = np.frombuffer(image.raw_data, dtype=np.uint8)
        array = array.reshape((image.height, image.width, 4))
        cv2.imshow('Camera', array[:, :, :3])
        cv2.waitKey(1)

    def lidar_callback(point_cloud):
        points = np.frombuffer(point_cloud.raw_data, dtype=np.float32)
        points = points.reshape(-1, 4)
        print(f"Lidar points: {len(points)}")

    def imu_callback(imu_data):
        print(f"Acc: {imu_data.accelerometer}")

    # 开始监听
    camera.listen(camera_callback)
    lidar.listen(lidar_callback)
    imu.listen(imu_callback)

    # 让车动起来
    vehicle.apply_control(carla.VehicleControl(throttle=0.5))

    # 跑一会儿
    import time
    time.sleep(10)

    # 清理
    camera.stop()
    lidar.stop()
    imu.stop()
    camera.destroy()
    lidar.destroy()
    imu.destroy()
    vehicle.destroy()

if __name__ == '__main__':
    main()

嗯,这个例子跑起来后,你会看到相机画面、激光雷达点云数量、IMU数据都在实时更新。这就是最基础的传感器数据流。

下一章我们会聊怎么用这些数据做闭环测试。说白了,传感器只是眼睛,怎么用眼睛看到的东西做决策,才是真正的挑战。