Denoising with wavelets


Let us consider a finite discrete signal, represented by a vector \(\mathbb{C}^N\) with \(N\) a power of two, affected by Gaussian noise \(N(0,\sigma)\). If the signal is in some sense smooth, its Discrete Wavelet Transform (DWT) should show a quick decay and the noise reappears in the DWT side separating randomly the values from zero. Then a natural idea to denoise the signal is to choose a threshold \(\lambda>0\) and set to zero any coordinate of the detail vectors with absolute value less than \(\lambda\). This is in fact a general idea that also works with the DFT and other transforms. In the case of the DWT, after the work of Donoho and Johnstone, a good asymptotic choice of the threshold parameter is \[\lambda = \sigma\sqrt{2\log N}.\]

In many practical situations \(\sigma\) is unknown. Orthogonal maps transform Gaussian distributed vectors into vectors with the same distribution then we expect the coordinates of \(\vec{d}_1\) to be a sample of a distribution \(N(0,\sigma)\) except for some outliers corresponding to discontinuities. Due to the possible existence of these outliers we need a very robust estimator and the sample standard deviation is not. A much better option is the median absolute deviation defined as \[\text{MAD}\big(\{v_n\}_{n=0}^{N-1}\big) = \text{median} \big( |v_0-m|,\dots, |v_{N-1}-m| \big) \quad\text{with}\quad m= \text{median}(v_0,\dots, v_{N-1}).\] It can be proved that if \(\{v_n\}_{n=0}^{N-1}\) is a sample of a distribution \(N(0,\sigma)\) then one has the convergence in probability \[\text{MAD}\big(\{v_n\}_{n=0}^{N-1}\big) \to K_0\sigma \qquad\text{with}\quad K_0\approx 0.6745.\] Using this with \(\vec{v}=\vec{d}_1\), the choice of the threshold parameter becomes \[\lambda = \approx 2.0967\,\text{MAD}(\vec{d}_1)\sqrt{\log N}.\]

The octave code below applies this denoising method with the DWT corresponding to different Daubechies wavelets and produces the examples shown here. Note that it calls to a function in "The matrix of the DWT". The output is treated with this sagemath plotter to get the images.

The chosen signal (taken from my course on signal processing) is \(\vec{x}= \{x_n\}_{n=0}^{N-1}\) with \(N=1024\) given by the following formula where \(t=n/N\): \[x_n= \frac{15-\text{sgn}(t-1/2) }{16}\sin^2(\pi t)+\frac{1}{6}\cos(8\pi t).\] We consider it affected by Gaussian noise with \(\sigma=0.02\). These are the corresponding plots:

image image
Signal \(\vec{\mathsf{x}}\) Noisy signal \(\mathsf{\sigma = 0.02}\)

The jump in the discontinuity is \(1/8>2\sigma\) then the noise in principle should not mask completely the discontinuity although visually one tends to see the noisy image as coming from a continuous function.

For D2, the Haar wavelet, the graphs of the denoised signal and of the error are

image image

Of course the technique for D2 has only an academic interest.

For D4 the result is a little ’shaky’ due to the lack of smoothness of the wavelet. On the other hand the discontinuity has been detected.

image image

With D8 we get a better result and in general smaller errors except for points very close to the discontinuity which now visually is less obvious.

image image

Finally, with D12 the error near the discontinuity boosts and there is also a replica, coming from \(\vec{d}_2\), that creates a strange spot around \(1/4\).

image image

The code

This is octave code that produces the figures:


pkg load signal

clear all
%close all


filename = "data_plot.txt";
fid = fopen (filename, "w");


% Haar
fd2 = [1,-1]/sqrt(2);


% D4
fd4 = [ 1-sqrt(3), sqrt(3)-3, 3+sqrt(3), -1-sqrt(3)]/4/sqrt(2);

%(the bizarre ordering compensated by fliplr is to copy the data from wikipedia)
% D6
fd6 = [0.47046721, 1.14111692 , 0.650365, -0.19093442, -0.12083221, 0.0498175 ];
fd6 = -fliplr(fd6)/sqrt(2).*(-1).^[1:size(fd6,2)];

