Bootstrap

C语言实现IIR型零相位带通滤波器

1. 引言

在数字信号处理领域,数字滤波器的高效实现一直是研究和实践的重点。当前,有多种编程语言可供选择,如C语言、Matlab语言与Python语言,即使如Python与Matlab有实现数字滤波器的库函数,但 Python 和 Matlab 在数字滤波器实现方面都存在着不容忽视的缺点。

Python是一种解释型语言,其代码在运行时需要通过解释器逐行翻译并执行,执行效率低。虽然 Python 有高效的数值计算库(如 Numpy),但在底层计算上,由于解释过程的存在,会引入一定的开销。例如,在同样的 FIR 数字滤波器实现中,Python 的循环结构(即使是使用了 Numpy 的向量化操作)在执行速度上通常比 C 语言慢几倍到几十倍不等,特别是当信号长度很长时,这种性能差距会进一步拉大。此外,由于Python自动内存管理在处理大型信号集时易浪费内存、产生碎片,且在硬件控制上较弱,对嵌入式等资源受限硬件平台适应性差。

Matlab 虽有丰富信号处理库,这使得它在快速原型开发方面具有一定优势。然而,Matlab高度依赖自身软件环境,这意味着要运行 Matlab 编写的数字滤波器程序,必须安装完整的 Matlab 软件。这种依赖性极大地限制了程序的可移植性,使得在不同的操作系统或硬件平台上部署变得复杂且困难。而且,将 Matlab 程序转化为独立的可执行应用程序的过程复杂且效率不高,这在实际的工程应用中会带来诸多不便。

相比之下, C 语言优势显著。C语言编译后直接运行,执行效率高,能快速处理滤波器的数学运算。C 语言有精细内存管理,可按需分配释放内存,适合资源有限环境。它还便于硬件控制,可直接访问寄存器和设置参数,可移植性也很好。因此,使用 C 语言实现数字滤波器具有重要意义,下面将介绍其实现方法。

2. 滤波器算法结构

2.1 滤波器系数的获取

滤波器函数的作用是对信号进行处理,以去除或减少不需要的频率成分,同时保留或增强有用的信号部分。不同类型的滤波器能够实现不同的效果,主要分为以下几类:

  • 低通滤波器(Low-pass filter):只允许低频成分通过抑制高频成分。适用于去除高频噪声,常用于平滑信号。
  • 高通滤波器(High-pass filter):只允许高频成分通过抑制低频成分。常用于去除慢变化的信号成分,如去除信号中的趋势或偏移。
  • 带通滤波器(Band-pass filter)允许特定频段的信号通过,抑制高于或低于该频段的成分。用于提取特定频段的信号成分。
  • 带阻滤波器(Band-stop filter)阻止特定频段的信号通过,允许其他频率通过。常用于消除电源干扰频率(如50Hz或60Hz)等。

由于每种类型的滤波器设计目标和频率范围不同,因此计算得到的滤波器系数也不同。设计滤波器系数的手动计算可能非常复杂,因此通常使用信号处理软件(如MATLAB、Python中的scipy.signal库)来自动生成所需的滤波器系数。例如:

  • Matlab代码
    %% 使用Matlab获取带通滤波器系数
    % 设定参数
    Fs = 250;             % 采样率 (Hz)
    low_cutoff_bandpass = 4;       % 带通滤波器的低频截止 (Hz)
    high_cutoff_bandpass = 40;     % 带通滤波器的高频截止 (Hz)
    order = 4;            % 滤波器阶数
    
    % 归一化截止频率
    low_normalized_bandpass = low_cutoff_bandpass / (Fs / 2);
    high_normalized_bandpass = high_cutoff_bandpass / (Fs / 2);
    
    % 设计4阶巴特沃斯带通滤波器
    [b_bandpass, a_bandpass] = butter(order, [low_normalized_bandpass high_normalized_bandpass], 'bandpass');
    disp('b_bandpass:');
    disp(b_bandpass);
    disp('a_bandpass:');
    disp(a_bandpass);
    
  • Python代码
    '''使用Python获取带通滤波器系数'''
    from scipy.signal import butter, filtfilt
    fs = 250   # 采样率
    nyq = 0.5 * fs  # 归一化频率
    order = 4  # 阶数
    low_bandpass, high_bandpass = 4, 40
    Wn_bandpass = [low_bandpass / nyq, high_bandpass / nyq]
    b_bandpass, a_bandpass = butter(order, Wn_bandpass, btype='bandpass', analog=False)
    
    print("b_bandpass:", b_bandpass.tolist())
    print("a_bandpass:", a_bandpass.tolist())
    
    

2.2 数字滤波器差分函数

