如何实现IIR滤波器?公式与代码实现相关疑问求助
你好!针对你提出的IIR滤波器相关问题,我来逐一梳理解答:
一、a0的取值是否为1?
没错,你写的这个IIR公式是归一化后的标准形式,这里a0确实取1。原始的IIR滤波器通用表达式是:
a₀·y(n) + a₁·y(n-1) + ... + a_N·y(n-N) = b₀·x(n) + b₁·x(n-1) + ... + b_M₋₁·x(n-M+1)
为了直接得到y(n)的计算式,我们把a₀·y(n)留在等式左侧,其余项移到右侧,再两边除以a₀,就得到:
y(n) = [b₀·x(n)+...+b_M₋₁·x(n-M+1)] - [(a₁/a₀)·y(n-1)+...+(a_N/a₀)·y(n-N)]
当我们将a₀归一化为1时,就简化成了你列出的公式,所以你的理解完全正确,a0取1是这个表达式的前提。
二、反馈部分的代码实现困惑
你的整体思路是对的:把IIR拆成前馈(FIR)部分(bcc)和反馈部分(acc),最后用bcc - acc得到输出。但问题出在反馈部分的历史y值获取上,具体解决方式如下:
核心问题:历史y值的获取
IIR的反馈依赖之前输出的y值(比如y[n-1]是上一次的输出,y[n-2]是上上次的),对于n=0这类边界情况,不存在的历史值(比如y[-1]、y[-2])我们默认取0。
直接修改你的代码
你代码里的////////处,可以替换为边界判断的表达式:
(n - 1 - k >= 0) ? output[n - 1 - k] : 0.0f
解释:当n-1-k >=0时,说明对应的历史y值已经计算并存入output数组,直接取用;否则取0。
更高效的延迟线实现(推荐)
如果想让逻辑更清晰、避免频繁的边界判断,推荐用延迟线数组专门保存历史y值,实现如下:
// 循环外初始化延迟线,所有历史值初始为0 float y_delay[filterLength] = {0}; for (n = 0; n < length; n++) { coeffa = coeffs_A; coeffb = coeffs_B; inputp = &insamp[filterLength - 1 + n]; acc = 0; bcc = 0; // 计算前馈(FIR)部分 for (k = 0; k < filterLength; k++) { bcc += (*coeffb++) * (*inputp--); } // 计算反馈部分:直接从延迟线取历史y值 for (k = 0; k < filterLength; k++) { acc += (*coeffa++) * y_delay[k]; } output[n] = bcc - acc; // 更新延迟线:把新输出放到最前面,旧值依次后移 for (k = filterLength - 1; k > 0; k--) { y_delay[k] = y_delay[k - 1]; } y_delay[0] = output[n]; }
这个方式里,y_delay[0]对应y[n-1],y_delay[1]对应y[n-2],刚好和你的coeffs_A(a1到a7)一一对应,逻辑更直观,也更符合IIR滤波器的经典实现方式。
三、整体思路是否正确?
你的核心思路完全没问题!将IIR分解为前馈的FIR部分加上反馈部分,这是IIR滤波器最基础的实现逻辑,只是之前没处理好反馈部分的历史值获取,修正后就能正常运行了。
内容的提问来源于stack exchange,提问作者Ryun

