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显示,颜色会怪怪的。
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 | 受材质影响 |
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 | 四元数朝向 | 无单位 |
获取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 字段,单位是秒。但注意,这个时间戳是仿真时间,不是真实时间。
为什么重要?因为你要做多传感器融合,必须把不同传感器的数据按时间对齐。我一般会维护一个缓冲区,把每个传感器的数据按时间戳排序,然后做插值或最近邻匹配。
好了,这一章的内容就到这里。总结一下:
- 相机:BGRA格式,记得转通道
- LiDAR:N×4的点云,传感器坐标系
- 雷达:球坐标系,速度信息是关键
- GNSS:经纬度+海拔,理想化数据
- IMU:加速度+角速度,记得加噪声
下一章,我们会把这些传感器数据串起来,做真正的可视化。到时候你会看到,车在仿真世界里跑起来,各种数据流在屏幕上跳动,那感觉,嗯,挺有成就感的。