本示例将展示如何使用Python语言中的SciPy库对一张玩具图像——浣熊脸部图像进行量化压缩。量化压缩是一种减少图像灰度级以降低存储空间的技术。将通过不同的策略来实现这一过程,并比较它们的效果。
首先,从SciPy库中加载浣熊脸部图像,并检查其尺寸、数据类型和在内存中的占用情况。根据SciPy的版本,加载图像的函数可能位于不同的模块中。从SciPy 1.10版本开始,需要安装额外的pooch包。以下是加载图像并获取相关信息的代码示例:
try:
# SciPy >= 1.10
from scipy.datasets import face
except ImportError:
from scipy.misc import face
raccoon_face = face(gray=True)
print(f"图像的尺寸是 {raccoon_face.shape}")
print(f"图像数据的类型是 {raccoon_face.dtype}")
print(f"图像在RAM中占用的字节数是 {raccoon_face.nbytes}")
输出结果显示,图像是一个768像素高、1024像素宽的二维数组。每个像素值是一个8位无符号整数,意味着图像使用8位来编码每个像素。图像在内存中的总占用约为786KB。使用8位无符号整数意味着图像最多可以有256种不同的灰度级。可以通过直方图来检查这些灰度值的分布情况。
量化压缩的核心思想是通过减少灰度级来表示图像。例如,可以使用8个灰度值而不是256个。这意味着可以有效地使用3位而不是8位来编码单个像素,从而将内存使用量减少约2.5倍。将在后面讨论这种内存使用情况。
压缩可以通过使用KBinsDiscretizer来实现。需要选择一个策略来定义8个灰度值进行子采样。最简单的策略是定义它们为等间距的,这对应于设置strategy="uniform"。从之前的直方图中知道,这种策略肯定不是最优的。以下是使用等间距策略进行压缩的代码示例:
from sklearn.preprocessing import KBinsDiscretizer
n_bins = 8
encoder = KBinsDiscretizer(n_bins=n_bins, encode="ordinal", strategy="uniform", random_state=0)
compressed_raccoon_uniform = encoder.fit_transform(raccoon_face.reshape(-1, 1)).reshape(raccoon_face.shape)
通过上述代码,可以看到压缩后的图像在视觉上仍然保持了较好的质量,尽管在某些小区域(例如右下角的树叶)可以看到压缩的效果。还观察到像素值的分布已经被映射到了8个不同的值。可以检查这些值与原始像素值之间的对应关系。
如前所述,等间距采样策略不是最优的。例如,映射到值7的像素将编码相对较少的信息量,而映射到值3的像素将代表大量的计数。可以使用k-means等聚类策略来找到更优的映射。以下是使用k-means策略进行压缩的代码示例:
encoder = KBinsDiscretizer(n_bins=n_bins, encode="ordinal", strategy="kmeans", random_state=0)
compressed_raccoon_kmeans = encoder.fit_transform(raccoon_face.reshape(-1, 1)).reshape(raccoon_face.shape)
使用k-means策略后,可以看到直方图中的计数更加平衡,且它们的中心不再等间距。需要注意的是,可以通过使用strategy="quantile"而不是strategy="kmeans"来强制每个箱子中的像素数量相同。
之前提到,应该节省8倍的内存。让来验证一下。以下是检查压缩后图像在内存中占用情况的代码示例:
print(f"压缩后图像在RAM中占用的字节数是 {compressed_raccoon_kmeans.nbytes}")
print(f"压缩比率: {compressed_raccoon_kmeans.nbytes / raccoon_face.nbytes}")
输出结果显示,压缩后的图像在内存中的占用量为6291456字节,压缩比率为8.0。这与预期相反。主要原因是用于编码图像的数据类型。
print(f"压缩后图像的数据类型是 {compressed_raccoon_kmeans.dtype}")
输出结果显示,KBinsDiscretizer的输出是一个64位浮点数数组。这意味着它占用的内存是原来的8倍。然而,使用这种64位浮点数表示来编码8个值。实际上,只有在将压缩后的图像转换为3位整数数组时才会节省内存。可以使用numpy.ndarray.astype方法。然而,3位整数表示不存在,为了编码8个值,也需要使用8位无符号整数表示。
在实践中,要观察到内存收益,原始图像需要以64位浮点数表示。
脚本的总运行时间为:(0分钟 2.133秒)