Python实现所有算法-音频过滤器.下(巴特沃斯)

原创 云深之无迹 2022-07-16 17:33


上节简单的写了一下音频滤波器的定义和作用。而这篇文章将主要集中精力在巴特沃斯过滤器上,在末尾将会给出:使用 Butterworth 设计的二阶 IIR 滤波器。


另外,因为微信这个垃圾的公式排版,我也使用了:


来进行一个排版


$H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}$

在构建N阶IIR滤波器的时候,使用的传递函数是这样的。


这个


然后会重写成这样:


y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)


因为这个重写的公式太长了,截图就是这样的


接下来就是大家不爱看的东西了,我尽量选通俗易懂的话。先解释频繁出现的IIR滤波器是什么?

IIR(Infinite Impulse Response)——无限脉冲响应滤波器,是数字信号处理的基本组成部分。


1、IIR滤波器是什么

IIR滤波器是用于数字信号处理(DSP)应用的两种主要数字滤波器之一(另一种是FIR)。“IIR”的意思是“无限脉冲响应”。


2、 IIR为什么脉冲响应是“无限的”?

脉冲响应是“无限”的,因为滤波器中有反馈;如果你输入一个脉冲(一个“1”样本后面跟着多个“0”样本),理论上就会输出无限个非零值。


3、 IIR过滤器的替代方案是什么?

DSP滤波器也可以是“有限脉冲响应”(FIR)。FIR滤波器不使用反馈,所以对于N个系数的FIR滤波器,输入N个脉冲响应的样本后输出总是为零。


 4、IIR滤波器(与FIR滤波器相比)的优点是什么?

与类似的FIR滤波器相比,IIR滤波器可以用更少的内存和计算来实现给定的滤波特性。


5、IIR滤波器(与FIR滤波器相比)的缺点是什么?


(1)它们更容易受到有限长度算法问题的影响,比如计算产生的噪声和极限环。(这是反馈造成的直接结果:当输出没有被完美地计算出来并得到反馈时,不完美可能会加剧。)


(2)它们使用定点算法更难(更慢)实现.


(3)对于多速率(抽取和插值)应用,它们没有FIR滤波器的计算优势。


再说一个,什么叫线性时不变系统?


一、线性

通信系统中的线性不再是数学中坐标轴上的直线,也不是所有的直线都符合线性特征,通信系统中的线性要满足一个条件。


为什么线性系统怎么重要。如果已知一个系统是线性系统,一旦有了任何特定输入的输出,就可以在不进行实际测试的情况下计算出所有其他输出。这就是线性系统的优点。也就是系统是可控的

二、时不变系统


一句话概括时不变系统:如果你的系统是时不变系统,如果将相同的输入输入到系统中,无论何时将输入输入到系统中,都将获得相同的输出。


那么我们还假设你的系统是时不变系统,你有一个确定的输入,你只需在知道某个时间下这个输入的所有特征。如果你明天还输入相同的输入,那么你的不用重新掌握它的特征。


相对的如果你的系统是时变系统:


投入了一定的投入,花了大量的时间和精力调查了投入的所有特征。但是无法重用结果,因为当重用它时,时间不同,结果也会不同。就像。。。你有一台电脑。每次运行完全相同的程序时,都会得到不同的结果。


唯一可以重用结果的情况是进一步努力找出规则,以显示输出是如何随时间变化的。不幸的是,你不能保证你能找到规则。即使你很幸运找到了规则,你的系统模型也会非常复杂。


三、总结


线性系统,你知道一个确定的结果,你就知道其他所有的结果,

时不变系统,你的特定输入,其输出的结果不会随着时间而变化。



这个IIR二阶的传递函数是从wiki里面拿到的



因为这个IIR是给下一级使用的,这里设计成一个类



类的初始化




接下来是使用一个现成的包来验证一下结果



返回的是分子和分母的多项式



计算a,b