数字滤波器用于离散时间信号处理,即信号在离散时间点上采样并处理。差分方程是描述离散系统的一种标准数学形式,它使用输入输出当前和历史样本值,能很好地描述滤波器的递推关系。

一个典型的线性差分方程形式为:
y [ n ] = − ∑ k = 1 N a k ∗ y [ n − k ] + ∑ k = 0 M b k ∗ x [ n − k ] (1) y[n]=-\sum_{k=1}^Na_k*y[n-k]+\sum_{k=0}^Mb_k*x[n-k]\tag{1} y[n]=k=1Naky[nk]+k=0Mbkx[nk](1)

其中, y [ n ] y[n] y[n]是当前输出, x [ n ] x[n] x[n]是当前输入, a k a_k ak b k b_k bk 是滤波器系数, N N N M M M 分别是反馈和前馈的延迟阶数。

通过差分方程可以实现递归和非递归滤波器的结构:

  • 非递归结构(FIR型滤波器):仅包含输入的前馈项(即上式中的 a k = 0 a_k=0 ak=0),没有反馈项,因此更稳定,没有延迟累积或振荡问题。其数学形式为:
    y [ n ] = ∑ k = 0 M b k ∗ x [ n − k ] (2) y[n]=\sum_{k=0}^Mb_k*x[n-k]\tag{2} y[n]=k=0Mbkx[nk](2)

  • 递归结构(IIR型滤波器):包含输出的反馈项,可以实现复杂的频率响应,但可能会导致非线性相位或稳定性问题。其数学形式为:
    y [ n ] = ∑ k = 0 M b k ∗ x [ n − k ] − ∑ k = 1 N a k ∗ y [ n − k ] (3) y[n]=\sum_{k=0}^Mb_k*x[n-k]-\sum_{k=1}^Na_k*y[n-k]\tag{3} y[n]=k=0Mbkx[nk]k=1Naky[nk](3)

由于IIR型滤波器具有低阶高效计算量小适合模拟滤波器特性以及良好的低频性能等优点,其在实时处理和资源受限的应用中具有显著优势。因此IIR型滤波器广泛用于音频处理无线通信航空航天图像处理医疗设备(ECG、EEG、超声信号处理)等领域。下面给出IIR型滤波器的差分函数C语言实现代码:

#include <stdio.h>
#include <math.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#define EPS 0.000001

// IIR型数字滤波器函数
void digitalFilter(const double* inputSignal, double* outputSignal, int inputLength, double* filterCoefficientsA, double* filterCoefficientsB, 
                   int filterLength, double* initialConditions) 
	{
	    double normalizationFactor;
	    int i, j;
	
	    // 判断是否需要归一化
	    if ((filterCoefficientsA[0] - 1.0 > EPS) || (filterCoefficientsA[0] - 1.0 < -EPS)) {
	        normalizationFactor = filterCoefficientsA[0];
	        for (i = 0; i < filterLength; i++) {
	            filterCoefficientsB[i] /= normalizationFactor;
	            filterCoefficientsA[i] /= normalizationFactor;
	        }
	    }
	
	    // 将输出信号初始化为零
	    for (i = 0; i < inputLength; i++) {
	        outputSignal[i] = 0.0;
	    }
	    
	    // 递归型差分方程
	    filterCoefficientsA[0] = 0.0;
    	for (i = 0; i < inputLength; i++) {
	        for (j = 0; i >= j && j < filterLength; j++) {
	            // 计算滤波器输出
	            outputSignal[i] += (filterCoefficientsB[j] * inputSignal[i - j] - filterCoefficientsA[j] * outputSignal[i - j]);
	        }
	        // 如果有初始条件且当前位置小于滤波器长度减一,加上初始条件
	        if (initialConditions && i < filterLength - 1) {
	            outputSignal[i] += initialConditions[i];
	        }
    }
    filterCoefficientsA[0] = 1.0;
}

2.3 滤波器的因果性与边缘效应

大多数滤波器都是因果滤波器,它们依赖当前和之前的样本来计算输出。当信号到达起点或终点时,缺少足够的历史或未来样本,导致滤波器无法正确初始化和延续滤波操作,从而产生不真实的波动或偏差。

滤波操作通常是卷积或相关操作的一种。在卷积运算中,滤波器窗口会滑动到信号的两端,而此时滤波器的一部分会超出信号范围。由于信号两端没有足够的数据来进行完整的卷积计算,这会导致滤波器输出不准确,产生边缘效应

而在对任务态的EEG数据进行滤波处理时,我们希望滤波后的数据与滤波前的数据的相位保持一致,以避免出现相位偏移现象,影响事件相关电位分析、事件监测、伪影判别与脑网络分析等情况。零相位滤波信号器可成功克服这些不利因素,因为零相位滤波器在滤波后不会引起相位失真现象。而上述介绍的IIR型滤波器本身是因果的,会引入相位延迟,因此通常需要通过双向滤波来实现零相位响应。

