how to estimate the SNR with limited knowledge of the signal

173 views Asked by At

I have a signal that I'm trying to determine the SNR of, but I have limited knowledge of the signal. For the signal that I'm receiving I don't know what the frequency will be, but I do know that frequency won't change. I know that my signal is broken into blocks (symbols), and that the phase difference between any two blocks will either be 0 or 180 degrees. I also know that the signals will have similar amplitudes, so the only thing different between any two blocks of signals should be the phase and whatever noise is affecting them.

As for the noise that's added to the signal, I'm expecting most of it to be awgn, but there could be intersymbol interference as well, which would be noise at the same frequency as the signal itself.

Is there a way to determine the SNR with only this information? Here's an example. I can generate the two blocks of signals, called prevSig and currSig, and that's what might be transmitted.

enter image description here

Then when receiving the signal, some random noise will be added and those same signals can be seen as:

enter image description here

So if i only have the noisy received signals, what can I do to get the SNR from these. I believe I can determine the relative phase between any two blocks of signals by doing a point-wise addition or subtraction of the samples. if adding the samples together is smaller than subtracting the samples, then that would mean the blocks have a relative phase difference of 180, and if it's not then the blocks have the same phase, so from there I can easily get two blocks of signals that should be the same initial signal just with different random noises affecting them (I'm assuming the noise variance will be similar though)

Thanks for helping me think through this. Below is the code I used to generate these signals

from plotly.subplots import make_subplots
import plotly.graph_objs as go
import numpy as np

fs = 12000
t = 505/fs
f = 500
samples = np.arange(t * fs) / fs
signal = np.sin(2 * np.pi * f * samples)

prevSig = signal
currSig = signal*-1 # 180 degree phase shift
fig = make_subplots(rows=2, cols=1)
fig.add_trace(go.Scatter(y=list(prevSig), mode='lines+markers', name='prevSig'), row=1, col=1)
fig.add_trace(go.Scatter(y=list(currSig), mode='lines+markers', name='currSig'), row=2, col=1)
fig.update_layout(dict(title=dict(text='Signals before noise')))
fig.show()

#calculate power of signal before noise
sigPower = np.mean(signal**2)

targetSNR = 5

#Calculate noise
prevNoise = np.random.normal(0, sigPower/targetSNR, len(signal))
currNoise = np.random.normal(0, sigPower/targetSNR, len(signal))

fig = make_subplots(rows=2, cols=1)
fig.add_trace(go.Scatter(y=list(prevSig+prevNoise), mode='lines+markers', name='prevSigNoisy'), row=1, col=1)
fig.add_trace(go.Scatter(y=list(currSig+currNoise), mode='lines+markers', name='currSigNoisy'), row=2, col=1)
fig.update_layout(dict(title=dict(text='Signals with noise')))
fig.show()
1

There are 1 answers

0
jlandercy On

Let's take your signal:

import numpy as np
from scipy import fft

fs = 12000
t = 505/fs
f = 500
samples = np.arange(t * fs) / fs
signal = np.sin(2 * np.pi * f * samples)

And add some reproducible noise on it:

np.random.seed(12345)
noise = 0.25*np.random.normal(size=signal.size)

Then we can estimate SNR by estimating RMS of each component:

def rms(x):
    return np.sqrt(np.sum(x**2)/x.size)

SNR = (rms(signal)/rms(noise))**2  # 7.76816527364244

Now let's confirm it can be estimated using FFT by denoising your signal.

We take the FFT of the noise signal (and also compute frequencies for plotting sake):

dt = np.diff(samples)[0]  # 1/f
G = fft.fft(signal + noise)
freq = fft.fftfreq(G.size, dt)

Now we create a numerical filter (hat filter) that will only pick up or reject the base frequency:

q = np.abs(np.abs(freq) - f) <= 20.
F = np.zeros(G.size)
F[q] = 1.

We filter signals:

Gf = G*F
Nf = G*(1-F)

enter image description here

And estimate Power and then SNR:

def power(X):
    return np.sum(np.real(X*np.conj(X)))

SNR2 = power(Gf)/power(Nf)  # 7.600875978410159

Which is compliant our reference.

We can confirm our filtered signal is properly denoised by taking the inverse FFT:

signalf = np.real(fft.ifft(Gf))

enter image description here

Provided you have enough capacity to preform FFT, your signal is sufficiently clean and simple to estimate SNR from FFT.