7

关于 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

因此,我们的 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 不仅仅是计算基频(幅度最高的 bin),但到目前为止我对音高检测中的 FFT 的理解是否正确?

有什么方法可以在不牺牲延迟的情况下提高 FFT 的准确性?

4

4 回答 4

3

关于您的第一个问题(“到目前为止,我对音高检测中 FFT 的理解是否正确?”)我会说是的,但我想指出一个陷阱:

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

二进制频率 (Hz) = 3 * 86.132 = 258.396Hz

请注意,第 0 个 bin 代表 0 Hz。这意味着代表 3 * 86.132 = 258.396Hz 的 bin 位于结果数组的第 4 位。

并且要完成这个索引陷阱,如果您的 FFT 为 512 点 (=fftsize),则索引值 256 表示奈奎斯特频率 (= 采样频率 / 2)。这意味着您总是会得到 fftsize/2+1 个代表真实频谱的 bin,即在您的情况下为 257 个 bin。

关于您的第二个问题,有两种广泛而简单的方法可以提高频率检测精度:

  1. 零填充(参见例如为什么使用零填充的一些答案

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

最后,一个没有被问到的答案:绝对推荐应用窗口函数,不仅因为它是抛物线插值的前提,而且因为它降低了显着人工旁瓣的幅度。

于 2014-05-31T06:00:22.347 回答
3

除了@HartmutPfitzinger 推荐零填充和插值的好答案之外,值得指出的是,您可以从限时信号提取的傅里叶变换中获得的信息存在重要的基本限制。

考虑零填充的极限情况——例如,获取单个样本,然后将其填充到 1 秒的持续时间,以便进行具有 1 Hz 分辨率的傅里叶变换。很明显,一个非常短的信号片段根本不包含有关周期性的信息。直观地说,我们需要一个比所讨论的周期更长的片段才能说出信号是否真的在那个周期重复。

如果我们对周期信号的形状有限制,我们可以做得更好。例如,如果我们只寻找单个正弦曲线(即,我们知道我们的信号是 s(t) = A*cos(w*t + phi)),那么我们可以求解未知的幅度 A、频率 w 和相位phi 使用少至三个 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

于 2014-06-03T15:45:53.497 回答
3

您需要首先了解“音高”到底是什么。当在吉他或钢琴上发出一个音符时,我们听到的不仅仅是一个声音振动频率,而是在不同数学相关频率上发生的多个声音振动的复合。这种不同频率的振动复合的元素被称为谐波或分音。例如,如果我们按下钢琴上的中间 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)。

下面,在 GitHub.com,是我设计的一种不寻常的两阶段算法的 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 音符的所有谐波来计算 Octave。该算法旨在检测和弦 MP3 文件中任何给定时刻的最主要音高(音符)。这通常对应于器乐独奏的音符。那些对我的两阶段音高检测算法的 C++ 源代码感兴趣的人可能希望从 GitHub.com 上的 SPitchCalc.cpp 文件中的 Estimate_ScalePitch() 函数开始。

https://github.com/CreativeDetectors/PitchScope_Player

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

在此处输入图像描述

于 2017-01-16T17:00:33.157 回答
1

通常,增加 FFT 的长度不仅会增加延迟,而且还会使检测频率变得困难,因为它可能不是恒定的。
使用重叠和窗口化的常见做法,看看这个:http ://en.wikipedia.org/wiki/Spectral_density_estimation

在峰值检测之后,有几种方法可以提高估计频率的准确性。例如,通过对傅立叶系数进行插值。
看看这里的第 2节或这里的第 1.3 节

于 2014-05-28T20:01:28.073 回答