双向滤波的原理是:由于正向滤波会带来一个正向的延迟,逆向滤波会带来一个逆向的延迟。因此先将信号先正向通过IIR滤波器,再反向通过同样的IIR滤波器,这样可以消除相位偏移。然而,这种双向滤波方式可能导致边界效应和稳定性问题。

2.4 矩阵乘法函数

为了实现稳定的双向滤波,通常使用状态空间模型来描述IIR滤波器。状态空间模型提供了矩阵表示的滤波器结构,可以方便地在正向和反向方向上进行递推,并且能够确保在双向滤波过程中保持系统的稳定性。

状态空间模型的形式为:
x [ n + 1 ] = A x [ n ] + B u [ n ] (4) x[n+1]=Ax[n]+Bu[n] \tag{4} x[n+1]=Ax[n]+Bu[n](4)
y [ n ] = C x [ n ] + D u [ n ] (5) y[n]=Cx[n]+Du[n]\tag{5} y[n]=Cx[n]+Du[n](5)

这里, A 、 B 、 C 、 D A、B、C、D ABCD 是描述滤波器动态特性的矩阵。零相位滤波需要在正反方向上计算状态,这会涉及矩阵乘法和逆矩阵计算。为此,我们在实现零相位滤波器时应实现矩阵乘法与逆矩阵计算函数。

// 矩阵乘法函数
void matrixMultiplication(double* matrixA, double* matrixB, double* resultMatrix, int numRowsA, int numColsA, int numColsB) {
    int rowIndex, colIndex, commonIndex;
    int elementIndex;
    for (rowIndex = 0; rowIndex < numRowsA; rowIndex++) {
        for (colIndex = 0; colIndex < numColsB; colIndex++) {
            elementIndex = rowIndex * numColsB + colIndex;
            resultMatrix[elementIndex] = 0.0;
            // 遍历两个矩阵共同的维度,即矩阵A的列与矩阵B的行
            for (commonIndex = 0; commonIndex < numColsA; commonIndex++) {
                // 计算矩阵乘法
                resultMatrix[elementIndex] += matrixA[rowIndex * numColsA + commonIndex] * matrixB[commonIndex * numColsB + colIndex];
            }
        }
    }
    return;
}

2.5 逆矩阵函数

在双向滤波中,状态向量在滤波过程中反向运行。要实现这种反向递推(尤其是在状态空间模型中),需要求解状态向量的初始条件。这通常涉及到逆矩阵计算,特别是在确定系统的初始状态向量时,用于确保滤波结果的精确性和稳定性。

下面这个函数用于计算给定矩阵的逆矩阵。它使用高斯 - 约旦消元法来实现。首先,通过寻找最大元素来进行行和列的交换,以避免除以小的数导致数值不稳定。然后,逐步将矩阵转换为单位矩阵,同时对原始矩阵进行操作,最终得到逆矩阵。

