当前位置:   article > 正文

使用奇异值分解(SVD)压缩图片(python实现)_基于奇异值分解svd实现图像压缩py代码

基于奇异值分解svd实现图像压缩py代码

奇异值分解压缩图片原理

图片在计算机内以矩阵形式存储,因此可以使用矩阵论知识和算法对数字图像(图片)进行分析与处理。

原理简述

彩色图片有3个图层,分别显示3个通道(红、绿、蓝),彩色图片就是3个图层混合才能表示不同的色彩。对3个图层分别进行奇异值分解,可以选取前k个奇异值进行近似表达(每个矩阵的奇异值是唯一的,矩阵大小跟奇异值数量有关),最后合并3个图层就可以以较少的数据表示原图片(也就是使用多于原图片奇异值数量的奇异值可能会使图片更大)。

SVD

代码实现

  1. import numpy as np
  2. from PIL import Image
  3. import matplotlib.pyplot as plt
  4. import os
  5. # 加载原始彩色图像
  6. original_image = Image.open("flower.jpg")
  7. # 将图像转换为NumPy数组
  8. image_array = np.array(original_image)
  9. print(f"原图像文件大小{image_array.size} bits\n")
  10. # 对每个颜色通道分别进行SVD压缩
  11. def compress_svd(image, k):
  12. U, S, V = np.linalg.svd(image, full_matrices=False) #分解原图像
  13. #对原图像的矩阵进行压缩
  14. compressed_U = U[:, :k]
  15. compressed_S = np.diag(S[:k])
  16. compressed_V = V[:k, :]
  17. compressed_image = np.dot(compressed_U, np.dot(compressed_S, compressed_V)) #对矩阵进行乘法运算,恢复压缩图像
  18. return compressed_image, compressed_U.size, compressed_S.size, compressed_V.size
  19. # 设置奇异值列表(不同的压缩级别)
  20. singular_values_list = [1, 50, 100, 200, 500, 1000, 1500, 2000] #奇异值大小跟图像大小(分辨率)有关,越大的分辨率可以使用更大的奇异值
  21. # 初始化一个列表以存储压缩后的图像
  22. compressed_images = []
  23. # 对每个奇异值进行压缩
  24. for k in singular_values_list:
  25. compressed_image = np.empty_like(image_array, dtype=np.uint8) #创建一个形状与原图形同的空矩阵,用于存放压缩后的矩阵乘积
  26. for channel in range(3):
  27. compressed_channel, U_size, S_size, V_size = compress_svd(image_array[:, :, channel], k)
  28. compressed_image[:, :, channel] = compressed_channel
  29. compressed_images.append(compressed_image)
  30. # 将压缩图像转换为Pillow图像对象并保存
  31. for k, compressed_image in zip(singular_values_list, compressed_images):
  32. compressed_image = Image.fromarray(compressed_image)
  33. compressed_image.save(f"compressed_image_{k}.jpg")
  34. # 计算并打印原图像和压缩图像的文件大小
  35. original_file_size = os.path.getsize("original_image.jpg") * 8 #根据图像像素空间大小计算压缩率
  36. compressed_file_sizes = [os.path.getsize(f"compressed_image_{k}.jpg") * 8 for k in singular_values_list]
  37. #计算图片压缩率
  38. for k, compressed_size in zip(singular_values_list, compressed_file_sizes):
  39. rate = round((original_file_size - compressed_size) / original_file_size, 4) * 100
  40. compressed_image, U_size, S_size, V_size = compress_svd(image_array[:, :, channel], k)
  41. #图像传输过程中不是直接传输图像,而是传输图像的矩阵,SVD对图像矩阵进行拆分
  42. transmit_rate = round((image_array.size - 3 * (U_size + S_size + V_size))/image_array.size, 4) * 100
  43. print(f"压缩图像 (k={k}) 文件大小: {compressed_size} bits")
  44. print(f"图像大小压缩率 (k={k}): {rate}%")
  45. print(f"传输压缩率(k={k}):{transmit_rate}%")
  46. # 显示原始图像和8个不同压缩级别的图像
  47. plt.rcParams["font.sans-serif"] = "SimHei" # 解决中文显示问题
  48. plt.figure()#自适应
  49. # plt.figure(figsize=(10, 8))
  50. # 显示原始图像
  51. plt.subplot(3, 3, 1)
  52. plt.title("原始图像")
  53. plt.imshow(original_image)
  54. plt.axis("off")
  55. # 显示压缩图像
  56. for i, (k, compressed_image) in enumerate(zip(singular_values_list, compressed_images)):
  57. plt.subplot(3, 3, i + 2)
  58. plt.title(f"压缩图像 (k={k})")
  59. plt.imshow(compressed_image)
  60. plt.axis("off")
  61. plt.tight_layout()
  62. plt.show()

可以看到不同的奇异值对图片的压缩有不同的效果,分辨率越低的图片的奇异值数量越低,过高的奇异值会使图片的大小更大。

