2、Carla仿真结果可视化与报告生成:理解Carla传感器数据格式(相机、LiDAR、雷达、GNSS、IMU)

各位同学,欢迎来到第二章。上一章我们搭好了Carla的环境,跑通了第一个仿真。但说实话,光跑起来没用,你得知道车「看」到了什么、「听」到了什么。这一章,我们就来拆解Carla里最核心的几种传感器数据格式。

我个人习惯是:先搞清楚数据长什么样,再谈怎么用。你想想看,连传感器吐出来的是啥都不知道,后面做融合、做可视化,那不都是瞎忙活吗?

2.1 相机数据:最直观,但坑最多

Carla里的相机传感器,说白了就是虚拟摄像头。它返回的数据格式,我直接说结论:一张H×W×4的numpy数组

等等,为什么是4通道?不是RGB三通道吗?嗯,这里要注意——Carla默认返回的是BGRA格式,不是我们熟悉的RGB。A是Alpha通道,透明度,一般用不上。但B和G的顺序是反的,你直接拿matplotlib显示,颜色会怪怪的。

避坑指南:我曾经在项目里直接拿Carla的相机数据去训练模型,结果模型死活不收敛。查了两天,发现是颜色通道顺序搞反了。所以记住:cv2.cvtColor(image, cv2.COLOR_BGRA2BGR) 或者 cv2.COLOR_BGRA2RGB,看你后续用什么库。

来看一段获取相机数据的典型代码:

import numpy as np
import cv2

# 假设 sensor_data 是 Carla 回调函数里拿到的数据
def process_camera_image(sensor_data):
    # 原始数据是 BGRA,shape 为 (H, W, 4)
    img = np.frombuffer(sensor_data.raw_data, dtype=np.uint8)
    img = img.reshape((sensor_data.height, sensor_data.width, 4))
    
    # 转成 BGR(OpenCV 用)或 RGB(matplotlib 用)
    bgr_img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
    return bgr_img

另外,相机还有几个参数你得心里有数:FOV(视场角)分辨率位置偏移。这些在传感器蓝图里配,我建议你一开始就用1280×720,别贪高分辨率,不然仿真跑起来卡得你怀疑人生。

3.2 LiDAR数据:点云,说白了就是一堆三维坐标

LiDAR,激光雷达。Carla里模拟的是旋转式激光雷达,数据格式是点云。每个点包含:x, y, z, intensity(强度值)。

我记得第一次拿到LiDAR数据时,心想:这不就是个N×4的数组吗?对,就是这么简单。但实际用起来,有几个细节要注意。

字段 含义 单位 备注
x 传感器坐标系下的X坐标 正前方
y 传感器坐标系下的Y坐标 正左方
z 传感器坐标系下的Z坐标 正上方
intensity 反射强度 0-1 受材质影响
小技巧:LiDAR的点云数据默认是传感器坐标系,不是世界坐标系。如果你要做全局地图拼接,记得用传感器自身的变换矩阵转一下。我一般用 sensor.get_transform() 拿到位置和旋转,然后用矩阵乘法转过去。

获取LiDAR数据的代码也很直接:

def process_lidar_data(sensor_data):
    # sensor_data 是 Carla 的 LidarMeasurement 对象
    points = np.frombuffer(sensor_data.raw_data, dtype=np.float32)
    points = points.reshape((-1, 4))  # 每一行是 [x, y, z, intensity]
    return points

这里有个坑:raw_data 里存的是 float32,不是 uint8。别跟相机搞混了。我曾经在代码里用 uint8 去解析LiDAR数据,结果出来的点云全在原点附近,吓我一跳。

2.3 雷达数据:速度信息是关键

雷达(Radar)在Carla里用得相对少一些,但做ADAS功能时绕不开。它的数据格式是一维数组,每个元素代表一个检测点,包含:方位角、俯仰角、距离、速度