设置系数用于 IIR 滤波器。这些都应该是 size order + 1,a_0 可以省略,它将使用 1.0 作为默认值。



这就是这样,下面的都是对参数的约束



a,b就是系数



计算f(n),对着公式编程序



这个就是两项计算的公式



按照这个写的


关于滤波器还有若干的问题需要说明:一阶滤波器,思路就是把一个连续的滤波器形式,通过离散化的方式,转换成差分方程。从wiki或者文章里面拿到的滤波器公式,通常是用传递函数表达的,这是S域下的表达形式,是连续的,这种我们称之为模拟滤波器。模拟滤波器传递函数,目的是用来设计滤波电路,针对的是连续时间的模拟信号,组成元器件是电阻,电容,电感。而数字滤波器实现方法是把滤波器所要完成的运算编成程序并让计算机执行,也就是采用在代码的形式。它面对的是离散时间的数字信号,是把输入序列通过一定的运算变换成输出序列。有没有办法能把连续的模拟滤波器变成离散的数字滤波器?


其中最常使用的一种叫做双线性变换:




把这个公式带入传递函数就可以得到一个z域的差分方程了。


以上变换这段参考:



后面截至频率什么的没有写


但是知道接下来应该看的是:自动控制原理。



我们在BW滤波器里面将要实现这些算法


所有滤波器传递函数均源自模拟原型,并已使用双线性变换 (BLT) 进行数字化。BLT 频率扭曲已被考虑到重要的频率重定位(这是使用 BLT 时必需的正常“预扭曲”)和带宽重新调整(因为使用 BLT 从模拟映射到数字时带宽被压缩)。这个就是上面文章里面说的:




出现这个问题,需要使用数学工具矫正


模拟截止频率与数字截止频率的关系



证明自己看吧,我也看不懂了,越看越觉得自己不该念这个书。。。这是TM的什么人间疾苦。



不过看了下。。。有欧拉公式就行



先定义双线性的函数,在上面写了



经过一次变换



最后才写成这个


省去一段推导,给出结果:



低通滤波器的结果



参数是,频率,采样率,Q值



第一个的滤波器的算法设计


在最后给出一个在双线性变换下使用补偿频率扭曲的补偿算法推导:



首先是归一化的操作



使用这个三角函数



还有这个



对公式进行重构,构个头就是重新带进去



三个



还有因子也重新写


对分子和分母中的所有项都是通用的,可以因式分解,因此在上面的替换中被忽略,所以有:



又重写了


此外,所有项,分子和分母,都可以乘以一个共同的:



好大个系数



最后就是简化出来的双二阶系数公式



啧,万物之源是数学,还不滚去学习。



在最终的演示中



这个错误



说是3.8才有


pip install typing_extensions
from typing_extensions import Protocol



可以写成这样



话说有朝一日我还能拍出这种照片



是不是有点古城的味道了嗷!


考虑到这个算法的复杂性,这里将代码写在了一起,可以直接调用:

from __future__ import annotations# from audio_filters.iir_filter import IIRFilterimport numpy as npimport matplotlib.pyplot as pltfrom typing_extensions import Protocolfrom math import pifrom math import cos, sin, sqrt, tau

class IIRFilter: def __init__(self, order: int) -> None: self.order = order # a_{0} ... a_{k} self.a_coeffs = [1.0] + [0.0] * order # b_{0} ... b_{k} self.b_coeffs = [1.0] + [0.0] * order
# x[n-1] ... x[n-k] self.input_history = [0.0] * self.order # y[n-1] ... y[n-k] self.output_history = [0.0] * self.order
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None: if len(a_coeffs) < self.order: a_coeffs = [1.0] + a_coeffs if len(a_coeffs) != self.order + 1: raise ValueError( f"预期 a_coeffs to 有 {self.order + 1} elements for {self.order}" f"-order filter, got {len(a_coeffs)}" ) if len(b_coeffs) != self.order + 1: raise ValueError( f"Expected b_coeffs to have {self.order + 1} elements for {self.order}" f"-order filter, got {len(a_coeffs)}" ) self.a_coeffs = a_coeffs self.b_coeffs = b_coeffs
def process(self, sample: float) -> float: result = 0.0 # 从索引 1 开始,最后执行索引 0 for i in range(1, self.order + 1): result += (self.b_coeffs[i] * self.input_history[i-1]-self.a_coeffs[i]*self.output_history[i-1] ) result = (result + self.b_coeffs[0] * sample) / self.a_coeffs[0] self.input_history[1:] = self.input_history[:-1] self.output_history[1:] = self.output_history[:-1] self.input_history[0] = sample self.output_history[0] = result return result