int inverseMatrix(double* matrix, int matrixSize) {
    // 用于存储行和列的交换索引
    int* rowIndices, *columnIndices;
    int i, j, k, l, u, v;
    double d, p;

    // 分配内存
    rowIndices = (int*)malloc(matrixSize * sizeof(int));
    columnIndices = (int*)malloc(matrixSize * sizeof(int));

    for (k = 0; k < matrixSize; k++) {
        // 寻找最大元素
        d = 0.0;
        for (i = k; i < matrixSize; i++) {
            for (j = k; j < matrixSize; j++) {
                l = i * matrixSize + j;
                p = fabs(matrix[l]);
                if (p > d) {
                    d = p;
                    rowIndices[k] = i;
                    columnIndices[k] = j;
                }
            }
        }

        // 判断是否可逆
        if (d + 1.0 == 1.0) {
            free(rowIndices);
            free(columnIndices);
            printf("err**not invn");
            return 0;
        }

        // 交换行
        if (rowIndices[k]!= k) {
            for (j = 0; j < matrixSize; j++) {
                u = k * matrixSize + j;
                v = rowIndices[k] * matrixSize + j;
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        // 交换列
        if (columnIndices[k]!= k) {
            for (i = 0; i < matrixSize; i++) {
                u = i * matrixSize + k;
                v = i * matrixSize + columnIndices[k];
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        // 计算对角线元素的倒数
        l = k * matrixSize + k;
        matrix[l] = 1.0 / matrix[l];

        // 更新非对角线上的元素
        for (j = 0; j < matrixSize; j++) {
            if (j!= k) {
                u = k * matrixSize + j;
                matrix[u] = matrix[u] * matrix[l];
            }
        }

        for (i = 0; i < matrixSize; i++) {
            if (i!= k) {
                for (j = 0; j < matrixSize; j++) {
                    if (j!= k) {
                        u = i * matrixSize + j;
                        matrix[u] = matrix[u] - matrix[i * matrixSize + k] * matrix[k * matrixSize + j];
                    }
                }
            }
        }

        for (i = 0; i < matrixSize; i++) {
            if (i!= k) {
                u = i * matrixSize + k;
                matrix[u] = -matrix[u] * matrix[l];
            }
        }
    }

    // 恢复交换的列和行
    for (k = matrixSize - 1; k >= 0; k--) {
        if (columnIndices[k]!= k) {
            for (j = 0; j < matrixSize; j++) {
                u = k * matrixSize + j;
                v = columnIndices[k] * matrixSize + j;
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        if (rowIndices[k]!= k) {
            for (i = 0; i < matrixSize; i++) {
                u = i * matrixSize + k;
                v = i * matrixSize + rowIndices[k];
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }
    }

    // 释放内存
    free(rowIndices);
    free(columnIndices);
    return 0;
}

2.6 零相位滤波器函数

上面谈到零相位滤波器可成功消除相位失真的现象。这样的处理方式非常适合对时域特征要求严格的信号处理任务,如EEG信号分析,确保滤波后的信号在保留频率特征的同时,不会受到滤波器引入的相位延迟影响。

具体而言,本文借助于以下几个步骤,成功将IIR型滤波器转化为IIR型零相位型滤波器:

  • 边缘反射扩展:为了减少滤波器对信号两端的边缘效应,可通过反射扩展输入信号的两端。前端和后端的信号样本被用来创建扩展信号,避免了因无法访问两端数据而产生的失真。

  • 初始化滤波器状态:在IIR型滤波器中,初始条件对正确滤波非常关键。通过计算工作矩阵和瞬态向量,函数求解出初始条件,从而避免了滤波器启动时的瞬态响应对信号的影响。

  • 正向滤波:对扩展信号执行正向滤波,得到初步的滤波结果。正向滤波会引入相位失真,但这不影响最终的结果,因为接下来会进行反向滤波。

  • 反向滤波:反转正向滤波结果,并对反转后的信号执行逆向滤波。由于信号的顺序已经被反转,反向滤波能消除相位失真。

  • 恢复原始顺序:反向滤波后的信号再反转回原始顺序,得到最终的零相位滤波结果。

  • 内存管理:在整个过程中,动态分配了多个内存块用于存储扩展信号、滤波器状态、矩阵计算等中间结果,并在结束时释放内存,避免内存泄漏。

// filtfilt 函数:对输入信号进行零相位滤波
int filtfilt(double* input_signal, double* output_signal, int signal_length, double* filter_a, double* filter_b, int filter_order)
{
    int reflection_length;
    int extended_length;  // 扩展序列的长度,包括边缘反射部分
    int i;
    double *extended_signal, *filtered_signal, *p, *t, *end;
    double *workspace_matrix, *transient_vector, *initial_conditions;
    double first_sample, last_sample;

    // 根据滤波器阶数计算反射长度
    reflection_length = filter_order - 1;

    // 检查输入信号长度是否过短或滤波器参数是否无效
    if(signal_length <= 3 * reflection_length || filter_order < 2) return -1;

    // 定义扩展长度以容纳边缘反射,用于减小边缘效应
    extended_length = 6 * reflection_length + signal_length;
    extended_signal = (double *)malloc(extended_length * sizeof(double));
    filtered_signal = (double *)malloc(extended_length * sizeof(double));

    workspace_matrix = (double*)malloc(sizeof(double) * reflection_length * reflection_length);
    transient_vector = (double*)malloc(sizeof(double) * reflection_length);
    initial_conditions = (double*)malloc(sizeof(double) * reflection_length);

    // 检查内存分配是否成功
    if( !extended_signal || !filtered_signal || !workspace_matrix || !transient_vector || !initial_conditions ){
        free(extended_signal);
        free(filtered_signal);
        free(workspace_matrix);
        free(transient_vector);
        free(initial_conditions);
        return 1;
    }

    // 在信号开始处反射以减小边缘效应
    first_sample = input_signal[0];
    for(p = input_signal + 3 * reflection_length, t = extended_signal; p > input_signal; --p, ++t) 
        *t = 2.0 * first_sample - *p;

    // 复制信号的主体部分
    for(end = input_signal + signal_length; p < end; ++p, ++t) 
        *t = *p;

    // 在信号结束处反射以减小边缘效应
    last_sample = input_signal[signal_length - 1];
    for(end = extended_signal + extended_length, p -= 2; t < end; --p, ++t) 
        *t = 2.0 * last_sample - *p;

    // 初始化工作矩阵,用于求解初始条件
    memset(workspace_matrix, 0, sizeof(double) * reflection_length * reflection_length);

    workspace_matrix[0] = 1.0 + filter_a[1];
    for(i = 1; i < reflection_length; i++) {
        workspace_matrix[i * reflection_length] = filter_a[i + 1];
        workspace_matrix[i * reflection_length + i] = 1.0;
        workspace_matrix[(i - 1) * reflection_length + i] = -1.0;
    }

    // 计算用于初始条件的瞬态向量
    for(i = 0; i < reflection_length; i++) {
        transient_vector[i] = filter_b[i + 1] - filter_a[i + 1] * filter_b[0];
    }

    // 如果矩阵求逆成功,计算初始条件
    if(inverseMatrix(workspace_matrix, reflection_length)) {
        free(initial_conditions);
        initial_conditions = NULL;
    } else {
        matrixMultiplication(workspace_matrix, transient_vector, initial_conditions, reflection_length, reflection_length, 1);
    }

    free(workspace_matrix);
    free(transient_vector);

    // 对扩展信号执行正向滤波,将结果保存在 filtered_signal 中
    first_sample = extended_signal[0];
    if(initial_conditions) {
        for(p = initial_conditions, end = initial_conditions + reflection_length; p < end; ) 
            *(p++) *= first_sample;
    }
    digitalFilter(extended_signal, filtered_signal, extended_length, filter_a, filter_b, filter_order, initial_conditions);

    // 反转滤波后的信号以进行逆向滤波
    for(p = filtered_signal, end = filtered_signal + extended_length - 1; p < end; p++, end--) {
        double temp = *p;
        *p = *end;
        *end = temp;
    }

    // 对反转后的信号执行逆向滤波
    last_sample = (*filtered_signal) / first_sample;
    if(initial_conditions) {
        for(p = initial_conditions, end = initial_conditions + reflection_length; p < end; ) 
            *(p++) *= last_sample;
    }
    digitalFilter(filtered_signal, extended_signal, extended_length, filter_a, filter_b, filter_order, initial_conditions);

    // 将滤波后的信号反转回原顺序,并存储在 output_signal 中
    end = output_signal + signal_length;
    p = extended_signal + 3 * reflection_length + signal_length - 1;
    while(output_signal < end) {
        *output_signal++ = *p--;
    }

    // 清理分配的内存
    free(initial_conditions);
    free(extended_signal);
    free(filtered_signal);

    return 0;
}

3. 完整代码实现

#include <stdio.h>
#include <math.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#define EPS 0.000001

// IIR型数字滤波器函数
void digitalFilter(const double* inputSignal, double* outputSignal, int inputLength, double* filterCoefficientsA, double* filterCoefficientsB, 
                   int filterLength, double* initialConditions) 
	{
	    double normalizationFactor;
	    int i, j;
	
	    // 判断是否需要归一化
	    if ((filterCoefficientsA[0] - 1.0 > EPS) || (filterCoefficientsA[0] - 1.0 < -EPS)) {
	        normalizationFactor = filterCoefficientsA[0];
	        for (i = 0; i < filterLength; i++) {
	            filterCoefficientsB[i] /= normalizationFactor;
	            filterCoefficientsA[i] /= normalizationFactor;
	        }
	    }
	
	    // 将输出信号初始化为零
	    for (i = 0; i < inputLength; i++) {
	        outputSignal[i] = 0.0;
	    }
	    
	    // 递归式差分方程
	    filterCoefficientsA[0] = 0.0;
    	for (i = 0; i < inputLength; i++) {
	        for (j = 0; i >= j && j < filterLength; j++) {
	            // 计算滤波器输出
	            outputSignal[i] += (filterCoefficientsB[j] * inputSignal[i - j] - filterCoefficientsA[j] * outputSignal[i - j]);
	        }
	        // 如果有初始条件且当前位置小于滤波器长度减一,加上初始条件
	        if (initialConditions && i < filterLength - 1) {
	            outputSignal[i] += initialConditions[i];
	        }
    }
    filterCoefficientsA[0] = 1.0;
}



// 矩阵乘法函数
void matrixMultiplication(double* matrixA, double* matrixB, double* resultMatrix, int numRowsA, int numColsA, int numColsB) {
    int rowIndex, colIndex, commonIndex;
    int elementIndex;
    for (rowIndex = 0; rowIndex < numRowsA; rowIndex++) {
        for (colIndex = 0; colIndex < numColsB; colIndex++) {
            elementIndex = rowIndex * numColsB + colIndex;
            resultMatrix[elementIndex] = 0.0;
            // 遍历两个矩阵共同的维度,即矩阵A的列与矩阵B的行
            for (commonIndex = 0; commonIndex < numColsA; commonIndex++) {
                // 计算矩阵乘法
                resultMatrix[elementIndex] += matrixA[rowIndex * numColsA + commonIndex] * matrixB[commonIndex * numColsB + colIndex];
            }
        }
    }
    return;
}


//求逆矩阵,当返回值为0时成功求解逆矩阵
int inverseMatrix(double* matrix, int matrixSize) {
    // 用于存储行和列的交换索引
    int* rowIndices, *columnIndices;
    int i, j, k, l, u, v;
    double d, p;

    // 分配内存
    rowIndices = (int*)malloc(matrixSize * sizeof(int));
    columnIndices = (int*)malloc(matrixSize * sizeof(int));

    for (k = 0; k < matrixSize; k++) {
        // 寻找最大元素
        d = 0.0;
        for (i = k; i < matrixSize; i++) {
            for (j = k; j < matrixSize; j++) {
                l = i * matrixSize + j;
                p = fabs(matrix[l]);
                if (p > d) {
                    d = p;
                    rowIndices[k] = i;
                    columnIndices[k] = j;
                }
            }
        }

        // 判断是否可逆
        if (d + 1.0 == 1.0) {
            free(rowIndices);
            free(columnIndices);
            printf("err**not invn");
            return 0;
        }

        // 交换行
        if (rowIndices[k]!= k) {
            for (j = 0; j < matrixSize; j++) {
                u = k * matrixSize + j;
                v = rowIndices[k] * matrixSize + j;
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        // 交换列
        if (columnIndices[k]!= k) {
            for (i = 0; i < matrixSize; i++) {
                u = i * matrixSize + k;
                v = i * matrixSize + columnIndices[k];
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        // 计算对角线元素的倒数
        l = k * matrixSize + k;
        matrix[l] = 1.0 / matrix[l];

        // 更新非对角线上的元素
        for (j = 0; j < matrixSize; j++) {
            if (j!= k) {
                u = k * matrixSize + j;
                matrix[u] = matrix[u] * matrix[l];
            }
        }

        for (i = 0; i < matrixSize; i++) {
            if (i!= k) {
                for (j = 0; j < matrixSize; j++) {
                    if (j!= k) {
                        u = i * matrixSize + j;
                        matrix[u] = matrix[u] - matrix[i * matrixSize + k] * matrix[k * matrixSize + j];
                    }
                }
            }
        }

        for (i = 0; i < matrixSize; i++) {
            if (i!= k) {
                u = i * matrixSize + k;
                matrix[u] = -matrix[u] * matrix[l];
            }
        }
    }

    // 恢复交换的列和行
    for (k = matrixSize - 1; k >= 0; k--) {
        if (columnIndices[k]!= k) {
            for (j = 0; j < matrixSize; j++) {
                u = k * matrixSize + j;
                v = columnIndices[k] * matrixSize + j;
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }

        if (rowIndices[k]!= k) {
            for (i = 0; i < matrixSize; i++) {
                u = i * matrixSize + k;
                v = i * matrixSize + rowIndices[k];
                p = matrix[u];
                matrix[u] = matrix[v];
                matrix[v] = p;
            }
        }
    }

    // 释放内存
    free(rowIndices);
    free(columnIndices);
    return 0;
}


// filtfilt 函数:对输入信号进行零相位滤波
int filtfilt(double* input_signal, double* output_signal, int signal_length, double* filter_a, double* filter_b, int filter_order)
{
    int reflection_length;
    int extended_length;  // 扩展序列的长度,包括边缘反射部分
    int i;
    double *extended_signal, *filtered_signal, *p, *t, *end;
    double *workspace_matrix, *transient_vector, *initial_conditions;
    double first_sample, last_sample;

    // 根据滤波器阶数计算反射长度
    reflection_length = filter_order - 1;

    // 检查输入信号长度是否过短或滤波器参数是否无效
    if(signal_length <= 3 * reflection_length || filter_order < 2) return -1;

    // 定义扩展长度以容纳边缘反射,用于减小边缘效应
    extended_length = 6 * reflection_length + signal_length;
    extended_signal = (double *)malloc(extended_length * sizeof(double));
    filtered_signal = (double *)malloc(extended_length * sizeof(double));

    workspace_matrix = (double*)malloc(sizeof(double) * reflection_length * reflection_length);
    transient_vector = (double*)malloc(sizeof(double) * reflection_length);
    initial_conditions = (double*)malloc(sizeof(double) * reflection_length);

    // 检查内存分配是否成功
    if( !extended_signal || !filtered_signal || !workspace_matrix || !transient_vector || !initial_conditions ){
        free(extended_signal);
        free(filtered_signal);
        free(workspace_matrix);
        free(transient_vector);
        free(initial_conditions);
        return 1;
    }

    // 在信号开始处反射以减小边缘效应
    first_sample = input_signal[0];
    for(p = input_signal + 3 * reflection_length, t = extended_signal; p > input_signal; --p, ++t) 
        *t = 2.0 * first_sample - *p;

    // 复制信号的主体部分
    for(end = input_signal + signal_length; p < end; ++p, ++t) 
        *t = *p;

    // 在信号结束处反射以减小边缘效应
    last_sample = input_signal[signal_length - 1];
    for(end = extended_signal + extended_length, p -= 2; t < end; --p, ++t) 
        *t = 2.0 * last_sample - *p;

    // 初始化工作矩阵,用于求解初始条件
    memset(workspace_matrix, 0, sizeof(double) * reflection_length * reflection_length);

    workspace_matrix[0] = 1.0 + filter_a[1];
    for(i = 1; i < reflection_length; i++) {
        workspace_matrix[i * reflection_length] = filter_a[i + 1];
        workspace_matrix[i * reflection_length + i] = 1.0;
        workspace_matrix[(i - 1) * reflection_length + i] = -1.0;
    }

    // 计算用于初始条件的瞬态向量
    for(i = 0; i < reflection_length; i++) {
        transient_vector[i] = filter_b[i + 1] - filter_a[i + 1] * filter_b[0];
    }

    // 如果矩阵求逆成功,计算初始条件
    if(inverseMatrix(workspace_matrix, reflection_length)) {
        free(initial_conditions);
        initial_conditions = NULL;
    } else {
        matrixMultiplication(workspace_matrix, transient_vector, initial_conditions, reflection_length, reflection_length, 1);
    }

    free(workspace_matrix);
    free(transient_vector);

    // 对扩展信号执行正向滤波,将结果保存在 filtered_signal 中
    first_sample = extended_signal[0];
    if(initial_conditions) {
        for(p = initial_conditions, end = initial_conditions + reflection_length; p < end; ) 
            *(p++) *= first_sample;
    }
    digitalFilter(extended_signal, filtered_signal, extended_length, filter_a, filter_b, filter_order, initial_conditions);

    // 反转滤波后的信号以进行逆向滤波
    for(p = filtered_signal, end = filtered_signal + extended_length - 1; p < end; p++, end--) {
        double temp = *p;
        *p = *end;
        *end = temp;
    }

    // 对反转后的信号执行逆向滤波
    last_sample = (*filtered_signal) / first_sample;
    if(initial_conditions) {
        for(p = initial_conditions, end = initial_conditions + reflection_length; p < end; ) 
            *(p++) *= last_sample;
    }
    digitalFilter(filtered_signal, extended_signal, extended_length, filter_a, filter_b, filter_order, initial_conditions);

    // 将滤波后的信号反转回原顺序,并存储在 output_signal 中
    end = output_signal + signal_length;
    p = extended_signal + 3 * reflection_length + signal_length - 1;
    while(output_signal < end) {
        *output_signal++ = *p--;
    }

    // 清理分配的内存
    free(initial_conditions);
    free(extended_signal);
    free(filtered_signal);

    return 0;
}



int main()
{
	// 例子带通滤波器系数(4阶Butterworth带通滤波器,采样率250Hz, 4-40Hz)
    double b_bandpass[] = {0.016255176549541173, 0.0, -0.06502070619816469, 0.0, 
	                       0.09753105929724704, 0.0, -0.06502070619816469, 0.0, 0.016255176549541173};
    double a_bandpass[] = {1.0, -5.360703285534569, 12.74230451194405, -17.736407401314917, 
	                       15.949378473690096, -9.51256976782561, 3.6615254094399727, -0.8286427351763588, 0.08515392332605591};
	
	// 定义测试的待滤波数据, 1s, 250个采样点
	double x[] = {7994.9079,  7978.61475, 7976.6703,  7992.0918,  8006.7534,  8000.07075,  7987.5771,  7984.0458,  7999.9143,  8014.4418,
                  8004.0714,  7983.04005, 7980.8721,  7994.70675, 8009.0778,  7999.93665,  7981.4979,  7978.3242,  7994.66205, 8007.84855, 
				  7997.2323,  7981.6767,  7984.2693,  7999.9143,  8012.7879,  8001.18825,  7982.8389,  7984.06815, 7998.19335, 8004.8313,
                  7994.7291,  7978.9053,  7976.24565, 7989.52155, 7999.75785, 7988.31465,  7978.12305, 7977.2514,  7993.49985, 8009.10015, 
				  7997.74635, 7978.57005, 7973.4519,  7987.24185, 8006.28405, 7999.13205,  7983.88935, 7980.2463,  7992.38235, 8005.8147,
				  7998.7074,  7981.43085, 7977.2514,  7991.8236,  8008.07205, 7997.12055,  7980.0675,  7974.10005, 7985.36445, 7998.37215, 
				  7990.0803,  7974.8376,  7971.55215, 7982.45895, 7999.8249,  7995.2655,   7977.81015, 7978.7712,  7995.3549,  8004.9207, 
				  7990.8849,  7975.21755, 7977.00555, 7994.03625, 8008.0497,  7997.1429,   7979.86635, 7978.25715, 7996.40535, 8008.69785, 
				  7998.23805, 7983.26355, 7982.2131,  7996.89705, 8008.74255, 7999.0203,   7981.20735, 7978.7265,  7996.13715, 8013.3243, 
				  8005.39005, 7987.51005, 7984.91745, 7999.2438,  8010.7764,  8000.2272,   7989.11925, 7989.2757,  7999.44495, 8006.46285, 
				  7996.36065, 7982.8836,  7979.37465, 7998.8415,  8017.01205, 8006.59695,  7990.5273,  7983.5541,  7999.71315, 8015.2017, 
				  8008.7202,  7992.96345, 7991.48835, 8006.9769,  8018.1966,  8004.89835,  7983.2859,  7979.77695, 8002.1046,  8017.86135, 
				  8005.6806,  7985.9232,  7985.40915, 8005.47945, 8017.0791,  8003.8926,   7986.34785, 7990.90725, 8005.5465,  8017.99545, 
				  8011.2681,  7991.1531,  7988.09115, 8005.3677,  8016.498,   8003.95965,  7989.0522,  7995.6231,  8011.7598,  8022.86775, 
				  8014.6653,  7997.3664,  7995.3996,  8011.02225, 8022.6666,  8008.69785,  7988.80635, 7987.9794,  7998.86385, 8011.3128, 
				  8001.5682,  7987.3983,  7986.07965, 8000.07075, 8011.4022,  8003.8479,   7993.05285, 7994.2821,  8009.54715, 8023.203, 
				  8014.1736,  7996.8747,  7992.69525, 8005.5912,  8019.3588,  8009.1225,   7998.0369,  7999.6014,  8014.06185, 8020.4316, 
				  8011.37985, 8000.3166,  8000.8083,  8017.68255, 8027.226,   8007.98265,  7995.33255, 7997.52285, 8016.34155, 8029.2375, 
				  8016.52035, 7997.2323,  7993.7457,  8009.81535, 8020.72215, 8006.73105,  7994.84085, 7995.64545, 8007.0216,  8012.1621, 
				  7999.6908,  7987.28655, 7987.19715, 8001.6576,  8012.45265, 7997.96985,  7986.83955, 7987.51005, 8001.4341,  8010.03885,
				  7993.2093,  7978.5924,  7979.2629,  7995.8019,  8003.3562,  7993.72335,  7980.00045, 7979.91105, 7998.68505, 8014.1289, 
				  8000.0931,  7983.57645, 7982.05665, 7996.36065, 8008.8543,  8000.51775,  7983.93405, 7982.50365, 7997.47815, 8011.0446, 
				  8000.1378,  7981.0956,  7980.5592,  7998.1263,  8010.97755, 8001.4341,   7989.3204,  7989.07455, 7998.7074,  8007.53565, 
				  7995.91365, 7984.7163,  7984.91745, 8004.9207,  8015.13465, 8003.51265,  7987.2642,  7985.2080,  8007.4239,  8019.3588, 
				  8005.83705, 7994.61735, 7995.46665, 8006.5299,  8014.50885, 8001.41175,  7984.51515, 7983.5541,  8001.6576,  8009.9718};
	
    
    // 使用自定义的滤波器对250个采样点数据进行数字滤波
    int x_length = 250;
    double y[250];
    
    int coeff_length = sizeof(b_bandpass) / sizeof(b_bandpass[0]);

    // 应用filtfilt进行带通滤波
    filtfilt(x, y, x_length, a_bandpass, b_bandpass, coeff_length);
    
    // 打开文件进行写入
    FILE *output_file = fopen("c_output_signal_blog.txt", "w");
    
    if (output_file == NULL) {
        printf("Failed to open file for writing!\n");
        return 1;
    }
    
    // 输出结果
    printf("Output result after bandpass filtering in C language:\n");
    for (int i = 0; i < x_length; i++) {
        printf("%f, ", y[i]);
        // 打开文件进行写入
        fprintf(output_file, "%f\n", y[i]);
    }
    
    return 0;
}

4. 实现效果对比

当使用相同的滤波系数与输入信号时,python、c、matlab三种编程语言实现的零相位IIR型滤波器输出信号在绘图时曲线应完全重合,如下图所示:

在这里插入图片描述

;