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数据都在实时更新。这就是最基础的传感器数据流。
下一章我们会聊怎么用这些数据做闭环测试。说白了,传感器只是眼睛,怎么用眼睛看到的东西做决策,才是真正的挑战。