Dynamic Image Compression using SVD¶
Implementing an Image Compression Algorithm that uses Singular Value Decomposition (SVD) where the program dynamically determines the minimum number of singular values (k) needed to reconstruct the image within a specified tolerance.
This notebook demonstrates an image compression technique using Singular Value Decomposition (SVD). The core idea is to decompose an image into its constituent singular values and then reconstruct a compressed version using only the most significant singular values.
Benefits:
- Dynamic Compression: The algorithm dynamically determines the optimal number of singular values (k) required to achieve a desired image quality (in this case, a PSNR greater than 30 dB).
- Reduced Storage: By using a smaller number of singular values, the compressed image requires significantly less storage space compared to the original.
- Quantifiable Quality: The use of PSNR provides a objective metric to evaluate the quality of the compressed image.
Algorithm:
- Load and Convert Image: The notebook loads an image and converts it to grayscale.
- Perform SVD: Singular Value Decomposition is applied to the grayscale image matrix, resulting in three matrices: U, S (singular values), and Vt.
- Determine Optimal k: The notebook iterates through different values of k (number of singular values) and calculates the PSNR for each reconstructed image. The minimum k that achieves a PSNR greater than the specified threshold (30 dB) is selected.
- Reconstruct Image: The compressed image is reconstructed using the truncated U, S, and Vt matrices based on the optimal k.
- Evaluate Compression: The compression ratio, final PSNR, and RMSE are calculated to assess the effectiveness of the compression.
- Display Images: The original and compressed images are displayed for visual comparison.
Imports¶
!pip install numpy pillow matplotlib
Requirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (2.0.2) Requirement already satisfied: pillow in /usr/local/lib/python3.11/dist-packages (11.3.0) Requirement already satisfied: matplotlib in /usr/local/lib/python3.11/dist-packages (3.10.0) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (1.3.3) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (4.59.0) Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (1.4.9) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (25.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (3.2.3) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.11/dist-packages (from matplotlib) (2.9.0.post0) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
Load Image¶
Downloading Image¶
!wget https://news.iitgn.ac.in/wp/wp-content/uploads/2019/07/ANK383_2224a-1280x640.jpg -O sample_image.jpg
--2025-08-16 05:37:11-- https://news.iitgn.ac.in/wp/wp-content/uploads/2019/07/ANK383_2224a-1280x640.jpg Resolving news.iitgn.ac.in (news.iitgn.ac.in)... 4.213.52.179 Connecting to news.iitgn.ac.in (news.iitgn.ac.in)|4.213.52.179|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 248999 (243K) [image/jpeg] Saving to: ‘sample_image.jpg’ sample_image.jpg 100%[===================>] 243.16K 285KB/s in 0.9s 2025-08-16 05:37:12 (285 KB/s) - ‘sample_image.jpg’ saved [248999/248999]
Loading Image using Numpy and Converting to Grayscale
img = Image.open("sample_image.jpg").convert("L")
img_arr = np.array(img)
arr = img_arr.astype(np.float64)
display(arr.shape)
(640, 1280)
SVD Decomposition and Calculate Min. K¶
Directly did Decomposition using the numpy library. Calculated the minimum number of singular values required for PSNR to be greater than 30 dB by iterating over values of different k.
m, n = arr.shape
U, S, Vt = np.linalg.svd(arr, full_matrices=False)
display(U.shape, S.shape, Vt.shape)
(640, 640)
(640,)
(640, 1280)
S2 = S ** 2
total_energy = np.sum(S2)
tail_energy = np.concatenate(([total_energy], np.cumsum(S2[::-1])[:-1][::-1]))
r = len(S2)
best_k = r
for k in range(1, r + 1):
residual_energy = np.sum(S2[k:])
mse = residual_energy / (m * n)
if (mse == 0):
best_k = k
break
current_psnr = 10 * np.log10(255 ** 2 / mse)
if current_psnr > 30:
best_k = k
break
print(f"Min. K for PSNR > 30: {best_k}")
Min. K for PSNR > 30: 156
Image Reconstruction¶
Uk = U[:, :best_k]
Sk = np.diag(S[:best_k])
Vtk = Vt[:best_k, :]
res = Uk @ Sk @ Vtk
res = np.clip(res, 0, 255).astype(np.uint8)
Data Compression Ratio¶
original_size = m * n
compressed_size = best_k * (m + n + 1) # U(m * k) + S(1 * k) + Vt(k * n)
compression_ratio = compressed_size / original_size
print(f"Compression Ratio: {compression_ratio}")
Compression Ratio: 0.3658154296875
Final PSNR¶
dif = arr - res
mse = np.mean(dif ** 2)
psnr = 10 * np.log10(255 ** 2 / mse)
print(f"Final PSNR: {psnr}")
Final PSNR: 30.000799468906703
Root Mean Square Error¶
rmse = np.sqrt(mse)
print(f"Final RMSE: {rmse}")
Final RMSE: 8.0630658564754
Image Displays¶
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(arr, cmap='gray')
axes[0].set_title("Original Image")
axes[0].axis('off')
axes[1].imshow(res, cmap='gray')
axes[1].set_title(f"Compressed Image (k={best_k})")
axes[1].axis('off')
plt.tight_layout()
plt.show()