圖像分割就是利用圖像自身的信息,比如顏色、紋理、形狀等特征進行划分,將圖像分割成不同的區域,划分出來的每個區域就相當於是對圖像中的像素進行了聚類。單個區域內的像素之間的相似度大,不同區域間的像素差異性大。這個特性正好符合聚類的特性,所以你可以把圖像分割看成是將圖像中的信息進行聚類。當然聚類只是分割圖像的一種方式,除了聚類,我們還可以基於圖像顏色的閾值進行分割,或者基於圖像邊緣的信息進行分割等。
將微信開屏封面進行分割
聚類的流程和分類差不多,如圖所示:
在准備階段里,我們需要對數據進行加載。因為處理的是圖像信息,我們除了要獲取圖像數據以外,還需要獲取圖像的尺寸和通道數,然后基於圖像中每個通道的數值進行數據規范化。這里我們需要定義個函數 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