def make_lowpass( frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor)
b0 = (1 - _cos) / 2 b1 = 1 - _cos
a0 = 1 + alpha a1 = -2 * _cos a2 = 1 - alpha
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b0]) return filt

def make_highpass( frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor)
b0 = (1 + _cos) / 2 b1 = -1 - _cos
a0 = 1 + alpha a1 = -2 * _cos a2 = 1 - alpha
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b0]) return filt

def make_bandpass( frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)) -> IIRFilter: """ 创建带通滤波器
>>> filter = make_bandpass(1000, 48000) >>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE [1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.06526309611002579, 0, -0.06526309611002579] """ w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor)
b0 = _sin / 2 b1 = 0 b2 = -b0
a0 = 1 + alpha a1 = -2 * _cos a2 = 1 - alpha
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) return filt

def make_allpass( frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor)
b0 = 1 - alpha b1 = -2 * _cos b2 = 1 + alpha
filt = IIRFilter(2) filt.set_coefficients([b2, b1, b0], [b0, b1, b2]) return filt

def make_peak( frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor) big_a = 10 ** (gain_db / 40)
b0 = 1 + alpha * big_a b1 = -2 * _cos b2 = 1 - alpha * big_a a0 = 1 + alpha / big_a a1 = -2 * _cos a2 = 1 - alpha / big_a
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) return filt

def make_lowshelf( frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor) big_a = 10 ** (gain_db / 40) pmc = (big_a + 1) - (big_a - 1) * _cos ppmc = (big_a + 1) + (big_a - 1) * _cos mpc = (big_a - 1) - (big_a + 1) * _cos pmpc = (big_a - 1) + (big_a + 1) * _cos aa2 = 2 * sqrt(big_a) * alpha
b0 = big_a * (pmc + aa2) b1 = 2 * big_a * mpc b2 = big_a * (pmc - aa2) a0 = ppmc + aa2 a1 = -2 * pmpc a2 = ppmc - aa2
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) return filt

def make_highshelf( frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)) -> IIRFilter: w0 = tau * frequency / samplerate _sin = sin(w0) _cos = cos(w0) alpha = _sin / (2 * q_factor) big_a = 10 ** (gain_db / 40) pmc = (big_a + 1) - (big_a - 1) * _cos ppmc = (big_a + 1) + (big_a - 1) * _cos mpc = (big_a - 1) - (big_a + 1) * _cos pmpc = (big_a - 1) + (big_a + 1) * _cos aa2 = 2 * sqrt(big_a) * alpha
b0 = big_a * (ppmc + aa2) b1 = -2 * big_a * pmpc b2 = big_a * (ppmc - aa2) a0 = pmc + aa2 a1 = 2 * mpc a2 = pmc - aa2
filt = IIRFilter(2) filt.set_coefficients([a0, a1, a2], [b0, b1, b2]) return filt

class FilterType(Protocol): def process(self, sample: float) -> float: return 0.0

