web-dev-qa-db-ja.com

SciPy / Numpyでフィルタリング/平滑化する方法は?

サンプリング周波数50 kHzの圧力トランスデューサーから取得した信号を平滑化しようとしています。サンプル信号を以下に示します。

enter image description here

MATLABのレスによって得られた滑らかな信号を取得したい(同じデータをプロットしていない、値が異なる)。

enter image description here

Matplotlibのpsd()関数を使用してパワースペクトル密度を計算しました。パワースペクトル密度も以下に示します。

enter image description here

次のコードを使用してみて、フィルター処理された信号を取得しました。

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()

私が得る出力は次のとおりです。

enter image description here

さらに平滑化が必要で、カットオフ周波数を変更しようとしましたが、まだ満足のいく結果が得られません。 MATLABで同じ滑らかさを得ることができません。 Pythonでできると確信していますが、どうやって?

データを見つけることができます こちら

更新

Statsmodelsから低域平滑化を適用しましたが、これも満足のいく結果を提供しません。

enter image description here

18
Nimal Naser

ここにいくつかの提案があります。

まず、lowessstatsmodels関数を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>]

plot

2番目の提案は、 scipy.signal.filtfiltlfilterの代わりにバターワースフィルターを適用します。 filtfiltforward-backwardフィルターです。フィルターを2回適用します。1回は順方向、1回は逆方向になり、位相遅延はゼロになります。

これは、スクリプトの修正バージョンです。重要な変更点は、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()

これが結果のプロットです。右エッジでのフィルタリングされた信号の偏差に注意してください。これを処理するには、padtypepadlenおよびfiltfiltパラメーターを試すか、十分なデータがあることがわかっている場合は、フィルター処理された信号のエッジを破棄できます。 。

plot of filtfilt result

24

loewess fitを使用した例を次に示します。 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()

これは私が得る結果です:

plot

0
cel