[英]Matlab filtfilt() function implementation in Java
有沒有人嘗試過在 Java (或至少在 C++ 中)實現 matlab 的filtfilt()
函數? 如果你們有一個算法,那將是非常有幫助的。
這是我在MATLAB中實現的filtfilt
算法的C ++實現。 希望這對你有所幫助。
好吧,我知道這個問題很古老,但也許我可以幫助那些在這里結束的人,想知道filtfilt
究竟做了什么。
盡管從文檔中可以明顯看出filtfilt
做了前向后(也稱為零相位)過濾,但對於我如何處理填充和初始條件等問題並不是那么明顯。
由於我在這里(或其他地方)沒有找到關於filtfilt
這些實現細節的足夠信息的任何其他答案,我基於其源和文檔實現了Python
的scipy.signal.filtfilt
的簡化版本(因此,不是Java
,也不是C++
,而是Python
)。 我相信scipy
版本的工作方式與Matlab
相同 。
為了簡單起見,下面的代碼是專門為二階IIR濾波器編寫的,它假設系數向量a
和b
是已知的(例如從scipy.signal.butter
獲得,或手工計算 )。
它匹配filtfilt
默認行為,使用長度為3 * max(len(a), len(b))
odd
填充,這是在正向傳遞之前應用的。 使用scipy.signal.lfilter_zi
( docs )的方法找到初始狀態。
免責聲明 :此代碼僅用於深入了解filtfilt
某些實現細節,因此目標是清晰度而不是計算效率/性能。 該scipy.signal.filtfilt
實現快得多(如快100倍,根據一個快速和骯臟的timeit
我的系統上測試)。
import numpy
def custom_filter(b, a, x):
"""
Filter implemented using state-space representation.
Assume a filter with second order difference equation (assuming a[0]=1):
y[n] = b[0]*x[n] + b[1]*x[n-1] + b[2]*x[n-2] + ...
- a[1]*y[n-1] - a[2]*y[n-2]
"""
# State space representation (transposed direct form II)
A = numpy.array([[-a[1], 1], [-a[2], 0]])
B = numpy.array([b[1] - b[0] * a[1], b[2] - b[0] * a[2]])
C = numpy.array([1.0, 0.0])
D = b[0]
# Determine initial state (solve zi = A*zi + B, see scipy.signal.lfilter_zi)
zi = numpy.linalg.solve(numpy.eye(2) - A, B)
# Scale the initial state vector zi by the first input value
z = zi * x[0]
# Apply filter
y = numpy.zeros(numpy.shape(x))
for n in range(len(x)):
# Determine n-th output value (note this simplifies to y[n] = z[0] + b[0]*x[n])
y[n] = numpy.dot(C, z) + D * x[n]
# Determine next state (i.e. z[n+1])
z = numpy.dot(A, z) + B * x[n]
return y
def custom_filtfilt(b, a, x):
# Apply 'odd' padding to input signal
padding_length = 3 * max(len(a), len(b)) # the scipy.signal.filtfilt default
x_forward = numpy.concatenate((
[2 * x[0] - xi for xi in x[padding_length:0:-1]],
x,
[2 * x[-1] - xi for xi in x[-2:-padding_length-2:-1]]))
# Filter forward
y_forward = custom_filter(b, a, x_forward)
# Filter backward
x_backward = y_forward[::-1] # reverse
y_backward = custom_filter(b, a, x_backward)
# Remove padding and reverse
return y_backward[-padding_length-1:padding_length-1:-1]
注意,此實現不需要 scipy
。 此外,它可以容易地適用於純Python工作,甚至沒有numpy
,通過寫出來的溶液zi
和使用列表代替numpy的陣列。 這甚至帶來了實質性的性能優勢,因為在python循環中訪問單個numpy數組元素比訪問列表元素要慢得多。
過濾器本身在一個簡單的Python
循環中實現。 它使用狀態空間表示,因為無論如何都要使用它來確定初始條件(請參閱scipy.signal.lfilter_zi
)。 我相信線性過濾器的實際scipy
實現(即scipy.signal.sigtools._linear_filter
)在C
做了類似的事情,這可以在這里看到(感謝這個答案 )。
這里有一些代碼提供(非常基本的)檢查scipy
輸出和custom
輸出的相等性:
import numpy
import numpy.testing
import scipy.signal
from matplotlib import pyplot
from . import custom_filtfilt
def sinusoid(sampling_frequency_Hz=50.0, signal_frequency_Hz=1.0, periods=1.0,
amplitude=1.0, offset=0.0, phase_deg=0.0, noise_std=0.1):
"""
Create a noisy test signal sampled from a sinusoid (time series)
"""
signal_frequency_rad_per_s = signal_frequency_Hz * 2 * numpy.pi
phase_rad = numpy.radians(phase_deg)
duration_s = periods / signal_frequency_Hz
number_of_samples = int(duration_s * sampling_frequency_Hz)
time_s = (numpy.array(range(number_of_samples), float) /
sampling_frequency_Hz)
angle_rad = signal_frequency_rad_per_s * time_s
signal = offset + amplitude * numpy.sin(angle_rad - phase_rad)
noise = numpy.random.normal(loc=0.0, scale=noise_std, size=signal.shape)
return signal + noise
if __name__ == '__main__':
# Design filter
sampling_freq_hz = 50.0
cutoff_freq_hz = 2.5
order = 2
normalized_frequency = cutoff_freq_hz * 2 / sampling_freq_hz
b, a = scipy.signal.butter(order, normalized_frequency, btype='lowpass')
# Create test signal
signal = sinusoid(sampling_frequency_Hz=sampling_freq_hz,
signal_frequency_Hz=1.5, periods=3, amplitude=2.0,
offset=2.0, phase_deg=25)
# Apply zero-phase filters
filtered_custom = custom_filtfilt(b, a, signal)
filtered_scipy = scipy.signal.filtfilt(b, a, signal)
# Verify near-equality
numpy.testing.assert_array_almost_equal(filtered_custom, filtered_scipy,
decimal=12)
# Plot result
pyplot.subplot(1, 2, 1)
pyplot.plot(signal)
pyplot.plot(filtered_scipy)
pyplot.plot(filtered_custom, '.')
pyplot.title('raw vs filtered signals')
pyplot.legend(['raw', 'scipy filtfilt', 'custom filtfilt'])
pyplot.subplot(1, 2, 2)
pyplot.plot(filtered_scipy-filtered_custom)
pyplot.title('difference (scipy vs custom)')
pyplot.show()
這個基本比較得到一個如下圖所示的數字,建議相等至少14位小數,對於這種特殊情況(機器精度,我猜?):
我在 Github 上找到了一個 filtfilt Java 實現。 https://github.com/hdw09/filtfilt 。但有一個問題需要澄清為什么結果與 MATLAB 輸出不同。 https://github.com/hdw09/filtfilt/issues/8(Java ) https://github.com/hdw09/filtfilt/issues/6(CPP )
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.