第四章 数据预处理:时间戳对齐、坐标系统一与数据清洗

各位工程师朋友,咱们接着聊。仿真数据和实车数据,就像两个说不同方言的人——时间对不上、坐标各说各的、还夹杂着各种噪声。不做预处理,后面的分析全是白搭。我个人习惯把这一步叫做「数据翻译」,翻译不好,后面所有结论都是错的。

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轴
世界坐标系 地球参考点 东 北 天
车辆坐标系 车辆质心 前进方向 左侧 上方
传感器坐标系 传感器安装点 传感器朝向 传感器左侧 传感器上方

我的转换流程:

  1. 先把所有传感器数据转到车辆坐标系(标定参数已知)。
  2. 再把车辆坐标系转到世界坐标系(通过GPS/IMU的位姿信息)。
  3. 仿真数据如果已经是世界坐标系,直接对齐即可。

个人经验:我建议在转换时保留原始坐标系数据,不要覆盖。因为有时候你会发现,某个传感器的标定参数是错的,需要重新转换。保留原始数据,能省很多事。

# 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小时调试时间。
  • 保留原始数据:每次处理都生成新文件,不要覆盖。我见过太多人把原始数据搞丢了,最后只能重采。
  • 写处理日志:记录每一步做了什么、参数是什么、处理了多少数据。这样出了问题,你能快速定位。
  • 验证对齐结果:对齐后,把仿真和实车数据画在同一张图上,肉眼检查。如果两条曲线在关键区域(如急加速、急转弯)对不上,说明预处理有问题。

核心原则:数据预处理的目标不是让数据「好看」,而是让数据「可信」。你想想看,如果输入的数据都是错的,后面的仿真验证还有什么意义?

好了,这一章就到这里。数据预处理是基本功,但也是决定成败的关键。下一章我们聊聊怎么用对齐后的数据做误差分析——嗯,到时候见。

可可精品资料库