信号的基频已使用FFT和某些频率估计方法进行了估计,并且位于两个bin
中心之间
/>采样频率是固定的
计算工作不是问题
知道频率,最基本的估算信号基波相应峰值的方法是什么?
一种方法可能是对时间信号进行零填充以提高FFT分辨率,从而使bin中心更接近估计的频率。在这种情况下,我不确定的一点是我是否可以根据需要进行零填充,或者这样做是否有缺点。另一个是在零填充后我应该选择哪个bin中心作为我要从中获取峰值的那个(因为即使填充为零后,也可能无法准确地达到目标频率)。
但是,我也想知道是否还有另一种方法可以提供更好的结果,例如一个估计器,它使用周围两个bin中心的峰值来估计感兴趣频率处的峰值。
#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
评论
FFT之前的零填充是一种方法。另一种方法是应用适合您需要的窗口功能。平顶窗正是为此目的而设计的。当然,如果您已经确切地知道了频率并且只对一种双峰感兴趣,那么可能有比FFT便宜的方法。无需零填充:简单的抛物线插值(3个点:imax-1,imax,imax + 1,其中imax是FFT峰值)将为您提供准确的结果
确保插值功能与窗口功能匹配。平顶是微不足道的,否则您想要匹配的对(例如矩形窗口+ sinc插值,高斯窗口+高斯插值等)
@CedronDawg这个问题及其答案与您的确切频率公式相关(但不相同)。也许您会发现它很有趣。