请假设以下内容:


信号的基频已使用FFT和某些频率估计方法进行了估计,并且位于两个bin
中心之间
/>采样频率是固定的
计算工作不是问题

知道频率,最基本的估算信号基波相应峰值的方法是什么?

一种方法可能是对时间信号进行零填充以提高FFT分辨率,从而使bin中心更接近估计的频率。在这种情况下,我不确定的一点是我是否可以根据需要进行零填充,或者这样做是否有缺点。另一个是在零填充后我应该选择哪个bin中心作为我要从中获取峰值的那个(因为即使填充为零后,也可能无法准确地达到目标频率)。

但是,我也想知道是否还有另一种方法可以提供更好的结果,例如一个估计器,它使用周围两个bin中心的峰值来估计感兴趣频率处的峰值。

评论

FFT之前的零填充是一种方法。另一种方法是应用适合您需要的窗口功能。平顶窗正是为此目的而设计的。当然,如果您已经确切地知道了频率并且只对一种双峰感兴趣,那么可能有比FFT便宜的方法。

无需零填充:简单的抛物线插值(3个点:imax-1,imax,imax + 1,其中imax是FFT峰值)将为您提供准确的结果

确保插值功能与窗口功能匹配。平顶是微不足道的,否则您想要匹配的对(例如矩形窗口+ sinc插值,高斯窗口+高斯插值等)

@CedronDawg这个问题及其答案与您的确切频率公式相关(但不相同)。也许您会发现它很有趣。

#1 楼

我想到的第一个算法是Goertzel算法。该算法通常假定感兴趣的频率是基本频率的整数倍。但是,本文将(通用)算法应用于您感兴趣的情况。


另一个问题是信号模型不正确。它使用2*%pi*(1:siglen)*(Fc/siglen)。应该使用2*%pi*(0:siglen-1)*(Fc/siglen)才能正确输出相位。

我还认为Fc=21.3的频率过低存在问题。当涉及相位/频率估计问题时,低频实值信号往往会出现偏差。

我还尝试了粗略的网格搜索来估算相位,它给出的答案与Goertzel算法相同。

下面的图显示了两个不同频率(Fc=21.3(实线)和Fc=210.3(虚线))的两种估算值(Goertzel:蓝色,粗略:红色)的偏差。如您所见,较高频率的偏置要小得多。

图$ x $轴是初始相位,从0变为$ 2 \ pi $。



评论


$ \ begingroup $
刚刚测试了基于该论文的Goerzel算法的代码。使用输出的DTFT值,可以非常准确地获得峰值。但是,比例因子恰好为1000。因此,如果原始峰为1,234,则在Goerzel之后为1234。有人知道这可能来自哪里吗?
$ \ endgroup $
–lR8n6i
13年10月10日,11:12



$ \ begingroup $
同时做了一些研究。可能与幅度缩放有关:缩放时域幅度=频域系数* 2 / N,其中N是信号的长度。这个假设正确吗?
$ \ endgroup $
–lR8n6i
13年10月10日在12:34

$ \ begingroup $
是的,通常是这样:信号的长度通常以刻度线的形式出现。
$ \ endgroup $
– Peter K.♦
13年10月10日在18:49

$ \ begingroup $
嗨!我刚刚发现,使用Goertzel算法,所得复数系数的幅度非常准确,但相位却完全错误。有人知道这可能来自哪里吗? “相位”是指原始信号的基波中指定的相位滞后。
$ \ endgroup $
–lR8n6i
13年11月12日在16:57



$ \ begingroup $
@ Rickson1982阶段是正确的。您只是没有正确解释它。 :-)请记住:$ \ sin(\ omega_0 t + \ phi)\ leftrightarrow \ frac {j} {2} [e ^ {-j \ phi} \ tilde {\ delta}(\ omega + \ omega_0 + 2 \ pi k)-e ^ {+ j \ phi} \ tilde {\ delta}(\ omega- \ omega_0 + 2 \ pi k)] $,也就是说,与您的位置相差$ \ pi / 2 $(90度)期待。
$ \ endgroup $
– Peter K.♦
13年13月13日在1:39



