使用 GNU Octave remez 函数的基于 C++ 的 FIR 滤波器设计

信息处理 过滤器 过滤器设计 C++ 软件实现 帕克斯-麦克莱伦
2022-02-19 04:46:06

我正在设计一个需要 FIR 滤波器系数生成器功能的系统。我尝试使用 Octave Signal 包中提供的 remez.cc 源文件。

remez.cc 的源代码在这里我删除了 octave 接口部分(第 757 行 - 结束)和库头(第 34 行),并在第 592 行创建了一个只有 remez 函数的简单头,然后将源代码编译到静态库中。

#pragma once
// remez.h
/********************
* remez
*=======
* Calculates the optimal (in the Chebyshev/minimax sense)
* FIR filter impulse response given a set of band edges,
* the desired response on those bands, and the weight given to
* the error in those bands.
*
* INPUT:
* ------
* int     numtaps     - Number of filter coefficients
* int     numband     - Number of bands in filter specification
* double  bands[]     - User-specified band edges [2 * numband]
* double  des[]       - User-specified band responses [numband]
* double  weight[]    - User-specified error weights [numband]
* int     type        - Type of filter
*
* OUTPUT:
* -------
* double h[]      - Impulse response of final filter [numtaps]
* returns         - true on success, false on failure to converge
********************/
int remez(double h[], int numtaps,
          int numband, const double bands[],
          const double des[], const double weight[],
          int type, int griddensity);

然后我按照一个 Octave示例进行滤波器设计,并实现了一个 C++ 主函数来试验如何调用 remez 函数。这是我的主要功能。

#include <remez.h>
#include <cmath>
#include <vector>
#include <iostream>

using namespace std;

int main()
{
    int numtaps = 52;
    vector<double> h(numtaps);
    vector<double> bands = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 };
    int numbands = bands.size() / 2;
    vector<double> des = { 1, 1, 0, 0, 0.5, 0.5, 0, 0, 1, 1 };  // designed response for each band

    double ripple1 = 1 - pow(10, -0.3 / 20);
    double att2 = pow(10, -60 / 20);
    double ripple3 = (1 - pow(10, -0.2 / 20)) * 0.5;
    double att4 = pow(10, -70 / 20);
    double ripple5 = 1 - pow(10, -0.1 / 20);
    vector<double> weight = { 1 / ripple1, 1 / att2, 1 / ripple3, 1 / att4, 1 / ripple5 };

    int err = remez(h.data(), numtaps, numbands, bands.data(), des.data(), weight.data(), 1, 16);
    for (auto &c : h)
        cout << c << ", ";
    cout << endl;
    cout << "error code: " << err << endl;
    return 0;
}

这个函数调用会产生一些奇怪的系数。h 向量内的所有值都是 10e144 的东西。这显然是一个溢出,所以我调试了 remez.cc 并追踪了导致这种情况的原因。

remez 函数文档指定 des 数组的大小应为 numband。remez 函数中的 des 数组在第 86 行被传递给 CreateDenseGrid 函数用于网格生成。第 73 行 CreateDenseGrid 的文档表明其参数 des 的大小应为 2*numband。显然,由于我的 des 数组的大小仅为 numband,因此第 110 行发生了一些超出范围的内存访问。

我将 des 向量更改为 {1, 1, 0, 0, 0.5, 0.5, 0, 0, 1, 1} 并再次尝试。系数看起来更好,但是当我在 Octave 中绘制频率响应时,它与我预期的滤波器设计并不接近。

我得到了这个奇怪的频率响应: 实际频率响应

任何人都可以帮助我解决这个问题吗?我的 remez 函数调用有什么问题吗?任何建议在这里表示赞赏。非常感谢你。

顺便一提。我正在使用 vs2015、windows7 64 位、Debug X86 build 进行编译器设置。

2个回答

经过一些调试和 remez.cc 源代码调查,我找到了使用 Octave 的 remez 函数的后端 C++ 实现的正确方法。

感谢@SleuthEye,remez.cc 的最新版本在这里

在 C++ 中调用 remez 函数时,我犯了几个错误。

  1. 在初始化通带/阻带纹波时,由于整数除法,我失去了一些准确性。
  2. 我没有注意到滤波器的 numtaps 应该返回 (numtaps + 1) 的滤波器系数。
  3. 在从 remez.cc 中删除的 Octave 接口部分中,有一些数据处理和有价值的错误代码,指示如何将数据提供给 C++ remez 函数。

这是我使用带有正确输入参数和注释的 remez.cc 更新的代码。

#include <remez.h>
#include <cmath>
#include <vector>
#include <iostream>

using namespace std;

int main()
{
    int filterorder = 52;
    vector<double> h(filterorder + 1);  // filter coefficients buffer should be numtaps + 1
    vector<double> bands = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 };
    int numbands = bands.size() / 2;
    vector<double> des = { 1, 1, 0, 0, 0.5, 0.5, 0, 0, 1, 1 };  // designed response for each band

    /** 
     * casting to double before calculation 
     */
    double ripple1 = 1 - pow(10, -0.3 / 20);
    double att2 = pow(10, static_cast<double>(-60) / 20);
    double ripple3 = (1 - pow(10, -0.2 / 20)) * 0.5;
    double att4 = pow(10, static_cast<double>(-70) / 20);
    double ripple5 = 1 - pow(10, -0.1 / 20);
    vector<double> weight = { 1 / ripple1, 1 / att2, 1 / ripple3, 1 / att4, 1 / ripple5 };

    /**
     * According to remez.cc line 810
     * the frequency band should be devided by 2
     * before feeding into C++ remez function
     */
    for (auto &b : bands)
        b /= 2;

    /**
     * According to remez.cc line 787
     * numtap = filterorder + 1
     */
    int err = remez(h.data(), filterorder + 1, numbands, bands.data(), des.data(), weight.data(), 1, 16);
    cout << "coefficients: " << endl;
    for (auto &c : h)
        cout << c << ", ";
    cout << endl;
    cout << "error code: " << err << endl;
    return 0;
}

remez.cc remez 函数调用中的一些隐藏需求只能在 Octave 接口代码中找到。这些要求在 remez.cc 中的 remez 函数注释中没有记录或记录不正确。修复所有这些差异后,我能够获得正确的频率响应! 频率响应

在 Octave 的信号包中找到的当前实现存在一些已知的错误(例如在 savannah 上的错误 #38134错误 #48078 )(参见sourceforge 上的 remez.cc),更不用说链接到的源似乎滞后与最新版本相比几乎没有(因此您缺少一些已包含以解决各种稳定性和收敛性问题的关键修复程序)。remez

由于您似乎对修改源代码感到相当满意,因此您可能想尝试最新的 remez.cc 源代码其中提交的补丁附加到错误 #38134,或者新提交的firpm替换为remez.