所以,我尝试在 C# 中实现 scipy.signal.filtfilt 函数,但是我没有得到我在 python 中得到的结果,我看到大的瞬变或非常微弱的信号。我查看了 scipy 函数的来源并试图模仿它,但没有成功。

我直接从我的 python 脚本中获取了滤波器系数 a 和 b 以及初始状态,所以没有区别。与 filtfilt 中的方法类似,我通过将信号旋转 180°(也在这里完成 MATLAB's filtfilt() Algorithm )用 3 倍滤波器大小填充两端的数据。初始状态乘以我填充数据的第一个元素。
过滤器本身计算新输出和新输入数据的所有状态 d[]。

那么,有人知道我的实现中的错误在哪里吗?

这是我的代码:

/// <remarks>
/// The filter function is implemented as a direct II transposed structure. This means that the filter implements:
///
///a[0]*y[n] = b[0]*x[n] + b[1]*x[n - 1] + ... + b[M]*x[n - M]
///              - a[1]*y[n - 1] - ... - a[N]*y[n - N]
///where M is the degree of the numerator, N is the degree of the denominator, and n is the sample number.It is implemented using the following difference equations(assuming M = N):
///
///a[0]* y[n] = b[0] * x[n] + d[0][n - 1]
///d[0][n] = b[1] * x[n] - a[1] * y[n] + d[1][n - 1]
///d[1][n] = b[2] * x[n] - a[2] * y[n] + d[2][n - 1]
///...
///d[N - 2][n] = b[N - 1]* x[n] - a[N - 1]* y[n] + d[N - 1][n - 1]
///d[N - 1][n] = b[N] * x[n] - a[N] * y[n]
///d[N] = 0</remarks>
public static double[] lfilter(double[] x, double[] a, double[] b, double[] zi)
{
    if (a.Length != b.Length)
        throw new ArgumentOutOfRangeException("A and B filter coefficents should have the same length");
    double[] y = new double[x.Length];
    int N = a.Length;
    double[] d = new double[N];
    zi.CopyTo(d, 0);
    for (int n = 0; n < x.Length; ++n)
    {
        y[n] = b[0] * x[n] + d[0];
        for(int f = 1; f < N; ++f)
        {
            d[f - 1] = b[f] * x[n] - a[f] * y[n] + d[f];
        }
    }
    return y;
}


/// <summary>
/// Apply a digital filter forwards and backwards to have a zero phase shift filter
/// </summary>
/// <param name="data">The data to filter</param>
/// <param name="a">Filter coefficents a</param>
/// <param name="b">Filter coefficents b</param>
/// <returns>The filterd data</returns>
/// <remarks>
/// In order to prevent transients at the end or start of the sequence we have to pad it
/// The padding is done by rotating the sequence by 180° at the ends and append it to the data
/// </remarks>
public static double[] FiltFilt(double[] data, double[] a, double[] b, double[] zi)
{
    //Pad the data with 3 times the filter length on both sides by rotating the data by 180°
    int additionalLength = a.Length * 3;
    int endOfData = additionalLength + data.Length;
    double[] x = new double[data.Length + additionalLength * 2];
    Array.Copy(data, 0, x, additionalLength, data.Length);
    for (int i = 0; i < additionalLength; i++)
    {
        x[additionalLength - i - 1] = (x[additionalLength] * 2) - x[additionalLength + i + 1];
        x[endOfData + i] = (x[endOfData - 1] * 2) - x[endOfData - i - 2];
    }
    //Calculate the initial values for the given sequence
    double[] zi_ = new double[zi.Length];
    for (int i = 0; i < zi.Length; i++)
        zi_[i] = zi[i] * x[0];
    double[] y = lfilter(x, a, b, zi_);
    double[] y_flipped = new double[y.Length];
    //reverse the data and filter again
    for (int i = 0; i < y_flipped.Length; i++)
    {
        y_flipped[i] = y[y.Length - i - 1];
    }
    for (int i = 0; i < zi.Length; i++)
        zi_[i] = zi[i] * y_flipped[0];
    y = lfilter(y_flipped, a, b, zi_);
    y_flipped = new double[data.Length];
    //rereverse it again and return
    for (int i = 0; i < y_flipped.Length; i++)
    {
        y_flipped[i] = y[endOfData - i - 1];
    }
    return y_flipped;
}

最佳答案

因此,事实证明 IIR 滤波器对给定的参数和数值精度非常敏感。我从一个文本文件中导入了我的过滤器系数,我认为它已经足够好了,但事实并非如此。从二进制文件导入给出了我想要的结果,我的算法产生与 scipy filtfilt 相同的输出。

为了完整起见,用于从 python 导出参数并将它们导入 C# 的函数:

import sys
import os;
import numpy as np
import struct

import scipy.signal as signal

order = 4
lowF = 0.2
highF = 0.8

b, a = signal.butter(order, [lowF, highF], btype='band')

#b, a = signal.iirfilter(float(sys.argv[4]), float(sys.argv[5]), btype='lowpass')

path = os.path.dirname(os.path.abspath(sys.argv[0])) + '\\';

# Get the steady state of the filter's step response.
zi = signal.lfilter_zi(b, a)

f = open(path + 'parameterfile.bin',"wb")

def writeToFile(array, file):
    file.write(struct.pack("<I", array.shape[0]))
    print('Byte length: ' + str(len(struct.pack("<I", array.shape[0]))))
    for i in range(array.shape[0]):
        file.write(bytes(array[i]));

writeToFile(a, f)
writeToFile(b, f)
writeToFile(zi, f)

f.close();

和 C# 代码:
private static void GetFilterAndZiFromBin(string path, out double[] a, out double[] b, out double[] zi)
{
    try
    {
        List<double> a_ = new List<double>();
        List<double> b_ = new List<double>();
        List<double> zi_ = new List<double>();
        FileStream fs = new FileStream(path,
                            FileMode.Open,
                            FileAccess.Read);
        BinaryReader br = new BinaryReader(fs);
        int length = br.ReadInt32();
        Console.WriteLine("Read " + length.ToString() + " doubles for a from file");
        while(length > 0)
        {
            a_.Add(br.ReadDouble());
            length--;
        }
        length = br.ReadInt32();
        Console.WriteLine("Read " + length.ToString() + " doubles for b from file");
        while (length > 0)
        {
            b_.Add(br.ReadDouble());
            length--;
        }
        length = br.ReadInt32();
        Console.WriteLine("Read " + length.ToString() + " doubles for zi from file");
        while (length > 0)
        {
            zi_.Add(br.ReadDouble());
            length--;
        }
        a = a_.ToArray();
        b = b_.ToArray();
        zi = zi_.ToArray();
    }
    catch (Exception e)
    {
        a = new double[0];
        b = new double[0];
        zi = new double[0];
        throw e;
    }

}

关于与 numpy.filtfilt 相当的 C# 数字滤波器实现,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45076210/

10-12 18:21