3. 塔架振动数据预处理与异常段截取

为什么原始振动时序不能直接做频谱

塔架振动在线监测系统通常以 50~200 Hz 连续采样,原始数据里同时混着四类成分:塔架结构响应、风轮转频及其谐波、传感器温漂与安装基线漂移、以及启停/偏航/变桨等瞬态过程。直接对整段数据做 FFT,会因趋势项造成低频能量泄漏、因瞬态冲击抬高宽带底噪、因转速变化使 1P/3P 谱峰展宽甚至分裂。因此预处理的目标很明确:保留结构响应与稳态转频成分,压制趋势、噪声与瞬态段,并按转速工况切出稳定运行段。

处理链与关键参数

本例采用一条可复现的处理链,参数按 100 Hz 采样、塔架一阶频率约 0.28 Hz 的典型海上风机设定(示例参数,需按实际机组调整):

  • 去趋势:对每段数据做一阶多项式最小二乘拟合后相减,消除温漂与基线漂移;对长记录可分段去趋势,段长取 60 s,避免把真实的低频结构响应一并减掉。
  • 低通滤波:4 阶 Butterworth 低通,截止 20 Hz,零相位(前后向)滤波,避免相位延迟影响后续峰值时刻判读;20 Hz 已覆盖塔架前几阶与 3P 上限。
  • 重采样:统一降到 50 Hz(满足 Nyquist 对 20 Hz 的 2.5 倍余量),便于多测点、多设备数据对齐。
  • 工况截取:以 SCADA 10 min 平均转速为工况标签,取转速在目标工况 ±3% 内、且持续 ≥120 s 的区段;同时剔除发电机并网/脱网前后 60 s、偏航动作期间、变桨速率超限时段。
判断稳定段的核心不是"转速看起来平",而是三个条件同时成立:转速波动带内、无启停/偏航标志位、段长足够支撑频率分辨率。频率分辨率 Δf = 1/T,要分辨 0.28 Hz 与 0.30 Hz,段长至少需 1/0.02 = 50 s,工程上取 120 s 以上更稳妥。
原始振动时序 → 干净分析数据集 原始振动时序 100 Hz · 加速度 含趋势/噪声/瞬态 前后向 + 侧向 去趋势 一阶多项式拟合 分段 60 s 消除温漂/基线 低通滤波 4 阶 Butterworth 截止 20 Hz 零相位(前后向) 重采样 100 Hz → 50 Hz 抗混叠前置 多测点对齐 工况截取 转速 ±3% 带内 持续 ≥120 s 剔除启停/偏航 转速工况时间轴与稳定段截取(示例) n t +3% −3% 启机爬升 剔除 偏航扰动 剔除 停机 剔除 稳定段 A(≥120 s) 保留 稳定段 B(≥120 s) 保留 T ≥ 120 s → Δf ≤ 0.008 Hz 输出:干净分析数据集 · 每段:前后向 + 侧向双通道,50 Hz · 元数据:转速均值/波动、段起止时刻、剔除原因 · 段长 ≥120 s,满足 0.28 Hz 与 0.30 Hz 分辨 · 供后续频谱分析直接调用 截取结果自检 ✓ 段内转速标准差 / 均值 < 1% ✓ 无并网/脱网、偏航、变桨速率超限标志 ✓ 去趋势后均值 ≈ 0,无残余线性斜率 ✓ 滤波后 20 Hz 以上能量衰减 > 40 dB

可复现的处理实现

下面给出一个自洽的最小实现,输入为一段示例振动序列与同步转速序列,输出为按工况切好的稳定段列表。示例数据用合成信号构造(含趋势、1P/3P、塔架一阶与启停瞬态),便于验证处理链是否正确,不代表任何实测结果。

import numpy as np
from scipy import signal

FS_RAW = 100.0          # 原始采样率 Hz
FS_OUT = 50.0           # 目标采样率 Hz
FC     = 20.0           # 低通截止 Hz
SEG_S  = 60.0           # 去趋势分段长度 s
MIN_LEN_S = 120.0       # 稳定段最短长度 s
RPM_TOL   = 0.03        # 转速容差 ±3%

def make_demo(n_sec=1800, seed=0):
    """合成示例:趋势 + 1P/3P + 塔架一阶 + 启停/偏航瞬态"""
    rng = np.random.default_rng(seed)
    t = np.arange(0, n_sec, 1/FS_RAW)
    # 转速:0-120s 启机,120-1500s 稳定 9.6rpm,1500-1560 偏航,1560-1800 稳定
    rpm = np.full_like(t, 9.6)
    rpm[t < 120] = np.linspace(0, 9.6, (t < 120).sum())
    rpm[(t >= 1500) & (t < 1560)] = 9.6 + 0.8*np.sin(2*np.pi*(t[(t>=1500)&(t<1560)]-1500)/30)
    f1p = rpm / 60.0
    # 塔架一阶 0.28 Hz,1P≈0.16 Hz,3P≈0.48 Hz
    x = (0.02*t                                  # 温漂趋势
         + 0.8*np.sin(2*np.pi*0.28*t)            # 塔架一阶
         + 0.5*np.sin(2*np.pi*f1p*t)             # 1P
         + 0.3*np.sin(2*np.pi*3*f1p*t)           # 3P
         + 0.05*rng.standard_normal(t.size))     # 宽带噪声
    # 启停瞬态冲击
    x[t < 120] += 1.5*np.exp(-((t-60)/8)**2)
    return t, x, rpm

