Skip to content

Image Compression with SVD

Import the necessary libraries.

import numpy as np
import matplotlib.pyplot as plt
import scipy.linalg as la

We use this image of a raccoon for our example.

img = plt.imread('raccoon-1000x750.jpg')

We check the shape of the array representing the image.

img.shape
(750, 1000, 3)

Therefore the image is made up of \(750\times 1000\) pixels. Let's have a look at the image.

fig = plt.figure(figsize=(3,3))
plt.imshow(img)

Image Image

We'll work with the gray scale image for svd compression. To work with colour we would just to the same thing to each colour channel matrix, then recombine to create the final colour image.

img_gray = img.sum(2)/255**3
plt.figure(figsize=(3, 3))
plt.imshow(img_gray, cmap="gray")
Image Image

Compute the SVD of the image:

U, sigma, V = la.svd(img_gray)

Computing an approximation of the image using the first column of \(U\) and first row of \(V\) reproduces the most prominent feature of the image, the light area on top/left and the dark area on the bottom/right. The darkness of the fur around the eyes causes the extra darkness in the middle/right of the reconstruction. Each column of pixels in this image is a different weighting of the same values, \(\vec{u}_1\):

reconst_img = np.matrix(U[:, :1]) * np.diag(sigma[:1]) * np.matrix(V[:1, :])
fig = plt.figure(figsize=(3,3))
plt.imshow(reconst_img, cmap='gray');

Image Image

Even with just the first five vectors, the shape of the face begins to appear.

for i in range(2, 6):
    reconst_img = np.matrix(U[:, :i]) * np.diag(sigma[:i]) * np.matrix(V[:i, :])
    fig = plt.figure(figsize=(3,3))
    plt.imshow(reconst_img, cmap='gray')
    title = "n = %s" % i
    plt.title(title)
    plt.show()

Image Image

Image Image

Image Image

Image Image

Let's use a few more of the singular values to approximate the image.

for i in range(5, 51, 10):
    reconst_img = np.matrix(U[:, :i]) * np.diag(sigma[:i]) * np.matrix(V[:i, :])
    fig = plt.figure(figsize=(3,3))
    plt.imshow(reconst_img, cmap='gray')
    title = "n = %s" % i
    plt.title(title)
    plt.show()

Image Image

Image Image

Image Image

Image Image

Image Image

Using \(n=45\) of the singular values/vectors we have a really good reconstruction of the image.

In fact, looking at \(n=30\) this is a pretty good approximation.

n = 30
reconst_img = np.matrix(U[:, :n]) * np.diag(sigma[:n]) * np.matrix(V[:n, :])
fig = plt.figure(figsize=(3,3))
plt.imshow(reconst_img, cmap='gray')
title = "n = %s" % n
plt.title(title)
plt.show()

Image Image

The original image is \(750\times 1000\) pixels. Therefore, it takes \(7,500,000\) numbers to store it. The svd approximation using n singular values/vectors takes \(n(750+1000)+n = 1751n\) numbers to store it. So when \(n = 30\), it requires \(\frac{401\cdot 40}{400,000}= 7\%\) as much space as the original data.