def get_bounds( fft_results: np.ndarray, samplerate: int) -> tuple[int | float, int | float]: lowest = min([-20, np.min(fft_results[1: samplerate // 2 - 1])]) highest = max([20, np.max(fft_results[1: samplerate // 2 - 1])]) return lowest, highest

def show_frequency_response(filter: FilterType, samplerate: int) -> None:
size = 512 inputs = [1] + [0] * (size - 1) outputs = [filter.process(item) for item in inputs]
filler = [0] * (samplerate - size) # zero-padding outputs += filler fft_out = np.abs(np.fft.fft(outputs)) fft_db = 20 * np.log10(fft_out)
# Frequencies on log scale from 24 to nyquist frequency plt.xlim(24, samplerate / 2 - 1) plt.xlabel("Frequency (Hz)") plt.xscale("log")
# Display within reasonable bounds bounds = get_bounds(fft_db, samplerate) plt.ylim(max([-80, bounds[0]]), min([80, bounds[1]])) plt.ylabel("Gain (dB)")
plt.plot(fft_db) plt.show()

def show_phase_response(filter: FilterType, samplerate: int) -> None: size = 512 inputs = [1] + [0] * (size - 1) outputs = [filter.process(item) for item in inputs]
filler = [0] * (samplerate - size) outputs += filler fft_out = np.angle(np.fft.fft(outputs)) plt.xlim(24, samplerate / 2 - 1) plt.xlabel("Frequency (Hz)") plt.xscale("log")
plt.ylim(-2 * pi, 2 * pi) plt.ylabel("Phase shift (Radians)") plt.plot(np.unwrap(fft_out, -2 * pi)) plt.show()

filt = IIRFilter(4)show_phase_response(filt, 48000)


应该是可以直接出现这样的图


https://stackoverflow.com/questions/54274630/can-not-import-protocol-from-typing
https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
https://blog.csdn.net/abc123mma/article/details/120251384
https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
https://blog.csdn.net/SHC2012377/article/details/120910496
https://en.wikipedia.org/wiki/Digital_biquad_filter
https://zhuanlan.zhihu.com/p/357619650
https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html

评论
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 75浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 87浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-12 10:13 42浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 88浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 78浏览
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 86浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 113浏览
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 66浏览
  • 本文介绍瑞芯微RK3588主板/开发板Android12系统下,APK签名文件生成方法。触觉智能EVB3588开发板演示,搭载了瑞芯微RK3588芯片,该开发板是核心板加底板设计,音视频接口、通信接口等各类接口一应俱全,可帮助企业提高产品开发效率,缩短上市时间,降低成本和设计风险。工具准备下载Keytool-ImportKeyPair工具在源码:build/target/product/security/系统初始签名文件目录中,将以下三个文件拷贝出来:platform.pem;platform.
    Industio_触觉智能 2024-12-12 10:27 72浏览
  • 应用环境与极具挑战性的测试需求在服务器制造领域里,系统整合测试(System Integration Test;SIT)是确保产品质量和性能的关键步骤。随着服务器系统的复杂性不断提升,包括:多种硬件组件、操作系统、虚拟化平台以及各种应用程序和服务的整合,服务器制造商面临着更有挑战性的测试需求。这些挑战主要体现在以下五个方面:1. 硬件和软件的高度整合:现代服务器通常包括多个处理器、内存模块、储存设备和网络接口。这些硬件组件必须与操作系统及应用软件无缝整合。SIT测试可以帮助制造商确保这些不同组件
    百佳泰测试实验室 2024-12-12 17:45 66浏览
  • 全球智能电视时代来临这年头若是消费者想随意地从各个通路中选购电视时,不难发现目前市场上的产品都已是具有智能联网功能的智能电视了,可以宣告智能电视的普及时代已到临!Google从2021年开始大力推广Google TV(即原Android TV的升级版),其他各大品牌商也都跟进推出搭载Google TV操作系统的机种,除了Google TV外,LG、Samsung、Panasonic等大厂牌也开发出自家的智能电视平台,可以看出各家业者都一致地看好这块大饼。智能电视的Wi-Fi连线怎么消失了?智能电
    百佳泰测试实验室 2024-12-12 17:33 59浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 109浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