我对FFT和音高检测的理解是否正确?
关于Stackoverflow和关于FFT和音调检测的讨论已经有无数次。
一般认为FFT虽然速度很快,但对于很多应用来说并不是很准确,但常常不能解释为什么。
我想解释一下为什么会出现这种情况,我希望有人比我更聪明,能够纠正我,并填补我无法填补的空白。
FFT将输入数据从时域转换到频域。
最初,我们从一系列数据开始,如果我们要在图表上绘制曲线,则会得到Y轴上给定时间点的声音幅度和X轴上的时间。 这是在时间域。
FFT将这些时间点的振幅值转换为不同频率的振幅。
从FFT输出的数据数量与数据输入的数量相同
如果我们输入10个时间点的振幅(10个采样),则FFT将输出这些采样内10个不同频率的振幅(乘以虚数和实数的sqrt后)。
哪些频率由以下因素决定:
我们将FFT的输出称为bin,每个bin的宽度通过将采样率除以FFT中的采样数来计算:
bin width = Sample Rate(Hz)/FFT Length (n samples)
有了一些真正的价值,那可能是:
bin_width = 44100/512 = 86.132
所以我们有512个来自我们的FFT的bin(记住它的数据输入是一样的),每一个都跨越86.132Hz。
所以对于给定的bin,我们可以通过以下公式计算它代表的频率:
Bin Freq (Hz) = Bin number (n) * bin width (Hz)
使用上面的值,FFT输出中的第三个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,但是到目前为止,我对于Pitch Detection中的FFT的理解是否正确?
有什么方法可以在不牺牲延迟的情况下提高FFT的准确性?
除了@HartmutPfitzinger推荐的零填充和插值的好的答案之外,值得指出的是,从时间限制信号提取的傅里叶变换中可以获得的信息有重要的基本限制。
考虑零填充的极限情况 - 例如,获取单个样本,然后将其填充至1秒的持续时间,以便以1 Hz的分辨率进行傅里叶变换。 很明显,一个非常短的信号片段根本不包含有关周期性的信息。 直观地说,我们需要一个比所讨论的时间更长的片段,以便能够说出信号是否真的在那段时间重复。
如果我们对周期信号的形状有限制,我们可以做得更好。 例如,如果我们只寻找单个正弦曲线(即我们知道我们的信号是s(t)= A * cos(w * t + phi)),那么我们可以求解未知幅度A,频率w和相位使用少至三个s(t)的样本。 然而,我们正在查看完全符合该公式的信号是非常罕见的。 至少我们期望增加噪声,但通常我们有很多谐波,即未知的非正弦周期性波形。
如果您尝试执行上面提到的内插峰值拾取和/或零填充,然后查看在缩短信号摘录时的结果(同时保持FFT长度相同),您将看到不确定性(误差)随着片段变短而增加 - 当片段短于您要测量的周期长度的两倍时,您可能会得到无用的结果。
这说明了一个有点违反直觉但非常基本的限制: 根据短于T秒的观测,很难确定信号的频率优于1 / T Hz。 这有时被称为不确定性原理,在数学上它与量子力学的海森堡不确定性原理相同。
最后,我用来改善离散傅里叶变换分辨率的另一种技术是瞬时频率,如下所述:
Toshihiko Abe,Takao Kobayashi,Satoshi Imai:在基于瞬时频率的嘈杂环境中利用谐波增强的鲁棒基音估计。 ICSLP 1996(你可以在网上找到PDF,我已经用完了我的链接津贴)。
频率只是时间相位的导数; 事实证明,通过使用不同的窗口函数(一个是另一个的导数)组合两个FFT的实部和虚部,可以使用“部分导数”直接计算每个FFT部分中的瞬时频率。 对于Matlab实现,请参阅
http://labrosa.ee.columbia.edu/matlab/chroma-ansyn/ifgram.m
或者在Python中:
https://github.com/bmcfee/librosa/blob/master/librosa/core.py#L343
关于你的第一个问题 (“我对FFT的基音检测的理解是否正确到目前为止?”)我会说是,但我想指出一个错误:
使用上面的值,FFT输出中的第三个bin将表示振幅为258.398Hz:
Bin Freq(Hz)= 3 * 86.132 = 258.396Hz
请注意,第0个仓代表0 Hz。 这意味着代表3 * 86.132 = 258.396Hz的bin位于结果数组的第4位。
为了完成这个索引陷阱,如果你有一个512点的FFT(= fftsize),那么索引值256代表奈奎斯特频率(=采样频率/ 2)。 这意味着你总是可以得到代表真实频谱的fftsize / 2 + 1 bin ,即你的情况是257个bin。
关于你的第二个问题 ,有两种广泛和简单的方法来提高频率检测精度:
零填充 (请参阅例如为什么使用零填充的答案)
抛物线插值 (参见例如第一个答案)
最后,一个不问的答案:绝对推荐应用一个窗口函数 ,这不仅是因为它是抛物线插值的前提,而且还因为它减少了重要的人造旁瓣的幅度。
通常,增加FFT的长度不仅增加了延迟,而且由于可能不是恒定的,所以也难以检测频率。
它使用重叠和窗口的一种常见做法,看看这个:http://en.wikipedia.org/wiki/Spectral_density_estimation
在峰值检测之后,有几种方法可以提高估计频率的准确性。 例如,通过对傅立叶系数进行插值。
看看这里的第2部分或第1.3部分
上一篇: Is my understanding of FFT and Pitch Detection correct here?