4、MDF文件读取:使用asammdf库读取MDF文件、通道选择与过滤、时间戳对齐处理
说到MDF文件,做标定的兄弟肯定不陌生。INCA采集出来的数据,大部分都是这个格式。我刚开始接触那会儿,面对一堆.mdf文件,只能用INCA自带的MDA打开看看,想做个批量处理?没门。
后来发现了asammdf这个库,说实话,真香。它让我能在Python里直接操作MDF文件,想怎么处理就怎么处理。今天我就把常用的几个操作分享给你。
4.1 安装与基础读取
先装库,这个简单:
pip install asammdf
装完之后,读取一个MDF文件只需要几行代码:
from asammdf import MDF
# 读取MDF文件
mdf = MDF('test_data.mdf')
# 查看文件基本信息
print(f"文件版本: {mdf.version}")
print(f"通道数量: {len(mdf.channels_db)}")
# 获取所有通道名
channels = list(mdf.channels_db.keys())
print(f"前10个通道: {channels[:10]}")
嗯,这里要注意。MDF文件有不同版本,老版本是MDF3,新版本是MDF4。asammdf都支持,但读取大文件时,MDF4的效率会高不少。我在项目里遇到过客户给的是MDF3格式,几百兆的文件读起来慢得让人抓狂。
mdf.convert('4.10') 就能搞定。
4.2 通道选择与过滤
一个MDF文件里可能有几百个通道,你不可能全读出来。我一般只挑需要的通道,这样内存占用小,处理也快。
4.2.1 按名称选择通道
# 选择单个通道
signal = mdf.get('EngineSpeed')
# 选择多个通道
signals = mdf.get(['EngineSpeed', 'VehicleSpeed', 'CoolantTemp'])
# 查看信号数据
print(signal.samples[:10]) # 前10个采样点
print(signal.timestamps[:10]) # 对应的时间戳
你想想看,如果每次都要手动输入通道名,那多麻烦。我通常先打印出所有通道名,然后用关键词过滤:
# 模糊匹配通道名
all_channels = list(mdf.channels_db.keys())
engine_channels = [ch for ch in all_channels if 'Engine' in ch]
print(f"与Engine相关的通道: {engine_channels}")
4.2.2 按时间范围过滤
有时候你只关心某一段数据,比如发动机某个工况下的表现。这时候按时间切一下:
# 截取时间范围 [10秒, 20秒]
start_time = 10.0
end_time = 20.0
# 方法1:直接切片
signal_cut = mdf.get('EngineSpeed').cut(start=start_time, stop=end_time)
# 方法2:对整个MDF文件切片
mdf_cut = mdf.cut(start=start_time, stop=end_time)
4.3 时间戳对齐处理
这才是重头戏。MDF文件里不同通道的采样频率可能不一样。比如发动机转速是100Hz,车速是10Hz,水温可能只有1Hz。你要做分析,必须先把它们对齐到同一个时间轴上。
4.3.1 理解时间戳差异
先看看原始数据长什么样:
# 查看不同通道的时间戳
rpm = mdf.get('EngineSpeed')
speed = mdf.get('VehicleSpeed')
print(f"转速时间戳前5个: {rpm.timestamps[:5]}")
print(f"车速时间戳前5个: {speed.timestamps[:5]}")
print(f"转速采样点数: {len(rpm.timestamps)}")
print(f"车速采样点数: {len(speed.timestamps)}")
你会发现,时间戳的间隔不一样,起始时间也可能有偏差。这就是为什么直接拿数据做计算会出问题。
4.3.2 重采样对齐
我常用的方法是重采样,把所有通道统一到同一个频率上:
import numpy as np
# 定义目标时间轴,比如100Hz,从0到30秒
target_freq = 100 # Hz
target_time = np.arange(0, 30, 1/target_freq)
# 对每个通道进行插值重采样
def resample_signal(signal, target_time):
"""将信号重采样到目标时间轴上"""
# 使用线性插值
resampled = np.interp(target_time,
signal.timestamps,
signal.samples)
return resampled
# 重采样所有通道
rpm_resampled = resample_signal(rpm, target_time)
speed_resampled = resample_signal(speed, target_time)
# 现在可以放心做计算了
# 比如计算转速和车速的比值
ratio = rpm_resampled / speed_resampled
4.3.3 使用asammdf内置对齐功能
其实asammdf自带了对齐功能,比自己写插值方便多了:
# 方法1:使用raster函数
# 将数据对齐到100Hz的栅格上
mdf_aligned = mdf.raster(raster=0.01) # 0.01秒 = 100Hz
# 方法2:指定时间轴
mdf_aligned = mdf.raster(raster=target_time)
# 对齐后直接获取数据
rpm_aligned = mdf_aligned.get('EngineSpeed').samples
speed_aligned = mdf_aligned.get('VehicleSpeed').samples
我个人更推荐用raster方法。它内部处理了边界情况,比如时间戳超出范围时会自动填充NaN,不会像自己写插值那样容易出bug。
4.4 实战:完整的读取与对齐流程
最后,我给你一个完整的示例。这是我项目里常用的模板:
from asammdf import MDF
import numpy as np
import matplotlib.pyplot as plt
def load_and_align_mdf(file_path, target_freq=100):
"""
读取MDF文件并对齐所有通道
参数:
file_path: MDF文件路径
target_freq: 目标采样频率(Hz)
返回:
aligned_data: 字典,键为通道名,值为对齐后的数据
time_axis: 对齐后的时间轴
"""
# 1. 读取文件
mdf = MDF(file_path)
# 2. 选择感兴趣的通道
channels_of_interest = [
'EngineSpeed',
'VehicleSpeed',
'AccelPedalPos',
'CoolantTemp'
]
# 3. 获取数据范围
start_time = 0
end_time = min(mdf.get(ch).timestamps[-1]
for ch in channels_of_interest)
# 4. 创建目标时间轴
time_axis = np.arange(start_time, end_time, 1/target_freq)
# 5. 对齐数据
aligned_data = {}
for ch in channels_of_interest:
signal = mdf.get(ch)
aligned_data[ch] = np.interp(time_axis,
signal.timestamps,
signal.samples)
# 6. 快速检查对齐效果
print(f"数据长度: {len(time_axis)} 个采样点")
print(f"时间范围: {start_time:.2f} ~ {end_time:.2f} 秒")
return aligned_data, time_axis
# 使用示例
data, time = load_and_align_mdf('test_data.mdf')
# 画个图看看效果
plt.figure(figsize=(12, 6))
plt.plot(time, data['EngineSpeed'], label='Engine Speed')
plt.plot(time, data['VehicleSpeed'], label='Vehicle Speed')
plt.xlabel('Time (s)')
plt.ylabel('Value')
plt.legend()
plt.show()
好了,MDF文件读取这块就讲这么多。说白了就是三步:读文件、选通道、对齐时间戳。你只要把这三步走稳了,后面做数据分析就顺风顺水了。
下一章我会讲怎么用这些对齐后的数据做标定分析,比如计算油耗、排放等关键指标。到时候你就知道,今天花时间把数据对齐好,绝对值!