第四章 数据预处理:时间戳对齐、坐标系统一与数据清洗
各位工程师朋友,咱们接着聊。仿真数据和实车数据,就像两个说不同方言的人——时间对不上、坐标各说各的、还夹杂着各种噪声。不做预处理,后面的分析全是白搭。我个人习惯把这一步叫做「数据翻译」,翻译不好,后面所有结论都是错的。
4.1 时间戳对齐——让两个时钟同步
先说时间戳对齐。仿真数据的时间戳,通常是理想化的,比如每10ms一个点,整整齐齐。实车数据呢?CAN总线、以太网、激光雷达,每个传感器都有自己的时钟。你想想看,一个信号在CAN上是100ms周期,另一个在以太网上是50ms周期,它们的时间戳根本对不上。
核心问题:两个数据源的时间基准不同,需要统一到一个时间轴上。
我的做法:
- 线性插值法:适用于低速信号(如车速、方向盘转角)。把实车数据的时间戳作为基准,对仿真数据做线性插值。
- 最近邻法:适用于离散状态信号(如档位、开关信号)。直接取时间上最接近的那个值。
- 样条插值:适用于高精度需求(如加速度、角速度)。但要注意,样条插值可能引入过冲,我踩过这个坑。
避坑指南:我曾经在一个项目中,直接用线性插值处理转向角信号。结果发现,实车在急转弯时,插值出来的角度比实际大了5度。后来改用样条插值,才把误差控制在0.5度以内。所以,选插值方法前,先看看信号的物理特性。
# Python示例:时间戳对齐(线性插值)
import numpy as np
from scipy.interpolate import interp1d
# 仿真数据时间戳(理想)
sim_time = np.array([0.0, 0.01, 0.02, 0.03, ...])
sim_speed = np.array([0.0, 1.2, 2.5, 3.8, ...])
# 实车数据时间戳(非均匀)
real_time = np.array([0.005, 0.015, 0.028, 0.042, ...])
# 线性插值
f = interp1d(sim_time, sim_speed, kind='linear', fill_value='extrapolate')
aligned_speed = f(real_time)
4.2 坐标系统一——别让坐标系「打架」
坐标系统一,说白了就是让仿真和实车用同一个「语言」描述位置和姿态。仿真里常用世界坐标系(WGS84、UTM),实车传感器输出的是车辆坐标系(前、右、下)。
常见的坐标系:
| 坐标系 | 原点 | X轴 | Y轴 | Z轴 |
|---|---|---|---|---|
| 世界坐标系 | 地球参考点 | 东 | 北 | 天 |
| 车辆坐标系 | 车辆质心 | 前进方向 | 左侧 | 上方 |
| 传感器坐标系 | 传感器安装点 | 传感器朝向 | 传感器左侧 | 传感器上方 |
我的转换流程:
- 先把所有传感器数据转到车辆坐标系(标定参数已知)。
- 再把车辆坐标系转到世界坐标系(通过GPS/IMU的位姿信息)。
- 仿真数据如果已经是世界坐标系,直接对齐即可。
个人经验:我建议在转换时保留原始坐标系数据,不要覆盖。因为有时候你会发现,某个传感器的标定参数是错的,需要重新转换。保留原始数据,能省很多事。
# Python示例:车辆坐标系到世界坐标系(2D简化版)
import numpy as np
# 车辆坐标系下的点(x, y)
vehicle_point = np.array([1.0, 0.5])
# 车辆位姿(世界坐标系下的位置和航向角)
vehicle_x = 100.0
vehicle_y = 200.0
yaw = 0.3 # 弧度
# 旋转矩阵
R = np.array([[np.cos(yaw), -np.sin(yaw)],
[np.sin(yaw), np.cos(yaw)]])
# 转换到世界坐标系
world_point = R @ vehicle_point + np.array([vehicle_x, vehicle_y])
print(f"世界坐标: {world_point}")
4.3 数据清洗与插值——去伪存真
数据清洗,说白了就是「挑毛病」。实车数据里什么妖魔鬼怪都有:丢包、毛刺、跳变、饱和。仿真数据虽然干净,但有时也会因为模型简化产生异常值。
常见问题及处理:
- 丢包:CAN总线偶尔丢一帧,用插值补上。但连续丢包超过3帧,我建议直接标记为无效。
- 毛刺:传感器受电磁干扰,出现瞬时尖峰。用中值滤波或滑动平均。
- 跳变:比如车速从30km/h瞬间跳到80km/h,明显不合理。用差分阈值检测,超过物理极限就剔除。
- 饱和:传感器量程限制,比如加速度计只能测±2g,超过就截断。这种情况要标记,不能直接插值。
注意:我曾经遇到一个案例,激光雷达的点云数据里,有个点突然飞到100米外。用滑动平均滤波后,这个异常点被「平均」到了正常数据里,导致后续的障碍物检测出现误报。所以,清洗时一定要先做异常值检测,再做平滑处理。顺序不能反。
# Python示例:异常值检测与插值
import numpy as np
from scipy import signal
# 原始数据(含毛刺)
data = np.array([1.0, 1.1, 5.0, 1.2, 1.3, 1.1, 1.0])
# 中值滤波(窗口大小3)
filtered = signal.medfilt(data, kernel_size=3)
print(f"滤波后: {filtered}")
# 线性插值补全丢包(假设第3个点丢失)
time = np.array([0, 1, 2, 3, 4, 5, 6])
valid_mask = np.array([True, True, False, True, True, True, True])
valid_time = time[valid_mask]
valid_data = data[valid_mask]
interp_data = np.interp(time, valid_time, valid_data)
print(f"插值后: {interp_data}")
4.4 知识体系总览
下面这张图,是我自己总结的数据预处理流程。你把它打印出来贴在工位上,每次做数据对齐时看一眼,能少走很多弯路。
4.5 实战中的几个小建议
嗯,最后再唠叨几句。数据预处理这件事,看起来简单,但做起来全是细节。我总结了几条经验:
- 先看数据,再动手:别上来就写代码。先把数据可视化,看看时间戳分布、坐标范围、噪声特征。你花10分钟看图,可能省下2小时调试时间。
- 保留原始数据:每次处理都生成新文件,不要覆盖。我见过太多人把原始数据搞丢了,最后只能重采。
- 写处理日志:记录每一步做了什么、参数是什么、处理了多少数据。这样出了问题,你能快速定位。
- 验证对齐结果:对齐后,把仿真和实车数据画在同一张图上,肉眼检查。如果两条曲线在关键区域(如急加速、急转弯)对不上,说明预处理有问题。
核心原则:数据预处理的目标不是让数据「好看」,而是让数据「可信」。你想想看,如果输入的数据都是错的,后面的仿真验证还有什么意义?
好了,这一章就到这里。数据预处理是基本功,但也是决定成败的关键。下一章我们聊聊怎么用对齐后的数据做误差分析——嗯,到时候见。
可可精品资料库