【问题标题】:IIR Lowpass filter in C# breaks in x86 modeC# 中的 IIR 低通滤波器在 x86 模式下中断
【发布时间】:2014-04-17 01:11:18
【问题描述】:

我正在尝试在 C# 中使用 IIR LP 滤波器。它是一个五阶巴特沃斯滤波器。 该代码在 64 位模式下工作,但在 32 位模式下中断。调试显示,参数略有不同,输出提高到无穷大/NAN。 我使用双打进行计算和存储。 正确的参数a[i],b[i]是:

-5 -4,9792522401964
10 9,91722403267282
-10 -9,87615728025693
5 4,91765142871949
-1 -0,979465940928259

32Bit 计算得到这些:

-5 -4,97925281524658
10 9,91722583770752
-10 -9,87615966796875
5 4,91765308380127
-1 -0,979466378688812

过滤代码:

    public void FilterBuffer(float[] srcBuf, long srcPos, float[] dstBuf, long dstPos, long nLen)
    {
        const double kDenormal = 0.000000000000001;
        double denormal = m_invertDenormal ? -kDenormal : kDenormal;
        m_invertDenormal = !m_invertDenormal;

        for (int sampleIdx = 0; sampleIdx < nLen; sampleIdx++)
        {
            double sum = 0.0f;

            m_inHistory[m_histIdx] = srcBuf[srcPos + sampleIdx] + denormal;

            for (int idx = 0; idx < m_aCoeff.Length; idx++)
                sum += m_aCoeff[idx] * m_inHistory[(m_histIdx - idx) & kHistMask];

            for (int idx = 1; idx < m_bCoeff.Length; idx++)
                sum -= m_bCoeff[idx] * m_outHistory[(m_histIdx - idx) & kHistMask];

            m_outHistory[m_histIdx] = sum;
            m_histIdx = (m_histIdx + 1) & kHistMask;
            dstBuf[dstPos + sampleIdx] = (float)sum;
        }
    }

历史记录是 32 个条目,因此 histMask 为“31”以避免模数/比较...

任何想法为什么这不起作用以及如何使其稳定?

【问题讨论】:

  • 如果这样的精度很重要,您是否尝试过使用decimal 而不是floatdouble
  • 我实际上不需要那么精确。双重承诺几个小数。但在这种情况下,大约 7 位有效数字之后就完全错误了。例如:Math.Tan(0.5 * Math.PI * f1 / m_fN) --> 括号中的值是 1,56759062000552(64Bit) 和 1,56759071350098(32Bit) 我担心使用小数会大大降低算法速度。同样在 64 位模式下也不需要...
  • 您至少可以试一试,然后测试性能。您还可以根据架构使用预处理器指令交换 double/decimal(假设您正在构建特定于平台的可执行文件。
  • 在上述代码(包括缓冲区)中使用小数确实有效。然而,在发布模式下性能下降了 100 倍(实际上 64 位比 32 位要差一些)所以这不是一个选项。还有其他想法吗?

标签: c# signal-processing lowpass-filter


【解决方案1】:

IIR 滤波器因对数值精度敏感而臭名昭著,尤其是当递归方程中的项数增加时。幸运的是,在这种情况下,通过将滤波器实现为较小的 1st 和 2nd 阶部分的级联,可以获得不太敏感的不同滤波器拓扑.请注意,虽然您提供的代码可以直接用于实现过滤器部分,但您可能会意识到,作为额外的好处,可以更有效地优化那些固定顺序的构建块。

在推导滤波器部分的系数之前,我们后退一步来获取滤波器设计参数。由于您提到该滤波器是一个 5 阶低通巴特沃斯滤波器,我假设您选择省略 a[0]b[0] 两者都是 1。根据您提供的信息进行回溯,它看起来像该滤波器由截止频率规格为 45Hz 的模拟滤波器生成,并使用双线性变换映射到以 44100Hz 采样率运行的数字滤波器。作为健全性检查,可以从 MATLAB 或this applet 确认滤波器系数:

 a   b
  1, +1
  5, -4.9792522401964
 10, +9.91722403267282
 10, -9.87615728025693
  5, +4.91765142871949
  1, -0.97946594092826

获得等效滤波器所需的第一步是获得极点和零点。该滤波器的极点和零点可以通过以下任一方式获得:

  • 用上面给出的a 系数(对于零点)和b 系数(对于极点)对多项式进行因式分解,或者
  • 通过对项 (s-sk) 应用双线性变换,其中 sk 是 s 平面中的极点,例如可以从 @987654322 获得@(根据您的滤波器设计规格,替换为 wc=2pi*(45/44100) 弧度)。

生成的零位于 z=-1(全部 5 个)。类似地,在 z 平面中得到的极点是:

0.998002182897612 +/- 0.00608551812433764 j
0.994819411183486 +/- 0.00374906249111718 j
0.993609052034203

2nd 阶滤波器部分然后可以通过匹配复共轭极点对,并与等效数量的零点组合来获得。 因此,将 z = x + y j 处的极点与其复共轭和 2 个零点(在 z=-1 处)相结合,滤波器部分的系数为:

1, 1
2, -2x
1, x*x+y*y

同样,对于 z = x 处的实极点以及零点(在 z=-1 处),可以获得 1st 阶滤波器部分:

1, 1
1, -x

提供的 5th 阶过滤器的 3 个过滤器部分如下:

// 1st section
//   using poles at 0.998002182897612 +/- 0.00608551812433764j, and 2 zeros at -1
1,  1
2, -1.996004365795224
1,  0.996045390599240
// 2nd section
//   using poles at 0.994819411183486 +/- 0.00374906249111718j, and 2 zeros at -1
1,  1
2, -1.989638822366971
1,  0.989679716337019
// 3rd section
//   using pole at 0.993609052034203 and a zero at -1
1,  1
1, -0.993609052034203

如前所述,输出是通过级联部分生成的。从而得到如下序列:

f1.FilterBuffer(srcBuf,  srcPos, tmpBuf1, 0,      nLen);
f2.FilterBuffer(tmpBuf1, 0,      tmpBuf2, 0,      nLen);
f3.FilterBuffer(tmpBuf2, 0,      dstBuf,  dstPos, nLen);

请注意,一些临时存储可以重复使用,但为了清楚起见,将其省略。

【讨论】:

  • 感谢您的解释。你是对的,假设已经完成。实际上,过滤器代码确实计算了极点和零点并从中获得 a/b。我使用的代码可以在这里找到:pitchtracker.codeplex.com/SourceControl/latest#PitchTracker/…(虽然我修改了部分代码,但与此问题无关)您能否解释一下,您如何从 5 阶滤波器到滤波器级联?是不是就像“5 阶 = 2 阶 + 2 阶 + 1 阶”(所以 6 阶将是 3 个 2 阶过滤器)?
  • 没错。您将极点/零点划分为复共轭对(奇数阶有 1 个剩余实值极点/零点),因此 6 阶将是 3 个 2 阶滤波器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-11
  • 2015-01-22
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多