K-Means之圖像分割


圖像分割就是利用圖像自身的信息,比如顏色、紋理、形狀等特征進行划分,將圖像分割成不同的區域,划分出來的每個區域就相當於是對圖像中的像素進行了聚類。單個區域內的像素之間的相似度大,不同區域間的像素差異性大。這個特性正好符合聚類的特性,所以你可以把圖像分割看成是將圖像中的信息進行聚類。當然聚類只是分割圖像的一種方式,除了聚類,我們還可以基於圖像顏色的閾值進行分割,或者基於圖像邊緣的信息進行分割等。

將微信開屏封面進行分割

聚類的流程和分類差不多,如圖所示:

在准備階段里,我們需要對數據進行加載。因為處理的是圖像信息,我們除了要獲取圖像數據以外,還需要獲取圖像的尺寸和通道數,然后基於圖像中每個通道的數值進行數據規范化。這里我們需要定義個函數 load_data,來幫我們進行圖像加載和數據規范化。代碼如下:

# 加載圖像,並對數據進行規范化
def load_data(filePath):
    # 讀文件
    f = open(filePath,'rb')
    data = []
    # 得到圖像的像素值
    img = image.open(f)
    # 得到圖像尺寸
    width, height = img.size
    for x in range(width):
        for y in range(height):
            # 得到點 (x,y) 的三個通道值
            c1, c2, c3 = img.getpixel((x, y))
            data.append([c1, c2, c3])
    f.close()
    # 采用 Min-Max 規范化
    mm = preprocessing.MinMaxScaler()
    data = mm.fit_transform(data)
    return np.mat(data), width, height          ##### np.mat()是將序列轉為np的二維數組,np.transpose()是將數組轉置

因為 jpg 格式的圖像是三個通道 (R,G,B),也就是一個像素點具有 3 個特征值。這里我們用 c1、c2、c3 來獲取平面坐標點 (x,y) 的三個特征值,特征值是在 0-255 之間。

為了加快聚類的收斂,我們需要采用 Min-Max 規范化對數據進行規范化。我們定義的 load_data 函數返回的結果包括了針對 (R,G,B) 三個通道規范化的數據,以及圖像的尺寸信息。在定義好 load_data 函數后,我們直接調用就可以得到相關信息,代碼如下:

# 加載圖像,得到規范化的結果 img,以及圖像尺寸
img, width, height = load_data('./weixin.jpg')

假設我們想要對圖像分割成 2 部分,在聚類階段,我們可以將聚類數設置為 2,這樣圖像就自動聚成 2 類。代碼如下:

# 用 K-Means 對圖像進行 2 聚類
kmeans =KMeans(n_clusters=2)
kmeans.fit(img)
label = kmeans.predict(img)
# 將圖像聚類結果,轉化成圖像尺寸的矩陣
label = label.reshape([width, height])
# 創建個新圖像 pic_mark,用來保存圖像聚類的結果,並設置不同的灰度值
pic_mark = image.new(mode="L", (width, height))      # L表示灰度圖像,RGB表示彩色圖像
for x in range(width):
    for y in range(height):
        # 根據類別設置圖像灰度, 類別 0 灰度值為 255, 類別 1 灰度值為 127
        pic_mark.putpixel((x, y), int(256/(label[x][y]+1))-1)
pic_mark.save("weixin_mark.jpg", "JPEG")

如果我們想要分割成 16 個部分,該如何對不同分類設置不同的顏色值呢?這里需要用到 skimage 工具包,它是圖像處理工具包。你需要使用 pip install scikit-image 來進行安裝。

這段代碼可以將聚類標識矩陣轉化為不同顏色的矩陣:

from skimage import color
# 將聚類標識矩陣轉化為不同顏色的矩陣
label_color = (color.label2rgb(label)*255).astype(np.uint8)
label_color = label_color.transpose(1,0,2)
images = image.fromarray(label_color)
images.save('weixin_mark_color.jpg')

