这实际上比最初看起来更难回答。@hotpaw2 给出的答案是完全正确的,并且比我发现的任何其他资源都更好地拼写出来,但它仍然只是一个大纲,我花了几个小时才把所有的肉都放在骨头上。
希望其他人也能找到相关的问题(并供我自己将来参考),这里有一个更彻底的解释:
假设您在索引 ind 处有一个(本地)最大值(如问题的情况)。
第 1 步:尝试通过使用两个周围的值来内插更精确的最大值位置。这在很多地方都有很好的解释,例如https://www.dsprelated.com/freebooks/sasp/Quadratic_Interpolation_Spectral_Peaks.html对如何做到这一点有很好的解释,但 TL:DR 版本是:
delta = 0.5*(X[ind-1]-X[ind+1])/(X[ind-1]-2*X[ind]+X[ind+1])
p0 = ind+delta
估计峰值为p0
(如果您想要更精确的估计,log(X[ind-1])
请改用,或全力以赴并使用sinc
功能,但对于大多数目的,上面的增量就足够了)
第 2 步:棘手的部分:使用该位置插入相位。第一个直觉是使用我们刚刚找到的增量进行简单的线性插值:
i0 = floor(p0); w = p0-i0; wp = 1-w
ang = wp*angle(X[i0]) + w*angle(X[i0+1])
由于多种原因,这将不起作用,其中大部分由@hotpaw2 概述。第一个是这不是你平均角度的方式,因为它们以 2pi 为模,所以 0 和 2pi 应该是相似的。更正确的方法是对归一化复数进行平均:
ang = angle(wp*X[i0]/abs(X[i0]) + w*X[i0+1]/abs(X[i0+1]))
但是,这仍然是不正确的,因为如果峰值在 i0 和 i0+1 之间,则相位会在此处翻转 180 度(pi 弧度),从而使平均值非常具有误导性。要修复此“相位翻转”,您必须 (a)fftshift
在 fft 之前执行(是的,在时域中)或 (b) 翻转 X 的每个奇数索引值的相位(通过将复数乘以 - 1)或(如果您不愿意像我一样触摸FFT),您也可以(c)使用以下代码模拟方法(b):
i0 = floor(p0); w = p0-i0; wp = 1-w
if (i0 % 2 == 1) { w*=-1; wp*=-1 } # Flip both if i0 odd
ang = angle(wp*X[i0]/abs(X[i0]) - w*X[i0+1]/abs(X[i0+1])) # Note the "-" here!
这将为您提供(大部分)正确的相位,但对于余弦并且位于 fft 窗口的中心。
第 3 步(可选):如果您需要正弦相位并且从窗口的开头开始,则需要添加一个校正因子:
ang_beg = ang - (2*pi*p0/N)*N/2 + pi*0.5 = ang - pi*(p0 - 0.5)
(0.5*pi
将 cos 转换为 sin,并-p0*pi
转换为窗口的开头)。这似乎有效,至少在我需要它的 Phase Vocoder 中。希望其他人也会发现这很有用。
顺便说一句,纯正弦波不需要相位插值,angle(X[i0]) = angle(-X[i0+1])
因此您可以直接使用它。对于实际信号,可能会有一些偏差,因此插值增加了一些鲁棒性,这通常是一个好主意,尽管使用w
andwp
和归一化可能会过大,angle(sgn*(X[i0]-X[i0+1))
通常就足够了。
非常欢迎对这一切提出任何意见。我不是 DSP 专家,所以我可能在某些细节上是错误的,但这似乎确实有效,所以希望其他人也会发现它有用。