
Would you like to be notified by email when Markus Nentwig publishes a new blog?
Pageviews: 0
| Summary:
The delay between a signal and a reference appears in the frequency domain as linear phase shift. It can be estimated with subsample accuracy using FFT methods. |
Hello,
there were several recent posts on comp.dsp that involved the following problem:
"The transmitted signal is a pulse, for example five cycles taken from a sine wave. How can I find the pulse location from the received signal?"
It's a textbook problem (matched filter, correlation receiver) and I do hope that it's not a homework problem...
Still, there was quite some discussion. An interesting idea was brought up:
"The delay between transmitted and received pulse appears as linear phase shift in the frequency domain. Therefore, I could take the FFT of received signal and original pulse, calculate the phase difference over frequency, and obtain the delay from the slope of the phase."
One advantage is that the delay can be resolved with subsample accuracy without need for excessive amounts of oversampling.
The problem is that the phase wraps around and is not that straightforward to unroll. But of course it can be done. The given example program is more robust than standard "phase unwrapping" and may work in cases, where the latter fails.
The approach may not be the first choice for hardware implementation (and neither for "matched filtering" homework problems). But it can be quite useful when it comes to for example offline processing of measured data.
Note that FFT processing as used in the example treats signals as cyclic. Usually this is not a restriction, simply use as much zero-padding as appropriate.
The picture below shows an example: the red curve is the transmitted pulse. Blue is the received signal, delayed by 10.45 samples. The example uses a rather short delay for clarity, but it works just as well with longer delay, of course at the expense of more memory and CPU cycles for the FFTs.
The "ringing" before and after the time-shifted pulse remains invisible in the original signal, because it is sampled exactly on the zero-crossings of the waveform (Nyquist's pulse shaping rule for an ideal lowpass filter).

The crosscorrelation is obtained by taking:
ifft(fft(signal) .* conj(fft(reference)))
Below the result for the windowed sin()-pulse:

The windowed sin()-pulse has rather "bad" autocorrelation properties. It is "too similar too itself" at some offsets, resulting in several crosscorrelation peaks. Nonetheless, the highest pulse corresponds to the delay between signal and reference.
For comparison, here is the same result using a different pulse consisting entirely of random numbers: When it is time shifted, it has very little similarity to itself, and there is only one clearly defined peak:

Ideally, it it wouldn't be necessary to search for the peak, because the delay can be determined from the phase of the cross spectral density fft(signal) .* conj(fft(reference)) alone.
The absolute value of above expression is related to the energy (amplitude squared) per bin, of both reference and received signal.
The phase is "the phase of the received signal, rotated back by the phase of the reference", in other words, the phase shift at each frequency caused by the delay.
The picture below shows said phase when using the windowed sin()-pulse: The leftmost bin corresponds to 0 Hz, the center bin to half the sampling frequency. The right half of the picture represents negative frequencies:

By eye, it is easy to see that the phase goes through about ten-and-a-half full turns (exactly: 10.45).
But there are many "spikes", caused by nulls in the spectrum of the pulse. Consequently, it is difficult to automatically unwrap the phase in a general way that is robust in the presence of noise.
Now the delay is already known from the location of the crosscorrelation peak, at least with an accuracy of +/- 0.5 samples.
Therefore, the next step is to rotate the phase backwards to compensate for the known delay (with one sample resolution), leaving only the phase corresponding to a subsample delay. The remaining phase is shown below:

The slope can be determined by using a least-squares fit to the phase of a unity delay, as shown in red.
This works, but is not very accurate. The reason is that most of the bins in the cross-spectrum contain little or no energy. In particular, the windowed sin()-pulse has a rather narrow spectrum.
The solution is to weight the phase values on "both sides" of the least-mean-squares-solution with the energy contained in the respective bin. For the windowed sin-pulse, the result looks as follows:

The location of the peaks correspond to the frequency of the windowed sinewave (positive and negative frequencies). Said pulse is a "difficult" case, because the energy is concentrated in a narrow frequency region.
As it turns out, the weighting improves the accuracy by about eight orders of magnitude! For a noise-free signal, a typical error is below 10-10 the sampling time interval, which could not be easily achieved by simple oversampling.
The program can be found here:
Download (commented Matlab / Octave program)
The relevant code excluding comments and test signal generation consists of the 20 lines below:
| % ref: transmitted signal % sig: received signal n=length(sig); binFreq=(mod(((0:n-1)+floor(n/2)), n)-floor(n/2))/n; sig_FD=fft(sig); ref_FD=fft(ref, n); u=sig_FD .* conj(ref_FD); if mod(n, 2) == 0 u(length(u)/2+1)=0; end Xcor=abs(ifft(u)); integerDelay=find(Xcor==max(Xcor)); integerDelay=integerDelay(1)-1; rotN=exp(2i*pi*integerDelay .* binFreq); uDelayPhase=-2*pi*binFreq; u=u .* rotN; weight=abs(u); uDelayPhase=uDelayPhase.* weight; ang=arg(u).* weight; fractionalDelay=transpose(uDelayPhase)\transpose(ang); result=integerDelay+fractionalDelay; |
Comments, additional references, bug reports are of course welcome!
Cheers
Markus
Update 16.8.2011:
An extended ready-to-use implementation for Matlab can be found in the code snippet.
References:
Similar approach, but uses FFT and oversampling to locate the crosscorrelation peak: http://dspace.dsto.defence.gov.au/dspace/bitstream/1947/4441/1/DSTO-TR-1705%20PR.pdf
Methods that could be used to locate the peak without oversampling, when it falls between bins:
http://www.dspguru.com/howto/tech/peakfft.htm
A similar approach is discussed in:
Bendat & Piersol: "Random Data", Wiley, 20090.
Thanks to Rune Allnor for this reference!
posted by Markus Nentwig Would you like to be notified by email when Markus Nentwig publishes a new blog?