Deblurring Images#

import requests
from io import BytesIO
from PIL import Image
import numpy as np
import scipy.linalg as la
import matplotlib.pyplot as plt
from scipy.io import loadmat
plt.set_cmap('binary')
<Figure size 640x480 with 0 Axes>

Blurring images by Toeplitz matrices#

Represent a image as a matrix \(X\). Use the function scipy.linalg.toeplitz to create a Toeplitz matrices \(A_c\) and \(A_r\). Matrix multiplication on the left \(A_c X\) blurs vertically (in the columns) and on the right \(X A_r\) blurs horizontally (in the rows).

Let us create a \(N \times N\) matrix of zeros and ones such that represents the image of square.

N = 256
Z = np.zeros((N//4,N//4))
O = np.ones((N//4,N//4))
X = np.block([[Z,Z,Z,Z],[Z,O,O,Z],[Z,O,O,Z],[Z,Z,Z,Z]])
plt.imshow(X)
plt.show()
../_images/d712527e02bbd2c10aee89ae7562ece8daaad26c6062d4a6f3d53ab8ea9afc37.png

Create a Toeplitz matrix where the values decrease linearly from the diagonal.

c = np.zeros(N)
s = 5
c[:s] = (s - np.arange(0,s))/(3*s)
Ac = la.toeplitz(c)
plt.imshow(Ac[:15,:15])
plt.colorbar()
plt.show()
../_images/5a004376242d5f61b5dea48f11f563b81e3f1ced2dfe297cad4358baa9a8c98a.png

Check the condition number of \(A_c\).

np.linalg.cond(Ac)
np.float64(24782.331042559137)

Blur the image \(X\) vertically.

plt.imshow(Ac @ X)
plt.show()
../_images/e290c8afdd249edd7b0552f66a33dbcf00c2e3204ebc21aa0c209e1efc4613dc.png

Do the same but in the horizontal direction.

r = np.zeros(N)
s = 20
r[:s] = (s - np.arange(0,s))/(3*s)
Ar = la.toeplitz(r)
plt.imshow(X @ Ar.T)
plt.show()
../_images/6b3f752febd351a08ec97aa844c73b605fa53a42b3f3134162bb434cf258b154.png

Combine both vertical and horizontal blurring.

plt.imshow(Ac @ X @ Ar.T)
plt.show()
../_images/ee3a0ef511875be4feb3407c7605259c10253afa1158df040cdcbf60fe21e9b5.png

Inverting the noise#

Let \(E\) represent some noise in the recording of the image

\[ A_c X A_r^T = B + E \]

How do we find \(X\)?

url = 'https://raw.githubusercontent.com/adrs0049/MATH545/master/notebooks/data/kitten.jpg'
page = requests.get(url)
kitten = np.asarray(Image.open(BytesIO(page.content))).astype(np.float64)
kitten.shape
(256, 256)
plt.imshow(kitten,cmap='gray')
plt.show()
../_images/1cee9b1853edac5205761108e129c417d90e550dc8eedd6f06b9bc9c9ecff4f6.png
B = Ac@kitten@Ar.T + 0.01*np.random.randn(256,256)
plt.imshow(B,cmap='gray')
plt.show()
../_images/3555b463948fd4ef4f5e140f3b1ddfa2519663a38bd59cad966600c1126be8b2.png
X1 = la.solve(Ac,B)
X2 = la.solve(Ar,X1.T)
X2 = X2.T
plt.imshow(X2,cmap='gray')
plt.show()
../_images/02798c40d17b11b8d10183ac06c6e5f7dfc187dfda38075d1cdd12d6b7fd0f94.png

Truncated SVD#

We need to avoid inverting the noise therefore we compute using the truncated pseudoinverse

\[ X = (A_c)_k^+ B (A_r^T)_k^+ \]
Pc,Sc,QTc = la.svd(Ac)
Pr,Sr,QTr = la.svd(Ar)
k = 200
Dc_k_plus = np.hstack([1/Sc[:k],np.zeros(N-k)])
Dr_k_plus = np.hstack([1/Sr[:k],np.zeros(N-k)])
Ac_k_plus = QTc.T @ np.diag(Dc_k_plus) @ Pc.T
Ar_k_plus = Pr @ np.diag(Dr_k_plus) @ QTr
X = Ac_k_plus @ B @ Ar_k_plus
plt.imshow(X,cmap='gray')
plt.show()
../_images/44c6463f524de2fb0fff5c6837cc761222c9005089f1e5696661e6c13ca65d17.png