#!/usr/bin/env python
# coding: utf-8
# Non Local Means
# ===============
#
# *Important:* Please read the [installation page](http://gpeyre.github.io/numerical-tours/installation_python/) for details about how to install the toolboxes.
# $\newcommand{\dotp}[2]{\langle #1, #2 \rangle}$
# $\newcommand{\enscond}[2]{\lbrace #1, #2 \rbrace}$
# $\newcommand{\pd}[2]{ \frac{ \partial #1}{\partial #2} }$
# $\newcommand{\umin}[1]{\underset{#1}{\min}\;}$
# $\newcommand{\umax}[1]{\underset{#1}{\max}\;}$
# $\newcommand{\umin}[1]{\underset{#1}{\min}\;}$
# $\newcommand{\uargmin}[1]{\underset{#1}{argmin}\;}$
# $\newcommand{\norm}[1]{\|#1\|}$
# $\newcommand{\abs}[1]{\left|#1\right|}$
# $\newcommand{\choice}[1]{ \left\{ \begin{array}{l} #1 \end{array} \right. }$
# $\newcommand{\pa}[1]{\left(#1\right)}$
# $\newcommand{\diag}[1]{{diag}\left( #1 \right)}$
# $\newcommand{\qandq}{\quad\text{and}\quad}$
# $\newcommand{\qwhereq}{\quad\text{where}\quad}$
# $\newcommand{\qifq}{ \quad \text{if} \quad }$
# $\newcommand{\qarrq}{ \quad \Longrightarrow \quad }$
# $\newcommand{\ZZ}{\mathbb{Z}}$
# $\newcommand{\CC}{\mathbb{C}}$
# $\newcommand{\RR}{\mathbb{R}}$
# $\newcommand{\EE}{\mathbb{E}}$
# $\newcommand{\Zz}{\mathcal{Z}}$
# $\newcommand{\Ww}{\mathcal{W}}$
# $\newcommand{\Vv}{\mathcal{V}}$
# $\newcommand{\Nn}{\mathcal{N}}$
# $\newcommand{\NN}{\mathcal{N}}$
# $\newcommand{\Hh}{\mathcal{H}}$
# $\newcommand{\Bb}{\mathcal{B}}$
# $\newcommand{\Ee}{\mathcal{E}}$
# $\newcommand{\Cc}{\mathcal{C}}$
# $\newcommand{\Gg}{\mathcal{G}}$
# $\newcommand{\Ss}{\mathcal{S}}$
# $\newcommand{\Pp}{\mathcal{P}}$
# $\newcommand{\Ff}{\mathcal{F}}$
# $\newcommand{\Xx}{\mathcal{X}}$
# $\newcommand{\Mm}{\mathcal{M}}$
# $\newcommand{\Ii}{\mathcal{I}}$
# $\newcommand{\Dd}{\mathcal{D}}$
# $\newcommand{\Ll}{\mathcal{L}}$
# $\newcommand{\Tt}{\mathcal{T}}$
# $\newcommand{\si}{\sigma}$
# $\newcommand{\al}{\alpha}$
# $\newcommand{\la}{\lambda}$
# $\newcommand{\ga}{\gamma}$
# $\newcommand{\Ga}{\Gamma}$
# $\newcommand{\La}{\Lambda}$
# $\newcommand{\si}{\sigma}$
# $\newcommand{\Si}{\Sigma}$
# $\newcommand{\be}{\beta}$
# $\newcommand{\de}{\delta}$
# $\newcommand{\De}{\Delta}$
# $\newcommand{\phi}{\varphi}$
# $\newcommand{\th}{\theta}$
# $\newcommand{\om}{\omega}$
# $\newcommand{\Om}{\Omega}$
# This numerical tour study image denoising using
# non-local means. This algorithm has been
# introduced for denoising purposes in [BuaCoMoA05](#biblio)
# In[1]:
from __future__ import division
import numpy as np
import scipy as scp
import pylab as pyl
import matplotlib.pyplot as plt
from nt_toolbox.general import *
from nt_toolbox.signal import *
get_ipython().run_line_magic('matplotlib', 'inline')
get_ipython().run_line_magic('load_ext', 'autoreload')
get_ipython().run_line_magic('autoreload', '2')
# Patches in Images
# -----------------
# This numerical tour is dedicated to the study of the structure of patches
# in images.
#
#
# Size $N = n \times n$ of the image.
# In[2]:
n = 128
# We load a noisy image $f_0\in \RR^N$.
# In[3]:
c = [100,200]
f0 = load_image("nt_toolbox/data/lena.bmp")
f0 = rescale(f0[c[0]-n//2:c[0]+n//2, c[1]-n//2:c[1]+n//2])
# Display $f_0$.
# In[4]:
plt.figure(figsize = (5,5))
imageplot(f0)
# Noise level $\si$.
# In[5]:
sigma = .04
# Generate a noisy image $f=f_0+\epsilon$ where $\epsilon \times
# \Nn(0,\si^2\text{Id}_N)$.
# In[6]:
from numpy import random
f = f0 + sigma*random.standard_normal((n,n))
# Display $f$.
# In[7]:
plt.figure(figsize = (5,5))
imageplot(clamp(f))
# We denote $w$ to be the half width of the patches,
# and $w_1=2w+1$ the full width.
# In[8]:
w = 3
w1 = 2*w + 1
# We set up large $(n,n,w_1,w_1)$ matrices to index the the X and Y
# position of the pixel to extract.
#
# Location of pixels to extract.
# In[9]:
[X,Y,dX,dY] = np.meshgrid(np.arange(1,n+1),np.arange(1,n+1),np.arange(-w,w+1),np.arange(-w,w+1))
X = X + dX
Y = Y + dY
# We handle boundary condition by reflexion
# In[10]:
X[X < 1] = 2-X[X < 1]
Y[Y < 1] = 2-Y[Y < 1]
X[X > n] = 2*n-X[X > n]
Y[Y > n] = 2*n-Y[Y > n]
# Patch extractor operator.
# In[11]:
I = (X-1) + (Y-1)*n
for i in range(n//w):
for j in range(n//w):
I[i,j] = np.transpose(I[i,j])
patch = lambda f: np.ravel(f)[I]
# Define the patch matrix $P$ of size $(n,n,w_1,w_1)$.
# Each $P(i,j,:,:)$ represent an $(w_1,w_1)$ patch extracted around pixel
# $(i,j)$ in the image.
# In[12]:
P = patch(f)
# Display some example of patches.
# In[13]:
from numpy import random
plt.figure(figsize = (5,5))
for i in range(16):
x = random.randint(n)
y = random.randint(n)
imageplot(P[x, y], '', [4, 4, i+1])
# Dimensionality Reduction with PCA
# ---------------------------------
# Since NL-means type algorithms require the computation of many distances
# between patches, it is advantagous to reduce the dimensionality of the
# patch while keeping as much as possible of information.
#
#
# Target dimensionality $d$.
# In[14]:
d = 25
# A linear dimensionality reduction is obtained by Principal Component
# Analysis (PCA) that projects the data on a small number of leading
# direction of the covariance matrix of the patches.
#
#
# Turn the patch matrix into an $(w_1*w_1,n*n)$ array, so that each $P(:,i)$
# is a $w_1*w_1$ vector representing a patch.
# In[15]:
resh = lambda P: np.transpose((np.reshape(P, (n*n,w1*w1), order="F")))
# Operator to remove the mean of the patches to each patch.
# In[16]:
remove_mean = lambda Q: Q - np.tile(np.mean(Q,0),(w1*w1,1))
# Compute the mean and the covariance of the points cloud representing the
# patches.
# In[17]:
P1 = remove_mean(resh(P))
C = np.dot(P1,np.transpose(P1))
# Extract the eigenvectors, sorted by decreasing amplitude.
# In[18]:
from numpy import linalg
[D,V] = linalg.eig(C)
D = np.sort(D)[::-1]
I = np.argsort(D)[::-1]
V = V[I,:]
# Display the decaying amplitude of the eigenvalues.
# In[19]:
plt.plot(D, linewidth = 2)
plt.ylim(0,max(D))
plt.show()
# Display the leading eigenvectors - they look like Fourier modes.
# In[20]:
plt.figure(figsize = (5,5))
for i in range(16):
imageplot(abs(np.reshape(V[:,i], (w1,w1))), '', [4, 4, i+1])
# Patch dimensionality reduction operator.
# In[21]:
iresh = lambda Q: np.reshape(np.transpose(Q),(n,n,d),order="F")
descriptor = lambda f: iresh(np.dot(np.transpose(V[: ,:d]),remove_mean(resh(P))))
# Each $H(i,j,:)$ is a $d$-dimensional descriptor
# of a patch.
# In[22]:
H = descriptor(f)
# Non-local Filter
# ----------------
# NL-means applies, an adaptive averaging kernel
# is computed from patch distances to each pixel location.
#
#
# We denote $H_{i} \in \RR^d$ the descriptor at pixel $i$.
# We define the distance matrix
# $$ D_{i,j} = \frac{1}{w_1^2}\norm{H_i-H_j}^2. $$
#
#
#
# Operator to compute the distances $(D_{i,j})_j$ between the patch around $i=(i_1,i_2)$
# and all the other ones.
# In[23]:
distance = lambda i: np.sum((H - np.tile(H[i[0],i[1],:], (n,n,1)))**2, 2)/(w1*w1)
# The non-local mean filter computes a denoised image $\tilde f$ as :
#
# $$ \tilde f_i = \sum_j K_{i,j} f_j $$
# where the weights $K$ are computed as :
# $$ K_{i,j} = \frac{ \tilde K_{i,j} }{ \sum_{j'} \tilde K_{i,j'} }
# \qandq
# \tilde K_{i,j} = e^{-\frac{D_{i,j}}{2\tau^2}} . $$
#
#
#
# The width $\tau$ of the Gaussian is very important and should be adapted to match
# the noise level.
#
#
#
# Compute and normalize the weight.
# In[24]:
normalize = lambda K: K/np.sum(K)
kernel = lambda i,tau: normalize(np.exp(-distance(i)/(2*tau**2)))
# Compute a typical example of kernel for some pixel position $(x,y)$.
# In[25]:
tau = .05
i = [83,72]
D = distance(i)
K = kernel(i, tau)
# Display the squared distance and the kernel.
# In[26]:
plt.figure(figsize = (10,10))
imageplot(D, 'D', [1, 2, 1])
imageplot(K, 'K', [1, 2, 2])
# Localizing the Non-local Means
# ------------------------------
# We set a "locality constant" $q$ that set the maximum distance between
# patches to compare. This allows to speed up computation, and makes
# NL-means type methods semi-global (to avoid searching in all the image).
# In[27]:
q = 14
# Using this locality constant, we compute the distance between patches
# only within a window.
# Once again, one should be careful about boundary conditions.
# In[28]:
selection = lambda i: np.array((clamp(np.arange(i[0]-q,i[0] + q + 1), 0, n-1), clamp(np.arange(i[1]-q,i[1] + q + 1), 0, n-1)))
# Compute distance and kernel only within the window.
# In[29]:
def distance_0(i,sel):
H1 = (H[sel[0],:,:])
H2 = (H1[:,sel[1],:])
return np.sum((H2 - np.tile(H[i[0],i[1],:],(len(sel[0]),len(sel[1]),1)))**2,2)/w1*w1
distance = lambda i: distance_0(i, selection(i))
kernel = lambda i, tau: normalize(np.exp(-distance(i)/ (2*tau**2)))
# Compute a typical example of kernel for some pixel position $(x,y)$.
# In[30]:
D = distance(i)
K = kernel(i, tau)
# Display the squared distance and the kernel.
# In[31]:
plt.figure(figsize = (10,10))
imageplot(D, 'D', [1, 2, 1])
imageplot(K, 'K', [1, 2, 2])
# The NL-filtered value at pixel $(x,y)$ is obtained by averaging the values
# of $f$ with the weight $K$.
# In[32]:
def NLval_0(K,sel):
f_temp = f[sel[0],:]
return np.sum(K*f_temp[:, sel[1]])
NLval = lambda i, tau: NLval_0(kernel(i, tau), selection(i))
# We apply the filter to each pixel location
# to perform the NL-means algorithm.
# In[33]:
[Y, X] = np.meshgrid(np.arange(0,n),np.arange(0,n))
def arrayfun(f,X,Y):
n = len(X)
p = len(Y)
R = np.zeros([n,p])
for k in range(n):
for l in range(p):
R[k,l] = f(k,l)
return R
NLmeans = lambda tau: arrayfun(lambda i1, i2: NLval([i1,i2], tau), X, Y)
# Display the result for some value of $\tau$.
# In[34]:
tau = .03
plt.figure(figsize = (5,5))
imageplot(NLmeans(tau))
# __Exercise 1__
#
# Compute the denoising result for several values of $\tau$ in order to
# determine the optimal denoising that minimizes $\norm{\tilde f - f_0}$.
# In[35]:
run -i nt_solutions/denoisingadv_6_nl_means/exo1
# In[36]:
## Insert your code here.
# Display the best result.
# In[37]:
plt.figure(figsize = (5,5))
imageplot(clamp(fNL))
# __Exercise 2__
#
# Explore the influence of the $q$ and $w$ parameters.
# In[38]:
run -i nt_solutions/denoisingadv_6_nl_means/exo2
# In[39]:
## Insert your code here.
# Bibliography
# ------------
#
#
#
# * [BuaCoMoA05] Buades, B. Coll, J.f Morel, [A review of image denoising algorithms, with a new one][1], SIAM Multiscale Modeling and Simulation, Vol 4 (2), pp: 490-530, 2005.
#
# [1]:http://dx.doi.org/10.1137/040616024