向量量化示例#
此示例展示了如何使用 KBinsDiscretizer
对一组玩具图像(浣熊脸)执行向量量化。
# Authors: Gael Varoquaux
# Jaques Grobler
# License: BSD 3 clause
原始图像#
我们首先从 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"The dimension of the image is {raccoon_face.shape}")
print(f"The data used to encode the image is of type {raccoon_face.dtype}")
print(f"The number of bytes taken in RAM is {raccoon_face.nbytes}")
The dimension of the image is (768, 1024)
The data used to encode the image is of type uint8
The number of bytes taken in RAM is 786432
因此,该图像是一个二维数组,高度为 768 像素,宽度为 1024 像素。每个值都是一个 8 位无符号整数,这意味着该图像是使用每像素 8 位编码的。图像的总内存使用量为 786 KB(1 字节等于 8 位)。
使用 8 位无符号整数意味着图像最多使用 256 种不同的灰度进行编码。我们可以检查这些值的分布。
import matplotlib.pyplot as plt
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(raccoon_face, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(raccoon_face.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Count of pixels")
ax[1].set_title("Distribution of the pixel values")
_ = fig.suptitle("Original image of a raccoon face")
通过向量量化进行压缩#
通过向量量化进行压缩背后的想法是减少表示图像的灰度级数。例如,我们可以使用 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
)
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(compressed_raccoon_uniform, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(compressed_raccoon_uniform.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Count of pixels")
ax[1].set_title("Sub-sampled distribution of the pixel values")
_ = fig.suptitle("Raccoon face compressed using 3 bits and a uniform strategy")
定性地说,我们可以发现一些可以看到压缩效果的小区域(例如右下角的叶子)。但毕竟,生成的图像看起来还不错。
我们观察到像素值的分布已映射到 8 个不同的值。我们可以检查这些值与原始像素值之间的对应关系。
bin_edges = encoder.bin_edges_[0]
bin_center = bin_edges[:-1] + (bin_edges[1:] - bin_edges[:-1]) / 2
bin_center
array([ 15.625, 46.875, 78.125, 109.375, 140.625, 171.875, 203.125,
234.375])
_, ax = plt.subplots()
ax.hist(raccoon_face.ravel(), bins=256)
color = "tab:orange"
for center in bin_center:
ax.axvline(center, color=color)
ax.text(center - 10, ax.get_ybound()[1] + 100, f"{center:.1f}", color=color)
如前所述,均匀采样策略并不是最佳的。例如,请注意,映射到值 7 的像素将编码相当少的信息,而映射值 3 将表示大量的计数。我们可以改用聚类策略(例如 k 均值)来找到更优的映射。
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
)
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(compressed_raccoon_kmeans, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(compressed_raccoon_kmeans.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Number of pixels")
ax[1].set_title("Distribution of the pixel values")
_ = fig.suptitle("Raccoon face compressed using 3 bits and a K-means strategy")
bin_edges = encoder.bin_edges_[0]
bin_center = bin_edges[:-1] + (bin_edges[1:] - bin_edges[:-1]) / 2
bin_center
array([ 18.90885631, 53.34346583, 82.64447187, 109.28225276,
134.70763101, 159.78681467, 185.17226834, 224.02069427])
_, ax = plt.subplots()
ax.hist(raccoon_face.ravel(), bins=256)
color = "tab:orange"
for center in bin_center:
ax.axvline(center, color=color)
ax.text(center - 10, ax.get_ybound()[1] + 100, f"{center:.1f}", color=color)
现在,箱中的计数更加平衡,并且它们的中心不再等距。请注意,我们可以通过使用 strategy="quantile"
而不是 strategy="kmeans"
来强制每个箱中的像素数量相同。
内存占用#
我们之前说过,我们应该节省 8 倍的内存。让我们来验证一下。
print(f"The number of bytes taken in RAM is {compressed_raccoon_kmeans.nbytes}")
print(f"Compression ratio: {compressed_raccoon_kmeans.nbytes / raccoon_face.nbytes}")
The number of bytes taken in RAM is 6291456
Compression ratio: 8.0
令人惊讶的是,我们压缩后的图像占用的内存比原始图像多 8 倍。这确实与我们预期的相反。原因主要是由于用于编码图像的数据类型。
print(f"Type of the compressed image: {compressed_raccoon_kmeans.dtype}")
Type of the compressed image: float64
实际上,KBinsDiscretizer
的输出是一个 64 位浮点数数组。这意味着它占用了 8 倍的内存。但是,我们使用这种 64 位浮点表示来编码 8 个值。实际上,只有将压缩后的图像转换为 3 位整数数组,我们才能节省内存。我们可以使用 numpy.ndarray.astype
方法。但是,不存在 3 位整数表示形式,并且为了对 8 个值进行编码,我们还需要使用 8 位无符号整数表示形式。
在实践中,观察内存增益将需要原始图像采用 64 位浮点表示形式。
**脚本总运行时间:**(0 分钟 2.161 秒)
相关示例