使用 OpenCV 和 K-Means 聚类进行图像分割

王 茂南 2021年8月13日07:46:15
评论
1 3262字阅读10分52秒
摘要本文介绍使用 OpenCV 中的 K-Means 来对图像颜色进行聚类(减少图像颜色),根据聚类结果对图像进行分块,或是替换某种颜色。

简介

图像分割是将一张图像分割为不同的区域,这样做可以使得图像变得更简单,方便后面的处理。例如在图像分类领域,图像中不止包含一个 object,于是可以使用图像分割对其进行分割。

在这个教程中,我们使用 K-Means 根据图像的颜色对其进行聚类(哪些颜色可以归为一类,例如现在图像中包含颜色「红色,粉红色,蓝色」,那么「红色,粉红色」可以为一类,「蓝色」为一类),接着按照聚类的结果对图像进行分割(比如可以使用遮罩遮住「蓝色」的区别,这样就只有「红色,粉红色」的区域了)。

在接着看下去之前,我们需要确保安装了以下的 python 库。

  1. pip3 install opencv-python numpy matplotlib

 

参考资料

How to Use K-Means Clustering for Image Segmentation using OpenCV in Python

 

通过 OpenCV 中 K-Means 简化图像颜色与分块

准备工作(库与图像)

我们首先导入需要使用的库:

  1. import cv2
  2. import numpy as np
  3. import matplotlib.pyplot as plt

接着选择一张图片来进行展示,我们这里就使用「机器猫」来作为展示(也可以替换为其他图片):

使用 OpenCV 和 K-Means 聚类进行图像分割

使用 K-Means 进行聚类(减少图像颜色)

我们使用 OpenCV 导入图片,看一下图像的大小为 4404733

  1. # read the image
  2. image = cv2.imread("./cat.png")
  3. print('image shape: {}'.format(image.shape))
  4. # (440, 473, 3)

为了之后的显示需要,我们将图像转换为 RGB 格式(进行这一步的原因,可以参考链接,图像处理-matplotlib显示opencv图像):

  1. # convert to RGB
  2. image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

为了进行聚类,我们将 image 转换为一个二维向量(cv2.kmeans 将一个「二维向量」作为输入),这个二位向量大小是 N*3。如下图所示,我们将一个像素的 RGB 放在一起:

使用 OpenCV 和 K-Means 聚类进行图像分割

再解释一下如下图所示,原始图像有三个 channel(分别是 RGB),我们在每一个 channel 中相同位置的数取出合并在一起。

使用 OpenCV 和 K-Means 聚类进行图像分割

按照上面的思路,我们对图像进行 reshape,接着转换为 float 类型:

  1. # reshape the image to a 2D array of pixels and 3 color values (RGB)
  2. pixel_values = image.reshape((-1, 3))
  3. # convert to float
  4. pixel_values = np.float32(pixel_values)
  5. print(pixel_values.shape)
  6. # (208120, 3)

上面最终的大小为 208120,也就是 440*473=208120。之后我们会对上面的数据进行聚类,上面每一个数据其实就是一种颜色,如果我们聚类结果为 3,那么原始图像就会被压缩为三种颜色

在使用 K-Means 算法的时候,我们需要知道算法的停止条件。在这里为了希望迭代次数不超过 100,或是 clusters 中心移动的值非常小(这里是 0.2),下面是定义的停止条件:

  1. # define stopping criteria --> 定义聚类停止的条件
  2. criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)

上面的图像主要有 5 中颜色,我们因此将图像颜色聚类为 5 种:

  1. # 进行聚类
  2. k = 5
  3. _, labels, (centers) = cv2.kmeans(data=pixel_values, K=k,
  4.                                     bestLabels=None, criteria=criteria,
  5.                                     attempts=10,
  6.                                     flags=cv2.KMEANS_RANDOM_CENTERS)
  7. # centers --> 是 rgb 的颜色, 这里聚成 5 个类别,所以 centers 会有 5 个 RGB 的值; 
  8. # labels --> 每个 pixel 的 label,会告诉你这个 pixel 对应的

我们再对上面的代码进行解释:

  • labels 是每一个 pixel 所属的类,这里有 5 种颜色,所以 label 的取值是从 0-4,代表五种颜色;
  • centers 表示五类中心点,在这里就是 5 种颜色的 RGB 值;
  • cv2.KMEANS_RANDOM_CENTERS 表示聚类中间点初始是随机的;

下面是 centers 的输出,可以看到这里就是五个值:

使用 OpenCV 和 K-Means 聚类进行图像分割

这里也注意到,centersfloat 类型,对于图像来说,我们需要首先转换为 int8 数据类型:

  1. centers = np.uint8(centers)

转换后 centers 如下所示:

使用 OpenCV 和 K-Means 聚类进行图像分割

接着我们根据 label,将 label 中的数字转换为对应的 RGB 值,并使用 reshape 转换为图像的大小:

  1. # flatten the labels array
  2. labels = labels.flatten()
  3. # convert all pixels to the color of the centroids
  4. segmented_image = centers[labels.flatten()]
  5. # reshape back to the original image dimension
  6. segmented_image = segmented_image.reshape(image.shape)

这里 segmented_image 是按照 pixel 转换后的 RGB 值,只需要进行 reshape 就是图像了。下图是 reshape 之前的 segmented_image 的样子:

使用 OpenCV 和 K-Means 聚类进行图像分割

到这里就完成了图像颜色的简化,我们将最终的结果显示一下:

  1. # show the image
  2. plt.imshow(segmented_image)
  3. plt.show()

可以看到简化之后图像的颜色就明显变少了,左侧是原始的图像,右侧是进行简化后的图像

使用 OpenCV 和 K-Means 聚类进行图像分割

到这里为止我们就完成了使用 OpenCV 中的 K-Means 完成颜色的聚类和简化。

 

遮挡住某种颜色

上面我们将图像分为了五种颜色,接下来我们想要替换机器猫的背景颜色(也就是淡蓝色的部分,这个颜色对应上面的 label=1)。

上面在聚类的时候我们已经获得了 label=1 的全部位置,我们只需要将图像中对应的位置改变 rgb 值即可。下面我们将背景替换为「白色」:

  1. # disable only the cluster number 2 (turn the pixel into black)
  2. masked_image = np.copycopy(image)
  3. # convert to the shape of a vector of pixel values
  4. masked_image = masked_image.reshape((-1, 3))
  5. # color (i.e cluster) to disable
  6. masked_image[labels == 0] = [255, 255, 255] # 替换颜色
  7. # convert back to original shape
  8. masked_image = masked_image.reshape(image.shape)
  9. # show the image
  10. plt.imshow(masked_image)
  11. plt.show()

最终的效果如下所示,可以看到背景颜色被换成了白色(但是好像影响到了腮红部分,那里聚类结果也是淡蓝色的):

使用 OpenCV 和 K-Means 聚类进行图像分割

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2021年8月13日07:46:15
  • 转载请务必保留本文链接:https://mathpretty.com/13919.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: