Answers:
在特定频率下使用DFT。然后从实部/成像部分计算振幅和相位。它为您提供了参考到采样时间开始的阶段。
在“普通” FFT(或为所有N个谐波计算的DFT)中,通常使用f = k *(sample_rate)/ N来计算频率,其中k是整数。尽管它看起来似乎是牺牲品(特别是对于全整数整数的成员),但是在执行单个DFT时,您实际上可以使用k的非整数值。
例如,假设您已生成(或获得)27 Hz的正弦波的N = 256个点。(假设sample_rate = 200)。256点FFT(或N点DFT)的“正常”频率将对应于:f = k *(sample_rate)/ N = k *(200)/ 256,其中k是整数。但是使用上面列出的参数,非整数“ k”为34.56将对应于27 Hz的频率。这就像创建一个DFT“箱”,该箱正好位于感兴趣的频率(27 Hz。)中心。一些C ++代码(DevC ++编译器)可能如下所示:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cmath>
using namespace std;
// arguments in main needed for Dev-C++ I/O
int main (int nNumberofArgs, char* pszArgs[ ] ) {
const long N = 256 ;
double sample_rate = 200., amp, phase, t, C, S, twopi = 6.2831853071795865;
double r[N] = {0.}, i[N] = {0.}, R = 0., I = 0. ;
long n ;
// k need not be integer
double k = 34.56;
// generate real points
for (n = 0; n < N; n++) {
t = n/sample_rate;
r[n] = 10.*cos(twopi*27.*t - twopi/4.);
} // end for
// compute one DFT
for (n = 0; n < N; n++) {
C = cos(twopi*n*k/N); S = sin(twopi*n*k/N);
R = R + r[n]*C + i[n]*S;
I = I + i[n]*C - r[n]*S;
} // end for
cout<<"\n\ndft results for N = " << N << "\n";
cout<<"\nindex k real imaginary amplitude phase\n";
amp = 2*sqrt( (R/N)*(R/N) + (I/N)*(I/N) ) ;
phase = atan2( I, R ) ;
// printed R and I are scaled
printf("%4.2f\t%11.8f\t%11.8f\t%11.8f\t%11.8f\n",k,R/N,I/N,amp,phase);
cout << "\n\n";
system ("PAUSE");
return 0;
} // end main
//**** end program
(PS:我希望以上内容可以很好地转换为stackoverflow-其中一些可能会绕来绕去)
上面的结果是-twopi / 4的相位,如生成的实点所示(并且amp被加倍以反映pos / neg频率)。
需要注意的几件事–我使用余弦来生成测试波形并解释结果–您必须注意–相位参考时间= 0,这是您开始采样的时间(即:当您收集r [0]时) ),并且余弦是正确的解释)。
上面的代码既不优雅也不高效(例如:使用查找表获取sin / cos值等)。
当您使用更大的N时,您的结果将变得更加准确,并且由于采样率和上方的N不是彼此的倍数,因此会出现一点误差。
当然,如果要更改采样率N或f,则必须更改代码和k的值。您可以在连续频率线上的任何位置放下DFT仓-只需确保您使用的k值对应于感兴趣的频率即可。
该问题可以表述为(非线性)最小二乘问题:
其中是相对于最小化的目标函数。ϕ
派生非常简单:
可以使用梯度下降法(一阶逼近),牛顿法,高斯-牛顿法或莱文伯格-马夸特方法(需要提供二阶逼近来迭代地最小化上述目标函数。
显然,上述目标函数由于周期性而具有多个极小值,因此可以添加一些惩罚项来区分其他极小值(例如,将到模型方程式中)。但我认为,优化只会收敛到最近的最小值和可更新的结果中减去。
Goertzel算法有几种不同的公式。提供2个状态变量(正交或接近)或复杂的状态变量(可能的输出)的变量通常可以用来参考Goertzel窗口中的某个点(例如中间点)来计算或估计相位。仅提供单个标量输出的输出通常不能。
您还需要知道Goertzel窗口相对于时间轴的位置。
如果您的信号在Goertzel窗口中不是完全整数周期,则窗口中间参考点周围的相位估计可能比将相位参考起点或终点更为准确。
如果您知道信号的频率,则完整的FFT会显得过大。另外,可以将Goertzel调谐到FFT长度上非周期性的频率,而FFT对于非周期性的窗口频率将需要附加的插值或零填充。
复数的Goertzel等效于DFT的1 bin,该DFT对余弦和正弦基向量或FFT旋转因子使用递归。
这取决于您对“快速”的定义,想要的估计精度,想要的是还是相对于采样的相位以及函数和参考正弦波上有多少噪声。
一种实现方法是仅获取的FFT,然后查看最接近的bin 。 但是,这将取决于接近bin中心频率。
所以:
PS:我假设您的意思是,而不是。
这是对@Kevin McGee的建议的改进,该建议使用具有分数bin索引的单频DFT。凯文(Kevin)的算法效果不佳:虽然在半垃圾箱和整个垃圾箱中非常精确,但也接近整体和一半,也相当不错,但是否则误差可能在5%之内,这对于大多数任务来说可能是不可接受的。
我建议通过调整来改进Kevin算法,即DFT窗口的长度,以使尽可能接近整体。这是可行的,因为与FFT不同,DFT不需要为2的幂。
下面的代码在Swift中,但是应该直观清楚:
let f = 27.0 // frequency of the sinusoid we are going to generate
let S = 200.0 // sampling rate
let Nmax = 512 // max DFT window length
let twopi = 2 * Double.pi
// First, calculate k for Nmax, and then round it
var k = round(f * Double(Nmax) / S)
// The magic part: recalculate N to make k as close to whole as possible
// We also need to recalculate k once again due to rounding of N. This is important.
let N = Int(k * S / f)
k = f * Double(N) / S
// Generate the sinusoid
var r: [Double] = []
for i in 0..<N {
let t = Double(i) / S
r.append(sin(twopi * f * t))
}
// Compute single-frequency DFT
var R = 0.0, I = 0.0
let twopikn = twopi * k / Double(N)
for i in 0..<N {
let x = Double(i) * twopikn
R += r[i] * cos(x)
I += r[i] * sin(x)
}
R /= Double(N)
I /= Double(N)
let amp = 2 * sqrt(R * R + I * I)
let phase = atan2(I, R) / twopi
print(String(format: "k = %.2f R = %.8f I = %.8f A = %.8f φ/2π = %.8f", k, R, I, amp, phase))