我的理解中FFT和音高检测的相关性是否准确?

7

在Stackoverflow和其他平台上,关于FFT和音高检测的讨论已经数不胜数。

一般认为,FFT虽然快速,但对于很多应用来说准确性并不高,但通常没有解释为什么。

我想解释一下我对此的理解,希望比我聪明的人能够纠正我,并在我无法理解的地方进行补充。


FFT将输入数据从时域转换为频域。

最初,我们从一系列数据开始。如果我们将其绘制在图表上,则Y轴上是声音的振幅,X轴上是时间。这是在时域中。

FFT将这些时间点上的振幅值转换为不同频率上的振幅值。

FFT输出的数据数量与输入的数据数量相同

如果我们输入10个时间点(10个样本)的振幅,FFT将在这些样本中输出10个不同频率的振幅(在乘以虚数和实数的平方根后)。

哪些频率由以下内容确定:

我们将FFT的输出称为 bin ,每个bin的宽度通过将采样率除以FFT中的样本数来计算:

bin width = Sample Rate(Hz)/FFT Length (n samples)

使用一些实际值,可以得到:

bin_width = 44100 / 512 = 86.132

因此,我们从FFT中获得了512个bin(记住输入和输出的数据量是相同的),每个bin跨越86.132 Hz。

因此,对于给定的bin,我们可以通过以下方式计算它代表的频率:

Bin Freq (Hz) = Bin number (n) * bin width (Hz)

使用上面的数值,FFT输出中的第3个bin将代表258.398Hz处的幅度:
Bin Freq (Hz) = 3 * 86.132 = 258.396Hz

这意味着在给定的采样率和缓冲区大小下,FFT输出的精度不能超过±86.132Hz。

如果您需要更高的精度(比如1Hz),则必须降低采样率或增加缓冲区大小(或两者兼备)。

desired bin width: 1Hz = 44100 / 44100  # A buffer size of 44100 would work in this instance 

随着缓冲区大小接近采样率,延迟问题变得更加严重。
FFT Results per second = Sample Rate / Buffer Size = 44100/44100 = 1 FFT per second

每秒 44100 个样本,填充一个 44100 个样本的缓冲区 = 每秒 1 个完整缓冲区。

我意识到 FFT 不仅仅是计算基频(具有最高幅度的二进制数),那么我对于声调检测中 FFT 的理解是否正确?

有没有办法在不牺牲延迟的情况下提高 FFT 的准确性?


请注意,FFT在缓冲区大小为2的幂时效果最佳。 - nalply
@nalply:这只是一些FFT实现的情况,而不是全部。FFT实现是递归的,并且要求在每个步骤中,剩余的数据被分成N个相等的块。根据实现的不同,允许使用不同选择的N,但几乎总是允许使用235较少见。4是多余的(只是2x2),6也是如此(2x3)。因为可以填充到8,所以因子7开始变得不太有用。 - MSalters
4个回答

