当前位置:   article > 正文

SVD(奇异值分解)应用的python实现(一:图片压缩)_python svd

python svd

先简单介绍一下原理:

以下为用SVD压缩图片的python实现:

  1. import numpy as np
  2. from PIL import Image
  3. from matplotlib import pyplot as plt
  4. from os.path import getsize
  5. def svd(img_array, ratio):
  6. """
  7. 使用奇异值分解对图片进行压缩
  8. :param img_array: 图片数组
  9. :param ratio: 压缩比率
  10. :return: 压缩后的图片数组
  11. """
  12. U, s, V = np.linalg.svd(img_array) # 奇异值分解
  13. diag_s = np.diag(s) # 构造对角矩阵
  14. k = np.argmax(np.cumsum(s) / np.sum(s) > ratio) # 根据给定的压缩比率选择保留的奇异值数量
  15. diag_s = diag_s[:k, :k] # 保留前k个奇异值
  16. # 压缩图片
  17. U = U[:, :k]
  18. V = V[:k, :]
  19. return U.dot(diag_s).dot(V)
  20. def svd_compress(img_path, ratio_list=[0.95]):
  21. """
  22. 对给定路径的图片进行一系列压缩,并显示压缩后的图片及其相关信息
  23. :param img_path: 图片路径
  24. :param ratio_list: 压缩比率列表
  25. :return: None
  26. """
  27. img = Image.open(img_path)
  28. img_array = np.array(img)
  29. red_array, green_array, blue_array = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2] # 分离RGB通道
  30. fig = plt.figure()
  31. img_num = len(ratio_list) + 1 # 图片数量
  32. img_size = getsize(img_path)
  33. index = 1
  34. # 压缩图片并显示相关信息
  35. for ratio in ratio_list:
  36. compressed_red_array, compressed_green_array, compressed_blue_array = (
  37. svd(red_array, ratio), svd(green_array, ratio), svd(blue_array, ratio)) # 压缩RGB通道
  38. compressed_img_array = np.stack((compressed_red_array, compressed_green_array, compressed_blue_array), axis=2) # 合并RGB通道
  39. compressed_img = Image.fromarray(compressed_img_array.astype(np.uint8)) # 转换为图片
  40. compressed_img_path = f'{img_path[:-4]}_svd_{ratio:.2f}.jpg'
  41. compressed_img.save(compressed_img_path) # 保存压缩后的图片
  42. compression_ratio = getsize(compressed_img_path) / img_size # 计算压缩率
  43. # 显示压缩后的图片及其相关信息
  44. ax = fig.add_subplot(1, img_num, index)
  45. ax.imshow(compressed_img)
  46. ax.set_title(f'Ratio:{ratio:.2f}\nCR:{compression_ratio*100:.2f}%')
  47. index += 1
  48. # 显示原始图片
  49. ax = fig.add_subplot(1, img_num, img_num)
  50. ax.imshow(img)
  51. ax.set_title('Original Image')
  52. plt.show()
  53. if __name__ == "__main__":
  54. svd_compress('lenna.bmp', np.linspace(0.2, 0.99, 9))

输入:

输出:

注意原始图片为.bmp格式,压缩后的图片为.jpg格式,该图片单纯由.bmp转为.jpg的压缩率为5.07%。

从输出的图片中可以直观地看到选取k值的比率对压缩率及成像质量的影响:

当k取奇异值总和的前50%时,可以得到一张勉强能够辨认的图片,压缩率为3.41%;

当k取奇异值总和的前60%时,可以得到一张清晰但有大量噪点的图片,压缩率为4.17%;

当k取奇异值总和的前90%时,可以得到一张与原图几乎一模一样、仅在物体边界处有少量噪点的图片,压缩率为4.97%;

当k取奇异值总和的前99%时,可以得到一张与原图一模一样、肉眼无法看出差别的图片,压缩率为5.06%。

优化k取值的一个策略:

目前为止对RGB三个颜色采用的都是相同的取值策略,但RGB的k值“能量”分布其实略有不同:

可以取三个颜色各自斜率为设定值的临界点的k值,如设定值为1表示取三个颜色各自增长速度小于y=x的临界点。

修改k取值策略后的代码如下:

  1. import numpy as np
  2. from PIL import Image
  3. from matplotlib import pyplot as plt
  4. from os.path import getsize
  5. def difference(array):
  6. """
  7. 计算数组的差分
  8. :param array: 一维数组
  9. :return: 差分数组
  10. """
  11. array_sum = sum(array)
  12. return [array[i] / array_sum for i in range(len(array))]
  13. def svd(img_array, ratio):
  14. """
  15. 使用奇异值分解对图片进行压缩
  16. :param img_array: 图片数组
  17. :param ratio: 压缩比率
  18. :return: 压缩后的图片数组
  19. """
  20. U, s, V = np.linalg.svd(img_array) # 奇异值分解
  21. diag_s = np.diag(s) # 构造对角矩阵
  22. ds = difference(s) # 差分奇异值
  23. k = np.argmax(ds <= ratio) # 找到第一个小于等于压缩比率的奇异值下标
  24. diag_s = diag_s[:k, :k] # 保留前k个奇异值
  25. # 压缩图片
  26. U = U[:, :k]
  27. V = V[:k, :]
  28. return U.dot(diag_s).dot(V)
  29. def svd_compress(img_path, ratio_list=[0.95]):
  30. """
  31. 对给定路径的图片进行一系列压缩,并显示压缩后的图片及其相关信息
  32. :param img_path: 图片路径
  33. :param ratio_list: 压缩比率列表
  34. :return: None
  35. """
  36. img = Image.open(img_path)
  37. img_array = np.array(img)
  38. red_array, green_array, blue_array = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2] # 分离RGB通道
  39. fig = plt.figure()
  40. img_num = len(ratio_list) + 1 # 图片数量
  41. img_size = getsize(img_path)
  42. index = 1
  43. # 压缩图片并显示相关信息
  44. for ratio in ratio_list:
  45. compressed_red_array, compressed_green_array, compressed_blue_array = (
  46. svd(red_array, ratio), svd(green_array, ratio), svd(blue_array, ratio)) # 压缩RGB通道
  47. compressed_img_array = np.stack((compressed_red_array, compressed_green_array, compressed_blue_array), axis=2) # 合并RGB通道
  48. compressed_img = Image.fromarray(compressed_img_array.astype(np.uint8)) # 转换为图片
  49. compressed_img_path = f'{img_path[:-4]}_svd_{ratio:.3f}.jpg'
  50. compressed_img.save(compressed_img_path) # 保存压缩后的图片
  51. compression_ratio = getsize(compressed_img_path) / img_size # 计算压缩率
  52. # 显示压缩后的图片及其相关信息
  53. ax = fig.add_subplot(1, img_num, index)
  54. ax.imshow(compressed_img)
  55. ax.set_title(f'Ratio:{ratio:.3f}\nCR:{compression_ratio * 100:.2f}%')
  56. index += 1
  57. # 显示原始图片
  58. ax = fig.add_subplot(1, img_num, img_num)
  59. ax.imshow(img)
  60. ax.set_title('Original Image')
  61. plt.show()
  62. if __name__ == "__main__":
  63. svd_compress('lenna.bmp', np.logspace(1, 3, 9, base=0.1))

优化前后的压缩率及成像质量对比(上方为优化后,下方为优化前):

选取压缩率差别小于10%的图片放大对比:

在k值较小的情况下,优化后的图片更加清晰,但噪点也更多;

优化后 CR=3.68%
优化前 CR=3.41%

在k值较大的情况下,两者差别不明显。

优化后 CR=4.95%
优化前 CR=4.97%

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

闽ICP备14008679号