说白了,雷达不直接给你x,y,z坐标,它给你的是球坐标系下的数据。你需要自己转成笛卡尔坐标。

def process_radar_data(sensor_data):
    # sensor_data 是 Carla 的 RadarMeasurement 对象
    for detection in sensor_data:
        azimuth = detection.azimuth   # 弧度
        altitude = detection.altitude # 弧度
        distance = detection.depth    # 米
        velocity = detection.velocity # 米/秒
        
        # 转成笛卡尔坐标
        x = distance * np.cos(altitude) * np.cos(azimuth)
        y = distance * np.cos(altitude) * np.sin(azimuth)
        z = distance * np.sin(altitude)
        
        # 你可以把 x,y,z,velocity 存起来
重点:雷达的速度是径向速度,也就是目标相对于雷达的接近/远离速度。正值表示远离,负值表示接近。这个在做碰撞预警时非常有用。

2.4 GNSS数据:经纬度,但别忘了高度

GNSS(全球导航卫星系统),就是咱们常说的GPS。Carla里返回的数据很简单:纬度、经度、海拔

但这里有个容易忽略的点:Carla的GNSS数据是基于UE4世界坐标转换过来的。它默认使用WGS84坐标系,但仿真地图的经纬度范围是固定的,不是真实的全球坐标。说白了,你拿到的经纬度只在当前地图里有效。

def process_gnss_data(sensor_data):
    lat = sensor_data.latitude
    lon = sensor_data.longitude
    alt = sensor_data.altitude
    # 注意:altitude 是海拔,单位米
    return lat, lon, alt

我个人习惯是:GNSS数据一般配合IMU一起用,做定位融合。单独用GNSS的话,精度不够,而且Carla里模拟的GNSS没有多路径效应,太理想了,别太当真。

2.5 IMU数据:加速度和角速度,但小心噪声

IMU(惯性测量单元)返回的是加速度角速度,都是三维向量。另外还有一个朝向(orientation),以四元数形式给出。

字段 含义 单位
accelerometer 三轴加速度 m/s²
gyroscope 三轴角速度 rad/s
orientation 四元数朝向 无单位
注意:Carla的IMU数据是理想值,没有加噪声。如果你要做真实的IMU仿真,需要自己加高斯噪声和漂移。我一般会在加速度上加0.1 m/s²的噪声,角速度上加0.01 rad/s的噪声,这样更接近真实传感器。

获取IMU数据的代码:

def process_imu_data(sensor_data):
    acc = sensor_data.accelerometer  # (x, y, z)
    gyro = sensor_data.gyroscope     # (x, y, z)
    orient = sensor_data.orientation # 四元数 (w, x, y, z)
    return acc, gyro, orient

2.6 数据对齐:所有传感器的时间戳

最后说一个所有传感器都有的共同点:时间戳。每个传感器数据都带一个 timestamp 字段,单位是秒。但注意,这个时间戳是仿真时间,不是真实时间。

为什么重要?因为你要做多传感器融合,必须把不同传感器的数据按时间对齐。我一般会维护一个缓冲区,把每个传感器的数据按时间戳排序,然后做插值或最近邻匹配。

我的经验:在Carla里,相机和LiDAR的帧率通常不一致。相机一般是20 FPS,LiDAR是10 FPS。如果你要做BEV(鸟瞰图)融合,建议以LiDAR的时间戳为基准,找最近的相机帧。别问我怎么知道的,调了三天才搞定。

好了,这一章的内容就到这里。总结一下:

  • 相机:BGRA格式,记得转通道
  • LiDAR:N×4的点云,传感器坐标系
  • 雷达:球坐标系,速度信息是关键
  • GNSS:经纬度+海拔,理想化数据
  • IMU:加速度+角速度,记得加噪声

下一章,我们会把这些传感器数据串起来,做真正的可视化。到时候你会看到,车在仿真世界里跑起来,各种数据流在屏幕上跳动,那感觉,嗯,挺有成就感的。