3
你需要先了解什么是“pitch”。当吉他或钢琴演奏单一音符时,我们所听到的不仅仅是一个声音振动频率,而是由多个以不同数学关系产生振动频率的声音振动组成的复合体。这个由不同频率振动组成的复合体元素被称为谐波或泛音。例如,如果我们按下钢琴上的中央C键,则复合频率的谐波会从261.6 Hz的基频开始,523 Hz是第2谐波,785 Hz是第3谐波,1046 Hz是第4谐波,依此类推。后面的谐波是基频261.6 Hz的整数倍(例如:2 x 261.6 = 523, 3 x 261.6 = 785, 4 x 261.6 = 1046)。
以下是我设计的一种非常规的两阶段算法的C++源代码,可以在Windows上实时检测多声部MP3文件的音高。这个免费应用程序(PitchScope Player,在网上可以下载)经常用于检测吉他或萨克斯风独奏的音符,并作为MP3录音的背景。您可以下载Windows版本的可执行文件,然后选择MP3文件来查看我的算法运行的情况。该算法旨在检测在任何给定时刻内MP3或WAV音乐文件中最显著的音高(即一个音符)。通过检测在MP3录音期间任何给定时刻内最显著的音高(即一个音符)的变化,可以准确推断出音符开始时的情况。
我使用修改过的DFT对数变换(类似于FFT)来首先检测这些可能的谐波,方法是查找具有峰值级别的频率(见下图)。由于我收集修改后的Log DFT数据的方式,因此无需对信号应用窗函数,也不需要进行添加和重叠。我已经创建了DFT,使其频道在对数位置上排列,以直接对齐吉他、萨克斯等乐器的音符产生的谐波频率。
我的音高检测算法实际上是一个两阶段的过程:a) 首先检测ScalePitch(“ScalePitch”有12个可能的音高值:{E,F,F#,G,G#,A,A#,B,C,C#,D,D#} )b) 确定ScalePitch后,然后通过检查4个可能的Octave-Candidate音符的所有谐波来计算八度。该算法旨在检测在多声部MP3文件中任何给定时刻内最显著的音高(即一个音符)。这通常对应于乐器独奏的音符。那些对我两阶段音高检测算法的C++源代码感兴趣的人可能希望从GitHub.com的SPitchCalc.cpp文件中的Estimate_ScalePitch()函数开始。 https://github.com/CreativeDetectors/PitchScope_Player

https://en.wikipedia.org/wiki/Transcription_(music)#Pitch_detection

enter image description here


3
关于您的第一个问题(“我对基音检测中FFT的理解正确吗?”),我想说是的,但我想指出一个陷阱:

使用上述值,FFT输出中的第三个bin将表示258.398Hz处的幅度:

Bin Freq (Hz) = 3 * 86.132 = 258.396Hz

请注意,第0个bin表示0 Hz。这意味着代表3 * 86.132 = 258.396Hz的bin在结果数组的第4个位置。
为了完善这个索引陷阱,如果您有512点(= fftsize)的FFT,则索引值256表示奈奎斯特频率(=采样频率/2)。这意味着您始终会获得fftsize / 2 +1个bin,代表实际频谱,即在您的情况下为257个bin。
关于您的第二个问题,有两种广泛且简单的方法可以提高频率检测精度:
  1. 零填充(见例如 一些关于为什么使用零填充的回答

  2. 抛物线插值(见例如第一个回答

最后,一个未被提问的回答:强烈推荐应用窗函数,不仅因为它是抛物线插值的前提,而且因为它降低了人工旁瓣的幅度。


太棒了!谢谢你!你知道上面链接的方法可以提高多少准确性吗?我需要大约1Hz左右,但每秒需要4个以上的FFT。 - bodacious
与零填充不同(它通过整数增加箱的数量),抛物线插值产生浮点结果,因此如果您选择高斯窗口函数,即使仅使用50ms窗口大小,对于合成信号的有效性也非常好。窗口大小和窗口函数是重要参数。 - Hartmut Pfitzinger
准确地说,bin 0应为0赫兹+/-43.132赫兹。 bin 3的中心频率为258赫兹,但它代表215至301赫兹的频率。 - MSalters
第0个和(fftsiz/2+1)个频率区间的宽度确实是其余频率区间宽度的一半。这是一个好观点。但实际宽度取决于零填充的数量和所选的窗口函数。它几乎从来不是+-半个频率区间距离。 - Hartmut Pfitzinger

3
除了@HartmutPfitzinger的好回答推荐零填充和插值之外,值得指出的是,对于时间限制信号提取的傅里叶变换可以获得的信息存在重要的基本限制。
考虑零填充的极限情况——例如,只取一个样本,然后将其填充到1秒的持续时间以便进行1 Hz分辨率的傅里叶变换。很明显,一个非常短的信号片段根本不包含周期性信息。直观地看,我们需要一个比所讨论的周期更长的片段才能够说出信号是否真正在该周期内重复。
如果我们对周期信号的形状有约束条件,我们可以做得更好。例如,如果我们只寻找单一正弦波(即,我们知道我们的信号是s(t) = A*cos(w*t + phi)),那么我们可以使用仅三个s(t)样本解决未知振幅A、频率w和相位phi。然而,我们很少会看到完全符合这种公式的信号。至少我们期望添加噪声,但通常我们有很多谐波,即未知的、非正弦周期波形。
如果您尝试实现上述建议中的插值峰值拾取和/或零填充,然后在保持FFT长度不变的情况下缩短信号摘录时查看所得到的结果,您会发现随着片段变得更短,不确定性(误差)也会增加 - 直到当片段长度小于您试图测量的周期性的两倍时,您可能会得到无用的结果。
这说明了一个有些反直觉但非常基本的限制:仅依据少于T秒的观察结果很难将信号的频率决定得比1 / T赫兹更好。 这有时被称为不确定性原理,在数学上与量子力学中的海森堡不确定性原理相同。
最后,我使用的另一种提高离散傅里叶变换分辨率的技术是瞬时频率,如以下文章所述:
Toshihiko Abe,Takao Kobayashi,Satoshi Imai:基于瞬时频率的噪声环境中具有谐波增强的稳健音高估计。ICSLP 1996 (您可以在网上找到PDF,我的链接配额已用完)。

频率只是相位随时间的导数;通过使用不同的窗口函数(一个是另一个的导数),结合两个FFT的实部和虚部,可以使用“分部求导”直接计算每个FFT bin的瞬时频率。有关Matlab实现,请参见

http://labrosa.ee.columbia.edu/matlab/chroma-ansyn/ifgram.m

或者在Python中:

https://github.com/bmcfee/librosa/blob/master/librosa/core.py#L343


你的评论指向了频谱和频率分析的深度。不要忘记提到Wigner Ville变换,当然还有线性预测编码。但问题是关于理解FFT。实际上,带有抛物线插值的FFT比你所建议的要好得多。关键在于“插值”。两个频率之间的频率可以通过相邻频率的振幅重构。当然,在傅里叶频谱中只有正弦波。但是,如果FFT和p.i.不足够,下一步就是高阶谱分析。 - Hartmut Pfitzinger
1
“每个FFT bin中的瞬时频率”非常有趣,但Python链接已经失效了。请更新一下,谢谢。 - user200340

1
一般来说,增加FFT的长度不仅会增加延迟,而且还可能使频率检测变得困难,因为它可能不是恒定的。
常见的做法是使用重叠和窗口技术,可以参考这里:http://en.wikipedia.org/wiki/Spectral_density_estimation 在峰值检测后,有几种提高估计频率精度的方法。例如,通过对傅里叶系数进行插值。
可以查看第2节 here 或第1.3节 here

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接