#2 楼

如果您愿意使用多个相邻的FFT仓位,而不仅仅是2个,那么根据窗口的宽度,复数仓位结果之间的窗口Sinc插值可以产生非常准确的估计值。

窗口Sinc插值通常在高质量音频上采样器中找到,因此关于该主题的论文将具有适合的插值公式,并带有误差分析。

评论


$ \ begingroup $
感谢您的评论。我也将尝试这种方法。
$ \ endgroup $
–lR8n6i
13年10月10日在10:52

#3 楼

如果您使用Flanagan [1],它是根据连续相位频谱的相位差Δϕ(瞬时频率)计算得出的;如果您使用正确的因子(瞬时量值)[2]来重建振幅,请使用归一化的Sinc函数:
$$ \ frac {\ sin(\ pi x)} {(\ pi x)} $$最后,在峰值幅度附近使用抛物线插值可以得到惊人的结果,今天我认为这是最好的方法,使用它,结果总是非常可靠:-)

[1] JL Flanagan和RM Golden,“相位声码器”,贝尔系统公司,技术期刊,第1卷。 45,第1493–1509页,1966年。

[2] K. Dressler,“使用多分辨率FFT的有效实现的正弦提取”,Proc.Natl.Acad.Sci.USA,90:4877。第九国际Conf。关于数字音频效果
(DAFx-06),加拿大蒙特利尔,2006年9月,第247–252页。

评论


$ \ begingroup $
嗨!非常感谢您的所有评论。我扩展了代码(参见下文),以将Goertzel滤波器与抛物线形峰值插值结合起来以获取相位。但是,结果仍然不准确(±3-4度)。这是尽可能接近还是在理解或编码方面存在错误?
$ \ endgroup $
–lR8n6i
13年11月14日在21:46



#4 楼

一种方法是找到最大值并在其附近拟合一个抛物线,然后将抛物线的最大值用作频率和幅度估计。您可以在此处阅读全部内容:https://ccrma.stanford.edu/~jos/sasp/Sinusoidal_Peak_Interpolation.html

#5 楼

几年前,我在遇到这个确切问题时遇到了很多困难。

我发布了这个问题:

https://stackoverflow.com/questions/4633203/extracting精确的频率从fft bins使用帧之间的相变

我最终从头开始进行计算,并发布了我自己问题的答案。

令我惊讶的是,我无法在互联网上找到任何类似的展览。

我将在此处再次发布答案;请注意,该代码是为我的FFT窗口重叠4倍而设计的。

π


此拼图需要两个键才能将其解锁。


第一个键是了解FFT窗口的重叠如何导致二进制相位旋转。
第二个键来自图3.3和3.4(感谢Stephan Bernsee提供)允许在此处复制图片)。
图3.3:


图3.4:



代码:

for (int k = 0; k <= fftFrameSize/2; k++) 
{
    // compute magnitude and phase 
    bins[k].mag = 2.*sqrt(fftBins[k].real*fftBins[k].real + fftBins[k].imag*fftBins[k].imag);
    bins[k].phase = atan2(fftBins[k].imag, fftBins[k].real);

    // Compute phase difference Δϕ fo bin[k]
    double deltaPhase;
    {
        double measuredPhaseDiff = bins[k].phase - gLastPhase[k];
        gLastPhase[k] = bins[k].phase;

        // Subtract expected phase difference <-- FIRST KEY
        // Think of a single wave in a 1024 float frame, with osamp = 4
        //   if the first sample catches it at phase = 0, the next will 
        //   catch it at pi/2 ie 1/4 * 2pi
        double binPhaseExpectedDiscrepancy = M_TWOPI * (double)k / (double)osamp;
        deltaPhase = measuredPhaseDiff - binPhaseExpectedDiscrepancy;

        // Wrap delta phase into [-Pi, Pi) interval 
        deltaPhase -= M_TWOPI * floor(deltaPhase / M_TWOPI + .5);
    }

    // say sampleRate = 40K samps/sec, fftFrameSize = 1024 samps in FFT giving bin[0] thru bin[512]
    // then bin[1] holds one whole wave in the frame, ie 44 waves in 1s ie 44Hz ie sampleRate / fftFrameSize
    double bin1Freq = (double)sampleRate / (double)fftFrameSize;
    bins[k].idealFreq = (double)k * bin1Freq;

    // Consider Δϕ for bin[k] between hops.
    // write as 2π / m.
    // so after m hops, Δϕ = 2π, ie 1 extra cycle has occurred   <-- SECOND KEY
    double m = M_TWOPI / deltaPhase;

    // so, m hops should have bin[k].idealFreq * t_mHops cycles.  plus this extra 1.
    // 
    // bin[k].idealFreq * t_mHops + 1 cycles in t_mHops seconds 
    //   => bins[k].actualFreq = bin[k].idealFreq + 1 / t_mHops
    double tFrame = fftFrameSize / sampleRate;
    double tHop = tFrame / osamp;
    double t_mHops = m * tHop;

    bins[k].freq = bins[k].idealFreq + 1. / t_mHops;
}


评论


$ \ begingroup $
您要插入频率,而OP知道频率并想插入振幅。
$ \ endgroup $
– Finnw
2013年11月15日20:47



#6 楼

此python代码通过抛物线插值(McAulay Quatieri,Serra等成功使用的方法以谐波+残差)为您提供了非常准确的结果(我将其用于许多音符,并且获得的误差小于半音的0.01%)分离技术)

import matplotlib.pyplot as plt
import numpy as np
from scipy.io.wavfile import read
from scipy.fftpack import fft, ifft
import math

(fs, x) = read('test.wav')
if (len(x.shape) == 2):    # if stereo we keep left channel only
 x = x[:,1]

n=x.size
freq = np.arange(n)*1.0/n*fs 
xfft = abs(fft(x))

imax=np.argmax(xfft)  
p=1.0/2*(xfft[imax-1]/xfft[imax]-xfft[imax+1]/xfft[imax])/(xfft[imax-1]/xfft[imax]-2+xfft[imax+1]/xfft[imax])   # parabolic interpolation 
print 'Frequence detectee avec interpolation parabolique :',(imax+p)*1.0/n*fs, 'Hz'


#7 楼

clear all
clc

for phase_orig = 0:pi/18:pi,

%% Specify and generate signal
Amp = 1;                     % Amplitude of signal
Fs = 8000;                   % samples per second
dt = 1/Fs;                   % seconds per sample
Fc = 21.3;                   % Hz
StopTime = 0.25;             % seconds
t = (0:dt:StopTime-dt)';     % seconds

siglen = length(t);
sig = Amp * 1.5 * sin(2*pi*(0:siglen-1)*(Fc/siglen) + phase_orig) + 1.5 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 3) ...
  + 1.5 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 5)+ 0.3 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 7) ...
  + 1.3 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 9)+ 1.4 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 11);

%% Estimate the peak value of the signals fundamental using Goertzel algorithm
peak = 0;
indvec = [Fc-1 Fc Fc+1];

% Check the input data
if ~isvector(sig) || isempty(sig)
  error('X must be a nonempty vector')
end

if ~isvector(indvec) || isempty(indvec)
  error('INDVEC must be a nonempty vector')
end
if ~isreal(indvec)
  error('INDVEC must contain real numbers')
end

% forcing x to be column
sig = reshape(sig,siglen,1);

% initialization
no_freq = length(indvec); %number of frequencies to compute
y = zeros(no_freq,1); %memory allocation for the output coefficients

