簡體   English   中英

Matlab filtfilt() 函數在Java中的實現

[英]Matlab filtfilt() function implementation in Java

有沒有人嘗試過在 Java (或至少在 C++ 中)實現 matlab 的filtfilt()函數? 如果你們有一個算法,那將是非常有幫助的。

是我在MATLAB中實現的filtfilt算法的C ++實現。 希望這對你有所幫助。

好吧,我知道這個問題很古老,但也許我可以幫助那些在這里結束的人,想知道filtfilt究竟做了什么。

盡管從文檔中可以明顯看出filtfilt做了前向后(也稱為零相位)過濾,但對於我如何處理填充初始條件等問題並不是那么明顯。

由於我在這里(或其他地方)沒有找到關於filtfilt這些實現細節的足夠信息的任何其他答案,我基於其源和文檔實現了Pythonscipy.signal.filtfilt的簡化版本(因此,不是Java ,也不是C++ ,而是Python )。 我相信scipy版本的工作方式與Matlab 相同

為了簡單起見,下面的代碼是專門為二階IIR濾波器編寫的,它假設系數向量ab是已知的(例如從scipy.signal.butter獲得,或手工計算 )。

它匹配filtfilt默認行為,使用長度為3 * max(len(a), len(b)) odd填充,這是在正向傳遞之前應用的。 使用scipy.signal.lfilter_zidocs )的方法找到初始狀態。

免責聲明 :此代碼僅用於深入了解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位小數,對於這種特殊情況(機器精度,我猜?):

filtfilt scipy vs自定義實現

我在 Github 上找到了一個 filtfilt Java 實現。 https://github.com/hdw09/filtfilt 。但有一個問題需要澄清為什么結果與 MATLAB 輸出不同。 https://github.com/hdw09/filtfilt/issues/8(Javahttps://github.com/hdw09/filtfilt/issues/6(CPP

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM