【问题标题】:How to filter/smooth with SciPy/Numpy?如何使用 SciPy/Numpy 过滤/平滑?
【发布时间】:2015-04-16 15:43:02
【问题描述】:

我正在尝试过滤/平滑从采样频率为 50 kHz 的压力传感器获得的信号。示例信号如下所示:

我想在MATLAB中得到一个loess得到的平滑信号(我画的不是同一个数据,数值不同)。

我使用 matplotlib 的 psd() 函数计算了功率谱密度,下面还提供了功率谱密度:

我已经尝试使用以下代码并获得了过滤后的信号:

import csv
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from scipy.signal import butter, lfilter, freqz

def butter_lowpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

def butter_lowpass_filter(data, cutoff, fs, order=5):
    b, a = butter_lowpass(cutoff, fs, order=order)
    y = lfilter(b, a, data)
    return y

data = np.loadtxt('data.dat', skiprows=2, delimiter=',', unpack=True).transpose()
time = data[:,0]
pressure = data[:,1]
cutoff = 2000
fs = 50000
pressure_smooth = butter_lowpass_filter(pressure, cutoff, fs)

figure_pressure_trace = plt.figure(figsize=(5.15, 5.15))
figure_pressure_trace.clf()
plot_P_vs_t = plt.subplot(111)
plot_P_vs_t.plot(time, pressure, linewidth=1.0)
plot_P_vs_t.plot(time, pressure_smooth, linewidth=1.0)
plot_P_vs_t.set_ylabel('Pressure (bar)', labelpad=6)
plot_P_vs_t.set_xlabel('Time (ms)', labelpad=6)
plt.show()
plt.close()

我得到的输出是:

我需要更多的平滑,我尝试改变截止频率但仍然无法获得满意的结果。我无法通过 MATLAB 获得相同的平滑度。我确信它可以在 Python 中完成,但是怎么做呢?

你可以找到数据here

更新

我应用了 statsmodels 的 lowess 平滑,这也不能提供令人满意的结果。

【问题讨论】:

    标签: python numpy scipy filtering smoothing


    【解决方案1】:

    这里有几个建议。

    首先,尝试使用 statsmodels 中的 lowess 函数和 it=0,并稍微调整 frac 参数:

    In [328]: from statsmodels.nonparametric.smoothers_lowess import lowess
    
    In [329]: filtered = lowess(pressure, time, is_sorted=True, frac=0.025, it=0)
    
    In [330]: plot(time, pressure, 'r')
    Out[330]: [<matplotlib.lines.Line2D at 0x1178d0668>]
    
    In [331]: plot(filtered[:,0], filtered[:,1], 'b')
    Out[331]: [<matplotlib.lines.Line2D at 0x1173d4550>]
    

    第二个建议是使用scipy.signal.filtfilt 而不是lfilter 来应用巴特沃斯过滤器。 filtfilt向前-向后 过滤器。它应用滤波器两次,一次向前,一次向后,导致零相位延迟。

    这是您的脚本的修改版本。显着的变化是使用filtfilt 而不是lfilter,以及cutoff 从 3000 到 1500 的变化。您可能需要尝试使用该参数——更高的值可以更好地跟踪压力的开始增加,但过高的值不会过滤掉压力增加后的 3kHz(大约)振荡。

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.signal import butter, filtfilt
    
    def butter_lowpass(cutoff, fs, order=5):
        nyq = 0.5 * fs
        normal_cutoff = cutoff / nyq
        b, a = butter(order, normal_cutoff, btype='low', analog=False)
        return b, a
    
    def butter_lowpass_filtfilt(data, cutoff, fs, order=5):
        b, a = butter_lowpass(cutoff, fs, order=order)
        y = filtfilt(b, a, data)
        return y
    
    data = np.loadtxt('data.dat', skiprows=2, delimiter=',', unpack=True).transpose()
    time = data[:,0]
    pressure = data[:,1]
    cutoff = 1500
    fs = 50000
    pressure_smooth = butter_lowpass_filtfilt(pressure, cutoff, fs)
    
    figure_pressure_trace = plt.figure()
    figure_pressure_trace.clf()
    plot_P_vs_t = plt.subplot(111)
    plot_P_vs_t.plot(time, pressure, 'r', linewidth=1.0)
    plot_P_vs_t.plot(time, pressure_smooth, 'b', linewidth=1.0)
    plt.show()
    plt.close()
    

    这是结果图。注意右边缘的滤波信号的偏差。为了解决这个问题,您可以尝试使用filtfiltpadtypepadlen 参数,或者,如果您知道您有足够的数据,您可以丢弃过滤信号的边缘。

    【讨论】:

    • 正是我想要的。但是你是如何选择frac 参数的呢?是通过反复试验获得的还是与信号的波动有关,因为我必须为多个文件(> 1000)这样做。
    • 我从反复试验开始,但事后您可以理解其价值。阶跃响应尾部的大振荡大约为 3.1kHz。在 50kHz 采样率下,这对应于每个周期大约 16 个样本。对于消除这些振荡的 lowess 方法,您需要一个足够大的窗口以包含多个振荡。数据中有2000个样本,2000*0.025是50个,大概包括3次振荡。如果您所有文件中的数据光谱相似,但每个文件中的样本数量不同,您可以尝试使用frac=50.0/num_samples
    【解决方案2】:

    这是一个使用 loewess 拟合的示例。请注意,我从data.dat 中删除了标题。

    从图中看来,这种方法在数据子集上表现良好。一次拟合所有数据并不能给出合理的结果。所以,这可能不是这里最好的方法。

    import pandas as pd
    import matplotlib.pylab as plt
    from statsmodels.nonparametric.smoothers_lowess import lowess
    
    data = pd.read_table("data.dat", sep=",", names=["time", "pressure"])
    sub_data = data[data.time > 21.5]
    
    result = lowess(sub_data.pressure, sub_data.time.values)
    x_smooth = result[:,0]
    y_smooth = result[:,1]
    
    tot_result = lowess(data.pressure, data.time.values, frac=0.1)
    x_tot_smooth = tot_result[:,0]
    y_tot_smooth = tot_result[:,1]
    
    fig, ax = plt.subplots(figsize=(8, 6))
    ax.plot(data.time.values, data.pressure, label="raw")
    ax.plot(x_tot_smooth, y_tot_smooth, label="lowess 1%", linewidth=3, color="g")
    ax.plot(x_smooth, y_smooth, label="lowess", linewidth=3, color="r")
    plt.legend()
    

    这是我得到的结果:

    【讨论】:

    • 这看起来不错,因此您拆分数据并应用平滑。但是我必须对超过一千个文件的多个文件执行此操作,并且压力上升并不总是发生在 21 毫秒。
    • @NimalNaser,我更新了答案。不拆分此方法不起作用,虽然拆分后的结果看起来相当不错,但我怀疑这是解决您问题的最佳方法。我仍然想显示结果以供参考:)
    • @NimalNaser 和另一个更新。这次我大幅减少了平滑区域。您仍然看到这远非最佳。
    • 渐变被改变了。我必须保留渐变。
    猜你喜欢
    • 1970-01-01
    • 2017-06-01
    • 2018-02-17
    • 2012-10-07
    • 2016-02-29
    • 1970-01-01
    • 2012-12-28
    相关资源
    最近更新 更多