% Computation via second-order system
% loop over the particular frequencies
for cnt_freq = 1:no_freq
  %for a single frequency:
  %a/ precompute the constants
  pik_term = 2*pi*(indvec(cnt_freq))/(siglen);
  cos_pik_term2 = cos(pik_term) * 2;
  cc = exp(-1i*pik_term); % complex constant
  %b/ state variables
  s0 = 0;
  s1 = 0;
  s2 = 0;
  %c/ 'main' loop
  for ind = 1:siglen-1 %number of iterations is (by one) less than the length of signal
    %new state
    s0 = sig(ind) + cos_pik_term2 * s1 - s2;  % (*)
    %shifting the state variables
    s2 = s1;
    s1 = s0;
  end
  %d/ final computations
  s0 = sig(siglen) + cos_pik_term2 * s1 - s2; %correspond to one extra performing of (*)
  y(cnt_freq) = s0 - s1*cc; %resultant complex coefficient

  %complex multiplication substituting the last iterationA
  %and correcting the phase for (potentially) non-integer valued
  %frequencies at the same time
  y(cnt_freq) = y(cnt_freq) * exp(-1i*pik_term*(siglen-1));
end

  % perfom amplitude scaling
  peak = abs(y(2)) * 2 / siglen

% perform parabolic interpolation to get the phase estimate
phase_orig=phase_orig*180/pi
ym1 = angle(unwrap(y(1)));
y0 = angle(unwrap(y(2)));
yp1 = angle(unwrap(y(3)));

p = (yp1 - ym1)/(2*(2*y0 - yp1 - ym1)); 
phase = y0 - 0.25*(ym1-yp1)*p;
phase_est = phase * 180/pi + 90;
phase_est = mod(phase_est+180,360)-180
end
您正在处理的频率(在8kHz处采样的21.3Hz)非常低。由于这些是实值信号,因此它们在**任何**频率的相位估计中都将表现出偏差。

此图显示了phase_est - phase_orig(红色)相对于Fc = 210.3;的偏差(Fc = 21.3;)与21.3的偏差。如您所见,对于Fs = 800情况,偏移量更为重要。

另一种选择是降低采样率。绿色曲线显示的是8000而不是q4312079q的偏差。



评论


$ \ begingroup $
感谢您的更新!看我的情节;我仍然认为,任何相位估计器都将在如此低的频率上产生偏差。解决该问题的一种方法是使用已知频率(如果已知!)通过查找表校正相位估计偏差。但是您需要注意:偏差会随频率而变化。另一种方法是降低采样率。
$ \ endgroup $
– Peter K.♦
2013年11月15日13:37



$ \ begingroup $
也谢谢你!但是,如果您使用的是Fs = 8000 Hz,而Fc = 210而不是210.3,则偏置看起来会更糟。知道这可能来自哪里吗?
$ \ endgroup $
–lR8n6i
2013年11月15日14:50



$ \ begingroup $
Erk!不知道。 FWIW,Geortzel估计量没有问题:goertzel = atan(imag(y(2()),real(y(2)))* 180 /%pi + 90;。 :-)会再挖一点。关注此空间。
$ \ endgroup $
– Peter K.♦
13年15月15日在15:34

$ \ begingroup $
抛物线插值法没有按照您认为的那样做。特别是,如果用p2 =(abs(y(3))-abs(y(1)))/(2 *(2 * abs(y(2))-abs(y(3) ))-abs(y(1))));相位2 = y0-0.25 *(ym1-yp1)* p2;那么即使Fc = 210,您也可以得到更好的答案。我完全不确定当前的p版本会为您提供任何明智的选择。插值公式用于插值抛物线的AMPLITUDE,但是p插值的相位是...奇数。
$ \ endgroup $
– Peter K.♦
2013年11月15日15:58



$ \ begingroup $
一切都很好,除了峰值位置(p =(yp1-ym1)/(2 *(2 * y0-yp1-ym1)))在某些情况下(如果使用)相位而不是振幅。这是因为相位可能会在+/- 180度边界附近跳跃。修复此问题所需要做的就是将该行更改为我上面的p2计算。
$ \ endgroup $
– Peter K.♦
13年11月15日在16:31