def detrend_seg(x, fs, seg_s=SEG_S):
    """分段一阶多项式去趋势"""
    n = int(seg_s*fs)
    y = x.copy()
    for i in range(0, len(x), n):
        seg = x[i:i+n]
        if len(seg) < 10:
            continue
        k = np.arange(len(seg))
        p = np.polyfit(k, seg, 1)
        y[i:i+n] = seg - np.polyval(p, k)
    return y

def lowpass_zero_phase(x, fs, fc=FC, order=4):
    b, a = signal.butter(order, fc/(fs/2), btype='low')
    return signal.filtfilt(b, a, x)

def resample(x, fs_in, fs_out):
    g = np.gcd(int(fs_in), int(fs_out))
    return signal.resample_poly(x, int(fs_out)//g, int(fs_in)//g)

def extract_stable(t, x, rpm, fs, min_len_s=MIN_LEN_S, tol=RPM_TOL):
    """按转速工况截取稳定段,剔除启停/偏航"""
    rpm_mean = np.mean(rpm[rpm > 0.5]) if np.any(rpm > 0.5) else 0
    if rpm_mean == 0:
        return []
    in_band = np.abs(rpm - rpm_mean) / rpm_mean <= tol
    # 剔除转速变化率过大的点(启停/偏航)
    drpm = np.abs(np.gradient(rpm, t))
    smooth = in_band & (drpm < 0.02)
    segs, start = [], None
    for i, ok in enumerate(smooth):
        if ok and start is None:
            start = i
        elif not ok and start is not None:
            if (i - start)/fs >= min_len_s:
                segs.append((start, i))
            start = None
    if start is not None and (len(smooth)-start)/fs >= min_len_s:
        segs.append((start, len(smooth)))
    return [(t[a], t[b-1], x[a:b]) for a, b in segs]

# ---- 执行 ----
t, x, rpm = make_demo()
x_dt = detrend_seg(x, FS_RAW)
x_lp = lowpass_zero_phase(x_dt, FS_RAW)
x_rs = resample(x_lp, FS_RAW, FS_OUT)
t_rs = np.arange(len(x_rs)) / FS_OUT
rpm_rs = resample(rpm, FS_RAW, FS_OUT)

segs = extract_stable(t_rs, x_rs, rpm_rs, FS_OUT)
print(f"原始时长 {len(x)/FS_RAW:.0f}s,输出采样率 {FS_OUT}Hz,稳定段数 {len(segs)}")
for i, (a, b, s) in enumerate(segs):
    print(f"  段{i+1}: {a:.1f}s ~ {b:.1f}s, 长度 {b-a:.1f}s, 均值 {s.mean():.4f}, 标准差 {s.std():.4f}")

预期输出形态(示例数据下的合理结果,非实测):稳定段数 2,分别对应启机后与偏航后的两段稳态,段长均超过 120 s;去趋势后段内均值接近 0,标准差主要由 0.28 Hz 塔架一阶与 1P/3P 贡献。若稳定段数为 0 或段长不足,优先检查转速容差与最短段长是否与机组实际转速波动匹配。

结果判读与常见坑

现象可能原因处理
去趋势后 0.28 Hz 峰明显削弱分段过长,一阶多项式把结构低频当趋势减掉缩短去趋势段长至 30~60 s,或改用高通(截止 0.05 Hz)
频谱低频端能量异常抬高未去趋势或去趋势不彻底,残余斜率造成泄漏检查段内拟合残差斜率,必要时加窗
1P 峰展宽成"山包"稳定段内转速仍有缓慢漂移收紧转速容差至 ±1.5%,或按转速分箱截取
稳定段数为 0转速容差过严或最短段长过长先统计转速分布,再定容差与段长
滤波后峰值时刻偏移用了单向滤波(filtfilt 缺失)改用零相位前后向滤波
重采样前必须先做抗混叠低通。若先降采样再滤波,20 Hz 以上的能量会折叠进 0~25 Hz 频带,直接污染塔架一阶与 3P 的判读。本例顺序为"去趋势 → 低通 → 重采样",不可颠倒。
交付给后续频谱分析的数据集,建议同时保存一份"截取日志":每段的起止时刻、转速均值与波动、剔除原因(启停/偏航/变桨)、去趋势段长与滤波参数。后续若发现某段频谱异常,可快速回溯是数据问题还是结构问题。