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) # 将数组还原成图像
chengdu.shape
(516, 819, 3)
在使用 mpimg.imread
函数读取图片后,实际上返回的是一个 numpy.array
类型的数组,该数组表示的是一个像素点的矩阵,包含长,宽,高三个要素。如成都锦里这张图片,总共包含了 \(516\) 行,\(819\) 列共 \(516*819=422604\) 个像素点,每一个像素点的高度对应着计算机颜色中的三原色 RGB(红,绿,蓝),共 3 个要素构成。
26.3. 数据预处理#
为方便后期的数据处理,需要对数据进行降维。
挑战:将形状为 \((516, 819, 3)\) 的数据转换为 \((422604, 3)\) 形状的数据。
提示:使用 np.reshape
进行数据格式的变换。
"""数据格式变换
"""
## 代码开始 ### (≈ 1 行代码)
data = None
## 代码结束 ###
Solution to Exercise 26.1
"""数据格式变换
"""
### 代码开始 ###(≈ 1 行代码)
data = chengdu.reshape(516 * 819, 3)
### 代码结束 ###
运行测试
data.shape, data[10]
期望输出
((422604, 3), array([0.12941177, 0.13333334, 0.14901961], dtype=float32))
26.4. 像素点种类个数计算#
尽管有 422604
个像素点,但其中仍然有许多相同的像素点。在此我们定义:RGB 值相同的点为一个种类,其中任意值不同的点为不同种类。
挑战:计算 422604
个像素点中种类的个数。
提示:可以将数据转化为 list 类型,然后将每一个元素转换为 tuple 类型,最后利用 set()
和 len()
函数进行计算。也可以按照自己的想法完成。
"""计算像素点种类个数
"""
def get_variety(data):
"""
参数:
预处理后像素点集合
返回:
num_variety -- 像素点种类个数
"""
### 代码开始 ### (≈ 3 行代码)
num_variety=None
### 代码结束 ###
return num_variety
Solution to Exercise 26.2
"""计算像素点种类个数
"""
def get_variety(data):
"""
参数:
预处理后像素点集合
返回:
num_variety -- 像素点种类个数
"""
### 代码开始 ### (≈ 3 行代码)
temp=data.tolist()
num_variety=len(set([tuple(t) for t in temp]))
### 代码结束 ###
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 的方式将图片的像素点进行聚类,将相似的像素点用同一像素点值来代替,从而降低像素点种类的数量,以达到压缩图片的效果。
挑战:使用 Mini Batch K-Means 聚类方法对像素点进行聚类,并用每一个中心的像素点代替属于该类别的像素点。
规定:聚类簇数量设置为 10 类。
提示:使用 MiniBatchKMeans
中 fit()
和 predict()
函数进行聚类,使用 cluster_centers_()
函数进行替换,本次挑战基本使用默认参数。阅读官方文档
from sklearn.cluster import MiniBatchKMeans
## 代码开始 ### (≈ 4 行代码)
predict=None
## 代码结束 ###
new_colors = model.cluster_centers_[predict]
Solution to Exercise 26.3
from sklearn.cluster import MiniBatchKMeans
### 代码开始 ###(≈ 4 行代码)
model = MiniBatchKMeans(10)
model.fit(data)
predict=model.predict(data)
### 代码结束 ###
new_colors = model.cluster_centers_[predict]
运行测试
# 调用前面实现计算像素点种类的函数,计算像素点更新后种类的个数
get_variety(new_colors)
期望输出
10
26.6. 图像压缩前后对比#
挑战:将聚类后并替换为类别中心点值的像素点,变换为数据处理前的格式,并绘制出图片进行对比展示。
提示:使用 reshape()
函数进行格式变换,使用 imshow()
函数进行绘图。
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
## 代码开始 ### (≈ 3 行代码)
## 代码结束 ###
Solution to Exercise 26.4
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
### 代码开始 ### (≈ 3 行代码)
new_chengdu = new_colors.reshape(chengdu.shape)
ax[0].imshow(chengdu)
ax[1].imshow(new_chengdu)
### 代码结束 ###
期望输出
通过图片对比,可以十分容易发现画质被压缩了。其实,因为使用了聚类,压缩后的图片颜色就变为了 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 聚类方法对图像压缩之后,体积明显缩小。
如果你觉得这些内容对你有帮助,可以通过 微信赞赏码 或者 Buy Me a Coffee 请我喝杯咖啡。