赞
踩
先简单介绍一下原理:
以下为用SVD压缩图片的python实现:
- import numpy as np
- from PIL import Image
- from matplotlib import pyplot as plt
- from os.path import getsize
-
-
- def svd(img_array, ratio):
- """
- 使用奇异值分解对图片进行压缩
- :param img_array: 图片数组
- :param ratio: 压缩比率
- :return: 压缩后的图片数组
- """
- U, s, V = np.linalg.svd(img_array) # 奇异值分解
- diag_s = np.diag(s) # 构造对角矩阵
- k = np.argmax(np.cumsum(s) / np.sum(s) > ratio) # 根据给定的压缩比率选择保留的奇异值数量
- diag_s = diag_s[:k, :k] # 保留前k个奇异值
- # 压缩图片
- U = U[:, :k]
- V = V[:k, :]
- return U.dot(diag_s).dot(V)
-
-
- def svd_compress(img_path, ratio_list=[0.95]):
- """
- 对给定路径的图片进行一系列压缩,并显示压缩后的图片及其相关信息
- :param img_path: 图片路径
- :param ratio_list: 压缩比率列表
- :return: None
- """
- img = Image.open(img_path)
- img_array = np.array(img)
- red_array, green_array, blue_array = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2] # 分离RGB通道
- fig = plt.figure()
- img_num = len(ratio_list) + 1 # 图片数量
- img_size = getsize(img_path)
- index = 1
- # 压缩图片并显示相关信息
- for ratio in ratio_list:
- compressed_red_array, compressed_green_array, compressed_blue_array = (
- svd(red_array, ratio), svd(green_array, ratio), svd(blue_array, ratio)) # 压缩RGB通道
- compressed_img_array = np.stack((compressed_red_array, compressed_green_array, compressed_blue_array), axis=2) # 合并RGB通道
- compressed_img = Image.fromarray(compressed_img_array.astype(np.uint8)) # 转换为图片
- compressed_img_path = f'{img_path[:-4]}_svd_{ratio:.2f}.jpg'
- compressed_img.save(compressed_img_path) # 保存压缩后的图片
- compression_ratio = getsize(compressed_img_path) / img_size # 计算压缩率
- # 显示压缩后的图片及其相关信息
- ax = fig.add_subplot(1, img_num, index)
- ax.imshow(compressed_img)
- ax.set_title(f'Ratio:{ratio:.2f}\nCR:{compression_ratio*100:.2f}%')
- index += 1
-
- # 显示原始图片
- ax = fig.add_subplot(1, img_num, img_num)
- ax.imshow(img)
- ax.set_title('Original Image')
- plt.show()
-
-
- if __name__ == "__main__":
- 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取值策略后的代码如下:
- import numpy as np
- from PIL import Image
- from matplotlib import pyplot as plt
- from os.path import getsize
-
-
- def difference(array):
- """
- 计算数组的差分
- :param array: 一维数组
- :return: 差分数组
- """
- array_sum = sum(array)
- return [array[i] / array_sum for i in range(len(array))]
-
-
- def svd(img_array, ratio):
- """
- 使用奇异值分解对图片进行压缩
- :param img_array: 图片数组
- :param ratio: 压缩比率
- :return: 压缩后的图片数组
- """
- U, s, V = np.linalg.svd(img_array) # 奇异值分解
- diag_s = np.diag(s) # 构造对角矩阵
- ds = difference(s) # 差分奇异值
- k = np.argmax(ds <= ratio) # 找到第一个小于等于压缩比率的奇异值下标
- diag_s = diag_s[:k, :k] # 保留前k个奇异值
- # 压缩图片
- U = U[:, :k]
- V = V[:k, :]
- return U.dot(diag_s).dot(V)
-
-
- def svd_compress(img_path, ratio_list=[0.95]):
- """
- 对给定路径的图片进行一系列压缩,并显示压缩后的图片及其相关信息
- :param img_path: 图片路径
- :param ratio_list: 压缩比率列表
- :return: None
- """
- img = Image.open(img_path)
- img_array = np.array(img)
- red_array, green_array, blue_array = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2] # 分离RGB通道
- fig = plt.figure()
- img_num = len(ratio_list) + 1 # 图片数量
- img_size = getsize(img_path)
- index = 1
- # 压缩图片并显示相关信息
- for ratio in ratio_list:
- compressed_red_array, compressed_green_array, compressed_blue_array = (
- svd(red_array, ratio), svd(green_array, ratio), svd(blue_array, ratio)) # 压缩RGB通道
- compressed_img_array = np.stack((compressed_red_array, compressed_green_array, compressed_blue_array), axis=2) # 合并RGB通道
- compressed_img = Image.fromarray(compressed_img_array.astype(np.uint8)) # 转换为图片
- compressed_img_path = f'{img_path[:-4]}_svd_{ratio:.3f}.jpg'
- compressed_img.save(compressed_img_path) # 保存压缩后的图片
- compression_ratio = getsize(compressed_img_path) / img_size # 计算压缩率
- # 显示压缩后的图片及其相关信息
- ax = fig.add_subplot(1, img_num, index)
- ax.imshow(compressed_img)
- ax.set_title(f'Ratio:{ratio:.3f}\nCR:{compression_ratio * 100:.2f}%')
- index += 1
-
- # 显示原始图片
- ax = fig.add_subplot(1, img_num, img_num)
- ax.imshow(img)
- ax.set_title('Original Image')
- plt.show()
-
-
- if __name__ == "__main__":
- svd_compress('lenna.bmp', np.logspace(1, 3, 9, base=0.1))
优化前后的压缩率及成像质量对比(上方为优化后,下方为优化前):
选取压缩率差别小于10%的图片放大对比:
在k值较小的情况下,优化后的图片更加清晰,但噪点也更多;
在k值较大的情况下,两者差别不明显。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。