上面的代码显示的图片就是SVD对图片压缩的影响,我们可以看到低奇异值压缩得到的图片会有一些花斑,这些花斑是因为图片的像素亮度差较大造成的,如果我们对图像像素的亮度进行归一化,我们就会得到不错的显示效果。下面是加入归一化的代码。

  1. import numpy as np
  2. from PIL import Image
  3. import matplotlib.pyplot as plt
  4. import os
  5. # 加载原始彩色图像
  6. original_image = Image.open("flower.jpg")
  7. # 将图像转换为NumPy数组
  8. image_array = np.array(original_image)
  9. print(f"原图像文件大小{image_array.size} bits\n")
  10. # 对每个颜色通道分别进行SVD压缩
  11. def compress_svd(image, k):
  12. U, S, V = np.linalg.svd(image, full_matrices=False) #分解原图像
  13. #对原图像的矩阵进行压缩
  14. compressed_U = U[:, :k]
  15. compressed_S = np.diag(S[:k])
  16. compressed_V = V[:k, :]
  17. compressed_image = np.dot(compressed_U, np.dot(compressed_S, compressed_V)) #对矩阵进行乘法运算,恢复压缩图像
  18. normalized_image = normalize_image(compressed_image) # 对压缩后的图像进行归一化,使用归一化的图像在亮度上会比较均衡,显示效果好
  19. return normalized_image, compressed_U.size, compressed_S.size, compressed_V.size
  20. #对压缩后的图像进行归一化
  21. def normalize_image(image):
  22. # 将图像矩阵的像素值缩放到0到255的范围内
  23. min_pixel = np.min(image)
  24. max_pixel = np.max(image)
  25. normalized_image = 255 * (image - min_pixel) / (max_pixel - min_pixel)
  26. # 将浮点数像素值转换为无符号整数(uint8)
  27. normalized_image = normalized_image.astype(np.uint8)
  28. return normalized_image
  29. # 设置奇异值列表(不同的压缩级别)
  30. singular_values_list = [1, 50, 100, 200, 500, 1000, 1500, 2000] #奇异值大小跟图像大小(分辨率)有关,越大的分辨率可以使用更大的奇异值
  31. # 初始化一个列表以存储压缩后的图像
  32. compressed_images = []
  33. # 对每个奇异值进行压缩
  34. for k in singular_values_list:
  35. compressed_image = np.empty_like(image_array, dtype=np.uint8) #创建一个形状与原图形同的空矩阵,用于存放压缩后的矩阵乘积
  36. for channel in range(3):
  37. compressed_channel, U_size, S_size, V_size = compress_svd(image_array[:, :, channel], k)
  38. compressed_image[:, :, channel] = compressed_channel
  39. compressed_images.append(compressed_image)
  40. # 将压缩图像转换为Pillow图像对象并保存
  41. for k, compressed_image in zip(singular_values_list, compressed_images):
  42. compressed_image = Image.fromarray(compressed_image)
  43. compressed_image.save(f"compressed_image_{k}.jpg")
  44. # 计算并打印原图像和压缩图像的文件大小
  45. original_file_size = os.path.getsize("original_image.jpg") * 8 #根据图像像素空间大小计算压缩率
  46. compressed_file_sizes = [os.path.getsize(f"compressed_image_{k}.jpg") * 8 for k in singular_values_list]
  47. #计算图片压缩率
  48. for k, compressed_size in zip(singular_values_list, compressed_file_sizes):
  49. rate = round((original_file_size - compressed_size) / original_file_size, 4) * 100
  50. compressed_image, U_size, S_size, V_size = compress_svd(image_array[:, :, channel], k)
  51. #图像传输过程中不是直接传输图像,而是传输图像的矩阵,SVD对图像矩阵进行拆分
  52. transmit_rate = round((image_array.size - 3 * (U_size + S_size + V_size))/image_array.size, 4) * 100
  53. print(f"压缩图像 (k={k}) 文件大小: {compressed_size} bits")
  54. print(f"图像大小压缩率 (k={k}): {rate}%")
  55. print(f"传输压缩率(k={k}):{transmit_rate}%")
  56. # 显示原始图像和8个不同压缩级别的图像
  57. plt.rcParams["font.sans-serif"] = "SimHei" # 解决中文显示问题
  58. plt.figure()#自适应
  59. # plt.figure(figsize=(10, 8))
  60. # 显示原始图像
  61. plt.subplot(3, 3, 1)
  62. plt.title("原始图像")
  63. plt.imshow(original_image)
  64. plt.axis("off")
  65. # 显示压缩图像
  66. for i, (k, compressed_image) in enumerate(zip(singular_values_list, compressed_images)):
  67. plt.subplot(3, 3, i + 2)
  68. plt.title(f"压缩图像 (k={k})")
  69. plt.imshow(compressed_image)
  70. plt.axis("off")
  71. plt.tight_layout()
  72. plt.show()

进行归一化的图片显示效果更好,但是因为图片亮度趋于中值,因此也会使图片显示效果不像原图。亮的地方变暗,暗的地方变亮。

对SVD的讲解参考了下面的文章:

基于奇异值分解(SVD)的图片压缩实践

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/713904
推荐阅读
相关标签
  

闽ICP备14008679号