代碼中,我使用 skimage 中的 label2rgb 函數來將 label 分類標識轉化為顏色數值,因為我們的顏色值范圍是 [0,255],所以還需要乘以 255 進行轉化,最后再轉化為 np.uint8 類型。unit8 類型代表無符號整數,范圍是 0-255 之間。

得到顏色矩陣后,你可以把它輸出出來,這時你發現輸出的圖像是顛倒的,原因可能是圖像源拍攝的時候本身是倒置的。我們需要設置三維矩陣的轉置,讓第一維和第二維顛倒過來,也就是使用 transpose(1,0,2),將原來的 (0,1,2)順序轉化為 (1,0,2) 順序,即第一維和第二維互換。

最后我們使用 fromarray 函數,它可以通過矩陣來生成圖片,並使用 save 進行保存。

最后得到的分類標識顏色化圖像是這樣的:

剛才我們做的是聚類的可視化。如果我們想要看到對應的原圖,可以將每個簇(即每個類別)的點的 RGB 值設置為該簇質心點的RGB 值,也就是簇內的點的特征均為質心點的特征。

代碼中,可以把范圍為 0-255 的數值投射到 1-256 數值之間,方法是對每個數值進行加 1,你可以自己來運行下:

# -*- coding: utf-8 -*-
# 使用 K-means 對圖像進行聚類,並顯示聚類壓縮后的圖像
import numpy as np
import PIL.Image as image
from sklearn.cluster import KMeans
from sklearn import preprocessing
import matplotlib.image as mpimg
# 加載圖像,並對數據進行規范化
def load_data(filePath):
    # 讀文件
    f = open(filePath,'rb')
    data = []
    # 得到圖像的像素值
    img = image.open(f)
    # 得到圖像尺寸
    width, height = img.size
    for x in range(width):
        for y in range(height):
            # 得到點 (x,y) 的三個通道值
            c1, c2, c3 = img.getpixel((x, y))
            data.append([(c1+1)/256.0, (c2+1)/256.0, (c3+1)/256.0])
    f.close()
    return np.mat(data), width, height
# 加載圖像,得到規范化的結果 imgData,以及圖像尺寸
img, width, height = load_data('./weixin.jpg')
# 用 K-Means 對圖像進行 16 聚類
kmeans =KMeans(n_clusters=16)
label = kmeans.fit_predict(img)
# 將圖像聚類結果,轉化成圖像尺寸的矩陣
label = label.reshape([width, height])
# 創建個新圖像 img,用來保存圖像聚類壓縮后的結果
img=image.new('RGB', (width, height))
for x in range(width):
    for y in range(height):
        c1 = kmeans.cluster_centers_[label[x, y], 0]
        c2 = kmeans.cluster_centers_[label[x, y], 1]
        c3 = kmeans.cluster_centers_[label[x, y], 2]
        img.putpixel((x, y), (int(c1*256)-1, int(c2*256)-1, int(c3*256)-1))
img.save('weixin_new.jpg')

你可以看到我沒有用到 sklearn 自帶的 MinMaxScaler,而是自己寫了 Min-Max 規范化的公式。這樣做的原因是我們知道 RGB 每個通道的數值在 [0,255]之間,所以我們可以用每個通道的數值 +1/256,這樣數值就會在 [0,1] 之間。

對圖像做了 Min-Max 空間變換之后,還可以對其進行反變換,還原出對應原圖的通道值。

對於點 (x,y),我們找到它們所屬的簇 label[x,y],然后得到這個簇的質心特征,用 c1,c2,c3 表示:

c1 = kmeans.cluster_centers_[label[x, y], 0]
c2 = kmeans.cluster_centers_[label[x, y], 1]
c3 = kmeans.cluster_centers_[label[x, y], 2]

因為 c1, c2, c3 對應的是數據規范化的數值,因此我們還需要進行反變換,即:

c1=int(c1*256)-1
c2=int(c2*256)-1
c3=int(c3*256)-1

然后用 img.putpixel 設置點 (x,y) 反變換后得到的特征值。最后用 img.save 保存圖像。

import PIL.Image as image
# 得到圖像的像素值
img = image.open(f)
# 得到圖像尺寸
width, height = img.size

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM