26. 使用 K-Means 完成图像压缩#

26.1. 介绍#

本次挑战将针对一张成都著名景点:锦里的图片,通过 Mini Batch K-Means 的方法将相近的像素点聚合后用同一像素点代替,以达到图像压缩的效果。

26.2. 知识点#

  • 图像压缩

  • Mini Batch K-Means 聚类

首先,我们下载并导入示例图片,图片名为 challenge-7-chengdu.png

wget -nc "https://cdn.aibydoing.com/hands-on-ai/files/challenge-7-chengdu.png"
--2023-11-13 17:16:21--  https://cdn.aibydoing.com/hands-on-ai/files/challenge-7-chengdu.png
正在解析主机 cdn.aibydoing.com (cdn.aibydoing.com)... 198.18.7.59
正在连接 cdn.aibydoing.com (cdn.aibydoing.com)|198.18.7.59|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:1057505 (1.0M) [image/png]
正在保存至: “challenge-7-chengdu.png”

challenge-7-chengdu 100%[===================>]   1.01M  1.54MB/s  用时 0.7s      

2023-11-13 17:16:23 (1.54 MB/s) - 已保存 “challenge-7-chengdu.png” [1057505/1057505])

使用 Matplotlib 可视化示例图片。

import matplotlib.pyplot as plt 
import matplotlib.image as mpimg 
%matplotlib inline

chengdu = mpimg.imread('challenge-7-chengdu.png') # 将图片加载为 ndarray 数组
plt.imshow(chengdu) # 将数组还原成图像
<matplotlib.image.AxesImage at 0x11e371ea0>
../_images/adf5448f80130079d091f2649f426a56c57fd0a0bc0e56f25f7c6f3876ea7ae7.png
chengdu.shape
(516, 819, 3)

在使用 mpimg.imread 函数读取图片后,实际上返回的是一个 numpy.array 类型的数组,该数组表示的是一个像素点的矩阵,包含长,宽,高三个要素。如成都锦里这张图片,总共包含了 \(516\) 行,\(819\) 列共 \(516*819=422604\) 个像素点,每一个像素点的高度对应着计算机颜色中的三原色 RGB(红,绿,蓝),共 3 个要素构成。

26.3. 数据预处理#

为方便后期的数据处理,需要对数据进行降维。

Exercise 26.1

挑战:将形状为 \((516, 819, 3)\) 的数据转换为 \((422604, 3)\) 形状的数据。

提示:使用 np.reshape 进行数据格式的变换。

"""数据格式变换
"""
## 代码开始 ### (≈ 1 行代码)
data = None
## 代码结束 ###

运行测试

data.shape, data[10]

期望输出

((422604, 3), array([0.12941177, 0.13333334, 0.14901961], dtype=float32))

26.4. 像素点种类个数计算#

尽管有 422604 个像素点,但其中仍然有许多相同的像素点。在此我们定义:RGB 值相同的点为一个种类,其中任意值不同的点为不同种类。

Exercise 26.2

挑战:计算 422604 个像素点中种类的个数。

提示:可以将数据转化为 list 类型,然后将每一个元素转换为 tuple 类型,最后利用 set()len() 函数进行计算。也可以按照自己的想法完成。

"""计算像素点种类个数
"""
def get_variety(data):
    """
    参数:
    预处理后像素点集合

    返回:
    num_variety -- 像素点种类个数
    """

    ### 代码开始 ### (≈ 3 行代码)
    num_variety=None
    ### 代码结束 ###
    
    return num_variety

运行测试

get_variety(data), data[20]

期望输出

(100109, array([0.24705882, 0.23529412, 0.2627451 ], dtype=float32))

26.5. Mini Batch K-Means 聚类#

像素点种类的数量是决定图片大小的主要因素之一,在此使用 Mini Batch K-Means 的方式将图片的像素点进行聚类,将相似的像素点用同一像素点值来代替,从而降低像素点种类的数量,以达到压缩图片的效果。

Exercise 26.3

挑战:使用 Mini Batch K-Means 聚类方法对像素点进行聚类,并用每一个中心的像素点代替属于该类别的像素点。

规定:聚类簇数量设置为 10 类。

提示:使用 MiniBatchKMeansfit()predict() 函数进行聚类,使用 cluster_centers_()函数进行替换,本次挑战基本使用默认参数。阅读官方文档

from sklearn.cluster import MiniBatchKMeans

## 代码开始 ### (≈ 4 行代码)
predict=None
## 代码结束 ###

new_colors = model.cluster_centers_[predict]

运行测试

# 调用前面实现计算像素点种类的函数,计算像素点更新后种类的个数
get_variety(new_colors)

期望输出

10

26.6. 图像压缩前后对比#

Exercise 26.4

挑战:将聚类后并替换为类别中心点值的像素点,变换为数据处理前的格式,并绘制出图片进行对比展示。

提示:使用 reshape() 函数进行格式变换,使用 imshow()函数进行绘图。

fig, ax = plt.subplots(1, 2, figsize=(16, 6))

## 代码开始 ### (≈ 3 行代码)

## 代码结束 ###

期望输出

image

通过图片对比,可以十分容易发现画质被压缩了。其实,因为使用了聚类,压缩后的图片颜色就变为了 10 种。

接下来,使用 mpimg.imsave() 函数将压缩好的文件进行存储,并对比压缩前后图像的体积变化。

# 运行对比
mpimg.imsave("new_chengdu.png", new_chengdu)
!du -h new_chengdu.png
!du -h challenge-7-chengdu.png

可以看到,使用 Mini Batch K-Means 聚类方法对图像压缩之后,体积明显缩小。