4、数据采集与预处理:充放电数据采集、异常值处理、数据平滑与滤波

各位同学,欢迎来到第四讲。

前面我们聊了SOH的定义和基础理论,说实话,那些公式看着挺唬人。但真正到了工程落地,你会发现——数据才是爹。模型再漂亮,喂进去一堆垃圾数据,出来的结果就是一堆垃圾。我当年刚入行时,就吃过这个亏,辛辛苦苦调了一个月的模型,结果发现是采集板上的一个电阻虚焊了,数据全是毛刺。从那以后,我养成了一个习惯:拿到数据第一件事,不是建模,而是先看看数据本身干不干净。

这一章,我们就来聊聊数据采集与预处理。说白了,就是怎么把电池的「体检报告」拿到手,并且把上面的脏东西擦干净。

4.1 充放电数据采集:你得知道测什么

电池管理系统(BMS)里,我们能直接测到的物理量其实就三个:电压、电流、温度。但SOH估算需要的信息,全藏在这三个量随时间的变化曲线里。

采集什么?

  • 电压:单体电压和总电压。采样频率建议至少1Hz,做OCV(开路电压)曲线时,需要静置足够长时间(通常2小时以上)。
  • 电流:充放电电流。注意方向定义,我习惯用正数表示充电,负数表示放电。采样频率建议10Hz以上,因为电流变化快。
  • 温度:电芯表面温度或极柱温度。温度变化慢,0.1Hz就够了,但位置要选对——别贴在散热片上。
  • 时间戳:这个容易被忽略。没有时间轴,你后面做积分、做微分全白搭。

采集工况

我个人建议,做SOH标定时,至少采集以下几种工况的数据:

  1. 恒流恒压充电(CC-CV):这是最标准的充电协议,能清晰看到电压平台和容量拐点。
  2. 恒流放电:通常用1C或0.5C倍率,放到底。用来算实际容量。
  3. 动态工况(DST或UDDS):模拟真实驾驶场景。我遇到过有些电池,静态测试容量没问题,一跑动态工况就露馅。
  4. 静置阶段:充放电前后各静置30分钟以上,用来获取OCV。

核心原则:数据采集的「三同时」——同时刻、同频率、同精度。电压电流的时间戳必须对齐,否则你算出来的内阻和容量全是错的。

4.2 异常值处理:把「坏点」揪出来

数据采回来了,但里面肯定有脏数据。为什么?传感器漂移、通信丢包、电磁干扰、甚至接线端子松动,都会产生异常值。我见过最离谱的一次,一个电芯的电压读数突然跳到了100V,明显是ADC采样通道串扰了。

常见的异常值类型

类型 表现 原因
野点(Spike) 单个数据点突然跳变,然后恢复 电磁干扰、ADC毛刺
死值(Stuck) 连续多个点数值不变 传感器卡死、通信中断
漂移(Drift) 数值缓慢偏离真实值 温度漂移、老化
缺失值(NaN) 数据为空或无效 丢包、存储错误

怎么处理?

我常用的方法分三步走:

  1. 阈值过滤:设定物理上限。比如锂电池单体电压不会超过4.5V,低于2.5V。超出这个范围的,直接剔除。
  2. 差分检测:计算相邻点的差值。如果电压变化率超过0.5V/s(正常情况远小于这个值),判定为野点。我曾经用这个方法抓出过一个接触不良的故障。
  3. 插值填补:对于剔除的点和缺失值,用前后有效值的线性插值填补。注意,连续缺失超过5个点,就别插了,直接丢弃这段数据。

小技巧:不要一上来就用复杂的统计方法。先用最简单的3σ原则(拉依达准则)扫一遍,能干掉90%的明显异常。剩下的再用孤立森林或DBSCAN去处理。

4.3 数据平滑与滤波:把毛刺磨平

异常值处理完了,但数据还是「毛茸茸」的。尤其是电流信号,因为负载变化快,噪声很大。你想想看,如果直接用带毛刺的电流去做安时积分,容量误差会越积越大。

为什么要滤波?

说白了,我们要保留电池的真实动态特性,同时去掉高频噪声。电池本身是一个低通系统,电压电流的变化频率不会太高(通常低于1Hz)。所以,高频噪声就是我们要滤掉的东西。

常用的滤波方法

  • 移动平均滤波(Moving Average):最简单,窗口大小选5~10个点。优点是快,缺点是会引入相位延迟。我在做实时SOH估算时不太用这个,因为延迟会让我错过容量拐点。
  • 中值滤波(Median Filter):对野点特别有效。窗口大小选3或5。我习惯先做一次中值滤波,再做平滑,效果很好。
  • 一阶低通滤波(RC滤波):这是BMS里最常用的。公式很简单:y[n] = α * x[n] + (1-α) * y[n-1]。α越小,滤波越强,但响应越慢。我一般取α=0.1~0.3。
  • Savitzky-Golay滤波:这个比较高级,能保留数据的形状(峰值、谷值)。适合处理电压曲线,因为电压的拐点对SOH估算很重要。不过计算量稍大,嵌入式上慎用。

代码示例:一阶低通滤波

def low_pass_filter(data, alpha=0.1):
    """
    一阶低通滤波器
    :param data: 原始数据列表
    :param alpha: 滤波系数,0~1,越小越平滑
    :return: 滤波后的数据
    """
    filtered = [data[0]]  # 初始值取第一个点
    for i in range(1, len(data)):
        # 核心公式:新值 = α * 当前测量值 + (1-α) * 上一次滤波值
        y = alpha * data[i] + (1 - alpha) * filtered[-1]
        filtered.append(y)
    return filtered

# 举个栗子
raw_current = [10.2, 10.5, 11.8, 10.3, 9.9, 10.1]  # 模拟电流数据
smooth_current = low_pass_filter(raw_current, alpha=0.2)
print(smooth_current)
# 输出:[10.2, 10.26, 10.568, 10.5144, 10.39152, 10.333216]

注意:滤波不是万能的。如果你发现滤波后的数据跟原始数据偏差太大,或者相位延迟导致你算出的容量不对,那就得调整参数了。我曾经在一个项目中,把α设成了0.01,结果滤波后的电压曲线几乎成了一条直线,完全看不出充放电平台——那叫一个惨。

4.4 实战经验:数据预处理流水线

好了,理论讲完了。我给大家总结一下,在实际项目中,我一般按这个顺序处理数据:

  1. 原始数据入库:先存一份原始数据,别动它。万一后面处理错了,还能回滚。
  2. 时间戳对齐:把电压、电流、温度按时间戳重采样到同一频率。我通常用线性插值重采样到1Hz。
  3. 异常值剔除:阈值过滤 + 差分检测,干掉野点和死值。
  4. 缺失值插补:线性插值填补,连续缺失超过5个点则丢弃该段。
  5. 滤波平滑:先中值滤波(窗口3),再一阶低通滤波(α=0.2)。
  6. 数据切片:按充放电循环切分成独立片段,每个片段包含一次完整的充放电过程。

嗯,这一步做完,你的数据才算「能用」。下一章,我们会拿着这些干净的数据,去提取SOH相关的特征——比如容量、内阻、OCV斜率。到时候你就知道,前面这些预处理工作有多值了。

一句话总结:数据预处理不是锦上添花,而是雪中送炭。你花在数据清洗上的每一分钟,都会在模型精度上得到回报。

好,今天就到这儿。有问题咱们评论区见。