【基于pyAudioKits的Python音频信号处理(五)】音频信号的短时分析方法

pyAudioKits是基于librosa和其他库的强大Python音频工作流支持。

API速查手册

通过pip安装:

pip install pyAudioKits

本项目的GitHub地址,如果这个项目帮助到了你,请为它点上一颗star,谢谢你的支持!如果你在使用过程中有任何问题,请在评论区留言或在GitHub上提issue,我将持续对该项目进行维护。

import pyAudioKits.audio as ak

在讨论数字音频信号的表示时,我们曾经讨论过音频的截断问题。而截断等价于首先采样得到 { x d [ n ] , − ∞ ≤ n < ∞ } \{x_d[n],-∞≤n<∞\} { xd[n],n<},再乘上一个矩形窗 w [ n ] = { 1 0 ≤ n < N m a x 0 o t h e r s w[n]=\begin{cases}1\quad 0≤n<N_{max}\\0\quad others\end{cases} w[n]={ 10n<Nmax0others得到 x [ n ] = { x d [ n ] 0 ≤ n < N m a x 0 o t h e r s x[n]=\begin{cases}x_d[n]\quad 0≤n<N_{max}\\0\quad others\end{cases} x[n]={ xd[n]0n<Nmax0others。这个操作称为加窗

通过加窗,也可以把音频截断成许多小段并排列成矩阵: x [ k , n ] = x [ k R + n ] w [ n ] , w [ n ] = { 1 0 ≤ n < L 0 o t h e r s x[k,n]=x[kR+n]w[n],w[n]=\begin{cases}1\quad 0≤n<L\\0\quad others\end{cases} x[k,n]=x[kR+n]w[n],w[n]={ 10n<L0others,其中 R R R被称为步长 L L L被称为窗长。这被称为分帧加窗。除了对整个音频的向量表示之外,分帧加窗后的矩阵表示也是音频信号的重要表示方法之一。

前两节的所用到的分析方法都是对整个音频应用的,得到的能量、功率、自相关函数、频谱、功率谱等信息反映的都是音频的整体特点。第三节的最后我们提到了一个随机过程通式: X t = c 0 + ∑ k = 1 ∞ [ a k s i n ( 2 π k T t ) + b k c o s ( 2 π k T t ) ] + α t , − ∞ < t < ∞ X_t=c_0+\displaystyle \sum_{k=1}^∞ [a_k sin(\frac{2\pi k}{T} t)+b_k cos(\frac{2\pi k}{T} t)]+\alpha_t,-∞<t<∞ Xt=c0+k=1[aksin(T2πkt)+bkcos(T2πkt)]+αt,<t<,其中 α t \alpha_t αt描述了随机振动,而 ∑ k = 1 ∞ [ a k s i n ( 2 π k T t ) + b k c o s ( 2 π k T t ) ] + c 0 \displaystyle \sum_{k=1}^∞ [a_k sin(\frac{2\pi k}{T} t)+b_k cos(\frac{2\pi k}{T} t)]+c_0 k=1[aksin(T2πkt)+bkcos(T2πkt)]+c0则是任意确定性周期信号的傅里叶级数表示形式,当时我们假设 a k a_k ak b k b_k bk c 0 c_0 c0 T T T均为随时间不变的常数,且 α t \alpha_t αt是平稳随机过程的一次实现,从而可以运用前两节中所用到的分析方法。然而,假如 a k ( t ) a_k(t) ak(t) b k ( t ) b_k(t) bk(t) c 0 ( t ) c_0(t) c0(t) T ( t ) T(t) T(t)随时间变化,且 α t \alpha_t αt非平稳,此时将该随机过程分解为确定和随机部分后,确定周期信号便不再具有每时每刻固定的频率成分,而随机信号也不再是平稳随机过程,此时使用前两节提到的分析方法就有一定的局限性。

首先我们创建三段持续时间均为1s,但是振幅和频率不同的单音,然后将它们拼接到一起。

monotone1=ak.create_Single_Freq_Audio(0.02,440,44100,1)
monotone2=ak.create_Single_Freq_Audio(0.01,880,44100,1)
monotone3=ak.create_Single_Freq_Audio(0.03,660,44100,1)
variating_tone = ak.concatenate([monotone1, monotone2, monotone3])

绘制拼接后的音频0到0.01s、1到1.01s和2到2.01s内的波形图,对比振幅和频率的不同。

variating_tone.plot(0, 0.01), variating_tone.plot(1, 1.01), variating_tone.plot(2, 2.01)

请添加图片描述
请添加图片描述

请添加图片描述

统计音频的功率,可以发现0到0.01s、1到1.01s和2到2.01s内音频的功率均不同。其中由于1到1.01s内单音振幅最小,因此功率也是最小的;而由于2到2.01s内单音振幅最大,功率也是最大的。整段音频的功率则介于最大和最小功率之间。

import pyAudioKits.analyse as aly

aly.power(variating_tone), aly.power(variating_tone[0.0:0.01]), aly.power(variating_tone[1.0:1.01]), aly.power(variating_tone[2.0:2.01])

'''
outputs:
0.00023333333333333336
0.00020327892831286895
5.016181796717433e-05
0.00044498231903136834
'''

整段音频的自相关函数以约 4.54 s 4.54s 4.54s为周期,其频率为 220 H z 220Hz 220Hz,这是频率为 440 H z 440Hz 440Hz 660 H z 660Hz 660Hz 880 H z 880Hz 880Hz的单音的频率的最大公约数。但是截取0到0.5s、1到2.5s和1到2.5s内的音频进行自相关,其自相关函数周期结果都是不同的,分别为 2.27 m s 2.27ms 2.27ms 1.14 m s 1.14ms 1.14ms 1.52 m s 1.52ms 1.52ms

aly.autocorr(variating_tone).plot(0, 10, xlabel="t/ms"), aly.autocorr(variating_tone[0.0:0.5]).plot(0, 10, xlabel="t/ms"), aly.autocorr(variating_tone[1.0:1.5]).plot(0, 10, xlabel="t/ms"), aly.autocorr(variating_tone[2.0:2.5]).plot(0, 10, xlabel="t/ms")

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

取1024点进行傅里叶变换,可以发现此时频谱上只出现代表 440 H z 440Hz 440Hz的峰值。这是因为在进行 N N N点傅里叶变换时,我们默认选择音频的前 N N N个样本点。这在音频的频率成分一直保持不变的情况下和使用全部样本点进行傅里叶变换是等价的,只有频谱分辨率上存在区别。但是由于我们的音频此时由三段频率不同的单音组成,因此1024点傅里叶变换得到的频谱无法体现全部的频率成分。

aly.FFT(variating_tone, 1024).plot(0,1000)

请添加图片描述

使用全部样本点进行傅里叶变换可以体现所有的频率成分,但是不能体现“什么时间有什么频率成分”这一信息。

aly.FFT(variating_tone).plot(0,1000)

请添加图片描述

功率谱密度也存在相同的问题。

aly.PSD(variating_tone).plot(0,1000)

请添加图片描述

分帧加窗使得我们可以分析音频的短时特点。当使用的窗长足够小时,我们可以将每个窗口内的音频视为由频率成分恒定的确定信号和服从平稳随机过程的随机信号组成,从而在每个窗口内运用前两节中提到的分析方法。

使用pyAudioKits的加窗函数来对音频进行分帧加窗。步长以窗与窗之间重叠的比例来计算(像示例中的0.5表示步长为窗长的一半,取0则表示窗与窗之间不重叠,此时步长等于窗长)。

variating_tone_framed = variating_tone.framing(0.03,0.5)	#使用窗长为0.03s、步长为0.015s的矩形窗分帧加窗

对分帧加窗后的音频,同样可以应用之前使用的所有分析统计函数,而返回结果则会多出一个帧维度。该维度默认还是以时间为单位,也可以以样本点数和帧数为单位。

此时可以绘制功率随时间变化的图像。

aly.power(variating_tone_framed).plot()

请添加图片描述

也可以表示为增益的形式。

aly.power(variating_tone_framed,dB=True).plot()

请添加图片描述

以帧数为x轴单位来展示结果。

aly.power(variating_tone_framed).plot(xlabel="frame")

请添加图片描述

自相关函数随时间变化的二维图像上,可以通过条纹间距来清晰分辨不同时间段的单音周期。

aly.autocorr(variating_tone_framed).plot(ylabel="t/ms")

请添加图片描述

使用getMaxFrequency可以获取每个时段振幅最大的频率成分及振幅。

aly.getMaxFrequency(variating_tone_framed)[0].plot(xlabel="t/ms")

请添加图片描述

aly.getMaxFrequency(variating_tone_framed)[1].plot(xlabel="t/ms")

请添加图片描述

对分帧加窗结果进行1024点傅里叶变换,我们可以发现此时三个频率成分都可以被顺利展示。展示的默认是幅度谱,根据色调的冷暖可以分辨不同频率成分的幅度。

我们将分帧加窗后得到的具有帧维度的频谱称为语谱

我们可以看到每个频率成分除了中心的幅度最大的主值外,旁边还有渐近为0的旁瓣,这就是频谱泄漏在语谱上的体现。频谱分辨率越低,这种现象就越明显。

aly.FFT(variating_tone_framed,1024).plot(ystart=0, yend=1000)

请添加图片描述

使用全点傅里叶变换,此时傅里叶变换点数等于每帧内的样本点数。可以看到此时频谱分辨率的提高。

aly.FFT(variating_tone_framed).plot(ystart=0, yend=1000)

请添加图片描述

类似地,功率谱密度随时间的变化也可以展现出来。

aly.PSD(variating_tone_framed,1024).plot(ystart=0, yend=1000)

请添加图片描述

注意到在不同频率成分的交界处,频谱会出现模糊的现象。我们尝试不同的窗长。

variating_tone_framed2 = variating_tone.framing(0.01,0.5)
aly.FFT(variating_tone_framed2).plot(ystart=0, yend=1000)

请添加图片描述

variating_tone_framed2 = variating_tone.framing(0.06,0.5)
aly.FFT(variating_tone_framed2).plot(ystart=0, yend=1000)

请添加图片描述

可以发现,窗长较短时,不同频率交界处的频谱模糊现象较轻,而窗长较长时,不同频率交界处的频谱模糊现象较重。我们称当窗长较短时,语谱具有较高的时间分辨率。在示例音频中,频率成分改变并不频繁,因此时间分辨率的影响似乎较小。但之后涉及到语音信号处理时,由于音频的频率成分复杂多变,时间分辨率就是不得不考虑的问题。

而同时我们又可以观察到,当窗长较长时,语谱具有较高的频谱分辨率。因此在构造语谱时对窗长的选择需要在时间分辨率和频谱分辨率之间进行权衡。0.03s是一个比较通用的数值。

分帧加窗时,除了加默认的矩形窗外,还可以加汉宁窗、汉明窗和Kaiser窗,这些窗能有效改善频谱泄漏问题。

  • 汉宁窗: w [ n ] = { 0.5 + 0.5 ( 2 π n 2 L + 1 ) , 0 ≤ n < L 0 , o t h e r s w[n]=\begin{cases}0.5+0.5(2\pi\frac{n}{2L+1}),& 0≤n<L\\0,& others\end{cases} w[n]={ 0.5+0.5(2π2L+1n),0,0n<Lothers

  • 汉明窗: w [ n ] = { 0.54 + 0.46 ( 2 π n 2 L + 1 ) , 0 ≤ n < L 0 , o t h e r s w[n]=\begin{cases}0.54+0.46(2\pi\frac{n}{2L+1}),& 0≤n<L\\0,& others\end{cases} w[n]={ 0.54+0.46(2π2L+1n),0,0n<Lothers

  • Kaiser窗: w [ n ] = { I 0 [ β 1 − ( 1 − 2 n L − 1 ) 2 ] I 0 ( α ) , 0 ≤ n < L 0 , o t h e r s w[n]=\begin{cases}\frac{I_0[\beta\sqrt{1-(1-\frac{2n}{L-1})^2}]}{I_0(\alpha)},& 0≤n<L\\0,& others\end{cases} w[n]={ I0(α)I0[β1(1L12n)2 ],0,0n<Lothers,其中 I 0 I_0 I0为第一类贝塞尔函数,而 β \beta β是待指定参数。

variating_tone_framed2 = variating_tone.framing(0.06,0.5,window="hann")
aly.FFT(variating_tone_framed2).plot(ystart=0, yend=1000)

请添加图片描述

variating_tone_framed2 = variating_tone.framing(0.06,0.5,window="hamming")
aly.FFT(variating_tone_framed2).plot(ystart=0, yend=1000)

请添加图片描述

variating_tone_framed2 = variating_tone.framing(0.06,0.5,window=("kaiser",4.0))
aly.FFT(variating_tone_framed2).plot(ystart=0, yend=1000)

请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43441742/article/details/126093596