A quick guide to color image compression using PCA in python

Step by step explanation on how to use PCA for Dimensionality Reduction on a colored image using python

Iqbal Hussain
Towards Data Science

--

Photo by Joshua Woroniecki on Unsplash

If you are a Data Science or Machine Learning enthusiast, you must have come across PCA (Principal Component Analysis) which is a popular unsupervised machine learning algorithm primarily used for dimensionality reduction of large dataset. We can use PCA for dimensionality reduction for images as well.

A simple use case

Let’s think of a case where you are working on an AI-ML project which deals with images. Normally images have a lot of pixels to retain their clarity, but that significantly increases its size and slows down the performance of the system when it has to process multiple images. To overcome this situation we can use the dimensionality reduction technique which comes under Unsupervised Machine Learning. Why don’t we check if PCA comes in handy in this case or not? We will use one picture in this article and reduce its dimensions or in other words compress the image using PCA in python.

At the end of the article, we will compare the resulted picture with the original picture to validate our effort.

I had explained the mathematics behind Principal Component Analysis (PCA) in my earlier article. If you have not gone through it yet, you can click here to go through the same.

Now let’s get started!

We will first split the image into the three channels (Blue, Green, and Red) first and then and perform PCA separately on each dataset representing each channel and will then merge them to reconstruct the compressed image. Hence, if our colored image is of shape (m, n, 3), where (m X n) is the total number of pixels of the image on the three channels (b, g, r).

We can also perform the same thing without splitting into blue, green, and red channels and reshaping the data into (m, n X 3) pixels, but we have found that the explained variance ratio given by the same number of PCA component is better if we use the splitting method as mentioned in the earlier paragraph.

I will use the following photograph for the demonstration.

Photo by author

Load and pre-process the image

Let’s import the libraries first:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import cv2
from scipy.stats import stats
import matplotlib.image as mpimg

Now let’s read the image rose.jpg and display it.

img = cv2.cvtColor(cv2.imread('rose.jpg'), cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()

Output:

Check the shape using the following line:

img.shape

Output:

(485, 485, 3)

Now, I will split the image into 3 channels and display each image:

#Splitting into channels
blue,green,red = cv2.split(img)
# Plotting the images
fig = plt.figure(figsize = (15, 7.2))
fig.add_subplot(131)
plt.title("Blue Channel")
plt.imshow(blue)
fig.add_subplot(132)
plt.title("Green Channel")
plt.imshow(green)
fig.add_subplot(133)
plt.title("Red Channel")
plt.imshow(red)
plt.show()

Output:

Let’s verify the data of the blue channel:

blue_temp_df = pd.DataFrame(data = blue)
blue_temp_df

Output:

I will divide all the data of all channels by 255 so that the data is scaled between 0 and 1.

df_blue = blue/255
df_green = green/255
df_red = red/255

Fit and transform the data in PCA

We already have seen that each channel has 485 dimensions, and we will now consider only 50 dimensions for PCA and fit and transform the data and check how much variance is explained after reducing data to 50 dimensions.

pca_b = PCA(n_components=50)
pca_b.fit(df_blue)
trans_pca_b = pca_b.transform(df_blue)
pca_g = PCA(n_components=50)
pca_g.fit(df_green)
trans_pca_g = pca_g.transform(df_green)
pca_r = PCA(n_components=50)
pca_r.fit(df_red)
trans_pca_r = pca_r.transform(df_red)

We have fitted the data in PCA, let’s check the shape of the transformed image of each channel:

print(trans_pca_b.shape)
print(trans_pca_r.shape)
print(trans_pca_g.shape)

Output:

(485, 50)
(485, 50)
(485, 50)

That is as expected. Let’s check the sum of explained variance ratios of the 50 PCA components (i.e. most dominated 50 Eigenvalues) for each channel.

print(f"Blue Channel : {sum(pca_b.explained_variance_ratio_)}")
print(f"Green Channel: {sum(pca_g.explained_variance_ratio_)}")
print(f"Red Channel : {sum(pca_r.explained_variance_ratio_)}")

Output:

Blue Channel : 0.9946260772755372
Green Channel: 0.9918219615668648
Red Channel : 0.987736292777275

Wow, that’s superb! because only using 50 components we can keep around 99% of the variance in the data.

Let's plot bar charts to check the explained variance ratio by each Eigenvalues separately for each of the 3 channels:

fig = plt.figure(figsize = (15, 7.2)) 
fig.add_subplot(131)
plt.title("Blue Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_b.explained_variance_ratio_)
fig.add_subplot(132)
plt.title("Green Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_g.explained_variance_ratio_)
fig.add_subplot(133)
plt.title("Red Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_r.explained_variance_ratio_)
plt.show()

Output:

Reconstruct the image and visualize

We have completed our PCA dimensionality reduction. Now we will visualize the image again and for that, we have to reverse transform the data first and then merge the data of all the 3 channels into one. Let’s proceed.

b_arr = pca_b.inverse_transform(trans_pca_b)
g_arr = pca_g.inverse_transform(trans_pca_g)
r_arr = pca_r.inverse_transform(trans_pca_r)
print(b_arr.shape, g_arr.shape, r_arr.shape)

Output:

(485, 485) (485, 485) (485, 485)

We can inverse transform the data to the original shape (although each channel is still separated), but as we know all the images are already compressed.

We will merge all the channels into one and print the final shape:

img_reduced= (cv2.merge((b_arr, g_arr, r_arr)))
print(img_reduced.shape)

Output:

(485, 485, 3)

That’s great to see the exact shape of the original image that we had imported at the very beginning. Now we will display both the Images (original and reduced) side by side.

fig = plt.figure(figsize = (10, 7.2)) 
fig.add_subplot(121)
plt.title("Original Image")
plt.imshow(img)
fig.add_subplot(122)
plt.title("Reduced Image")
plt.imshow(img_reduced)
plt.show()

Output:

It's amazing to see that the compressed image is very similar (at least we can still identify it as a rose) to that of the original one although we have reduced the dimension individually for each channel to only 50 from 485. But, we have achieved our goal. No doubt that now the reduced image will be processed much faster by the computer.

Conclusion

I have explained how we can use PCA to reduce the dimension of a color image by splitting it into 3 channels and then reconstruct it back for visualization.

I hope you have enjoyed reading and learning from the article.

You can download the complete code and also the image I shown here which is on the same directory from my github link mentioned below:

--

--