% D8
fd8 = [0.32580343 , 1.01094572 , 0.89220014, -0.03957503 , -0.26450717 , 0.0436163 , 0.0465036, -0.01498699];
fd8 = -fliplr(fd8)/sqrt(2).*(-1).^[1:size(fd8,2)];

% D12
fd12 = [0.15774243 , 0.69950381 ,1.06226376 , 0.44583132, -0.31998660, -0.18351806 , 0.13788809 , 0.03892321 , -0.04466375 , 7.83251152e-4, 6.75606236e-3, -1.52353381e-3 ];
fd12 = -fliplr(fd12)/sqrt(2).*(-1).^[1:size(fd12,2)];


N = 1024

% FIX RANDOM NUMBERS
randn ("seed", 0.5)


% SIGNAL
t = ( [0:N-1]/N )';
x = (15-sign(t-1/2))/16.*(sin(pi*t)).^2 + cos(8*pi*t)/6;

metadat = '>!_!_!_\nPJ0 SI6 FI./file_%s.png\n!_!_!_<\n';

% POINT SIGNAL x
fprintf (fid, '(%f, %f)\n', [t'; x']);
fprintf (fid, sprintf(metadat, '1') );


% NOISY SIGNAL xn1
sig1 = 0.02;
xn1 = x +randn(N,1)*sig1;
fprintf (fid, '(%f, %f)\n', [t'; xn1']);
fprintf (fid, sprintf(metadat, '2') );


% DENOISED SIGNAL AND ERROR D2
xden = noi_denoi(xn1, fd2, N);

fprintf (fid, '(%f, %f)\n', [t'; xden']);
fprintf (fid, sprintf(metadat, 'd2') );
fprintf (fid, '(%f, %f)\n', [t'; (x-xden)']);
fprintf (fid, sprintf(metadat, 'd2e') );


% DENOISED SIGNAL AND ERROR D4
xden = noi_denoi(xn1, fd4, N);

fprintf (fid, '(%f, %f)\n', [t'; xden']);
fprintf (fid, sprintf(metadat, 'd4') );
fprintf (fid, '(%f, %f)\n', [t'; (x-xden)']);
fprintf (fid, sprintf(metadat, 'd4e') );

% DENOISED SIGNAL AND ERROR D8
xden = noi_denoi(xn1, fd8, N);

fprintf (fid, '(%f, %f)\n', [t'; xden']);
fprintf (fid, sprintf(metadat, 'd8') );
fprintf (fid, '(%f, %f)\n', [t'; (x-xden)']);
fprintf (fid, sprintf(metadat, 'd8e') );

% DENOISED SIGNAL AND ERROR D12
xden = noi_denoi(xn1, fd12, N);

fprintf (fid, '(%f, %f)\n', [t'; xden']);
fprintf (fid, sprintf(metadat, 'd12') );
fprintf (fid, '(%f, %f)\n', [t'; (x-xden)']);
fprintf (fid, sprintf(metadat, 'd12e') );


fclose (fid);

It requires the functions:

noi_denoi

noi_sigma

% Denoise signal: xn = noisy signal
% xn = filter, N = dimension

function y = noi_denoi( xn, fd, N )
  W = wav_tr( fd, eye(N));
  u = W*xn;
  sig_n = noi_sigma( u );
  printf('Estimation sigma = %f\n',sig_n);
  lamb = sig_n*sqrt(2*log(N));
  % starting number of row giving wavelets
  st = floor(log(size(fd,2))/log(2) -1e-6 );
  st = 2^st+1;
  u(st:N) = u(st:N).*(abs(u(st:N))>=lamb);
  y = W'*u;
end

% Estimator of the variance: v = transformed signal

function sig = noi_sigma( v )
  N = size(v,1);
  v = v(N/2+1:N);
  v = v - median(v);
  sig = median( abs(v) )/0.67449;
end