'recovering phase of sine signal from FFT
I have a simple sine function as sin(2*pift+phi). I want to obtain the phase signal phi. I tried to use FFT to calculate phi. In matlab I do the following
f=200; %frequency of sine wave
overSampRate=30; %oversampling rate
fs=overSampRate*f; %sampling frequency
phase = 3/5*pi; %desired phase shift in radians
nCyl = 5; %to generate five cycles of sine wave
t=0:1/fs:nCyl*1/f; %time base
x=sin(2*pi*f*t+phase); %replace with cos if a cosine wave is desired
NFFT=1024; %NFFT-point DFT
X=fft(x,NFFT); %compute DFT using FFT
XX=2*abs(X(1:NFFT/2+1));
[tt ind]=max(XX);
phase_Estimate=angle(X(ind);
This result makes almost no sense to me. For example, when phi=0.523, phase_Estimate is obtained -0.98.
Solution 1:[1]
Using an non-interpolated FFT result phase only works if the period of sinusoid is exactly an integer submultiple of the FFT length. In your example, the sine wave isn't integer periodic in aperture.
If not, you will need to interpolate the phase to get a better estimate. Here's one method to get an better interpolated phase:
First fftshift (rotate by N/2) the data to move the zero phase reference point to the center of the window before doing the FFT. (This is needed to keep the phase from flipping/alternating between adjacent FFT result bins. * )
Then do the FFT and estimate the frequency of the sinusoid by parabolic or, better yet, Sinc interpolation.
Then use the estimated frequency to linearly interpolate the phase between the nearest two FFT result bin phases. Update: Or better yet, use Sinc interpolation of the real and imaginary components of the FFT result separately, then use atan2 on the interpolated IQ components to get an interpolated phase.
Then use the estimated frequency and phase at the center of the window to calculate the phase at some other point, such as the beginning of the FFT window.
Also note that the phase of a sine is different from the phase of a cosine wave by pi/2. atan(im,re) returns the cosine phase.
(* as an alternative to pre-fftshit-ing the data, one could also post-flip the phase of the odd FFT result bins.)
Solution 2:[2]
This is actually much more difficult question to answer than it first seems. The answer @hotpaw2 gives is completely correct and spells it out way better than any other resource I found, but it is still only an outline and it took me a few hours to put all the meat to it's bones.
In the hopes that someone else will also find the question relevant (and for future reference for myself), here is a bit more thorough explanation:
Suppose you have a (local) maximum at index ind (as in the case of question).
Step 1: try to interpolate the more precise location of the maximum by using the two surrounding values. This is well explained in many, many places such as https://www.dsprelated.com/freebooks/sasp/Quadratic_Interpolation_Spectral_Peaks.html has a good explanation for how to do that, but TL:DR version is:
delta = 0.5*(X[ind-1]-X[ind+1])/(X[ind-1]-2*X[ind]+X[ind+1])
p0 = ind+delta
with the estimated peak at p0
(If you want a more precise estimate, use log(X[ind-1]) instead, or go full out and use sinc function, but for most purposes, the delta above is sufficient)
Step 2: the tricky part: use that location to interpolate the phase. The first instinct is to do simple linear interpolation using the delta we just found:
i0 = floor(p0); w = p0-i0; wp = 1-w
ang = wp*angle(X[i0]) + w*angle(X[i0+1])
This WILL NOT WORK for multiple reasons, most of which were outlined by @hotpaw2. The first of them is that this is not how you average angles, as they wrap modulo 2pi so 0 and 2pi should be similar. The more correct approach is to average the normalized complex numbers instead:
ang = angle(wp*X[i0]/abs(X[i0]) + w*X[i0+1]/abs(X[i0+1]))
However, this is still not correct, because if a peak is between i0 and i0+1, the phase flips 180 degrees (pi radians) there, making the average very misleading. To fix this "phase flip", you either have to (a) perform fftshift before fft (yes, in time-domain) or (b) flip the phase of every odd-indexed value of X (achieved by multiplying the complex number with -1) or (in case you are reluctant to touch the FFT as I was), you can also just (c) mock the approach (b) with the following code:
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!
This will give you a (mostly) correct phase, but for a cosine and at the center of fft window.
Step 3 (Optional): If you need the phase for sine and from the beginning of the window, you need to add a correction factor:
ang_beg = ang - (2*pi*p0/N)*N/2 + pi*0.5 = ang - pi*(p0 - 0.5)
(0.5*pi converts cos to sin, and -p0*pi translates to beginning of window).
This seemed to work, at least in the Phase Vocoder I needed it in. Hopefully someone else will also find this useful.
As an aside, the phase interpolation is not needed for a pure sine wave, as angle(X[i0]) = angle(-X[i0+1]) so you can just use it directly. With actual signals, there is likely to be some deviation so interpolation adds some robustness which is usually a good idea, although using w and wp and normalizing is likely overkill and angle(sgn*(X[i0]-X[i0+1)) is usually enough.
Any comments to all this are very welcome. I am not a DSP specialist, so I may well be wrong in some details, bu this does seem to work, so hopefully someone else will also find it useful.
Solution 3:[3]
You're trying to get the phase from the power spectrum (XX) when you should be getting it from the FFT (X). Change:
phase_Estimate=angle(XX(ind));
to:
phase_Estimate=angle(X(ind));
Solution 4:[4]
It maybe late, but I changed Your script a little
f=200; %frequency of sine wave
overSampRate=30; %oversampling rate
fs=overSampRate*f; %sampling frequency
shift = 30
phase = shift*pi/180; %desired phase shift in radians
nCyl = 5; %to generate five cycles of sine wave
t=0:1/fs:nCyl*1/f; %time base
x=cos(2*pi*f*t+phase); %replace with cos if a cosine wave is desired
NFFT=4096; %NFFT-point DFT
X=fft(x,NFFT); %compute DFT using FFT
XX=2*abs(X(1:NFFT/2+1));
[tt, ind]=max(XX);
phase_Estimate = angle(X(ind)) * 360/(2*pi)
It spits out quite close results to what I would expect.
I changed the x vector generation to cosine, calculated degrees in phase_Estimate instead of radians, and made it easy to change input phase shift.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | Paul R |
| Solution 4 | Kai - Kazuya Ito |
