【火爐煉AI】機器學習021-使用K-means進行圖片的向量量化操作

煉丹老頑童發表於2019-02-27

【火爐煉AI】機器學習021-使用K-means進行圖片的向量量化操作

(本文所使用的Python庫和版本號: Python 3.5, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )

前一篇文章我們講解了K-means演算法的定義方法,並用K-means對資料集進行了簡單的聚類分析。此處我們講解使用k-means對圖片進行向量量化操作。

1. 向量量化簡介

向量量化(Vector Quantization, VQ)是一種非常重要的訊號壓縮方法,在圖片處理,語音訊號處理等領域佔據十分重要的地位。

向量量化是一種基於塊編碼規則的有損資料壓縮方法,在圖片壓縮格式JPEG和視訊壓縮格式MPEG-4中都有向量量化這一步,其基本思想是:將若干個標量資料組構成一個向量,然後在向量空間給以整體量化,從而達到壓縮資料的同時但不損失多少資訊。

向量量化實際上是一種逼近,其核心思想和“四捨五入”基本一樣,就是用一個數來代替其他一個數或者一組資料,比如有很多資料(6.235,6.241,6.238,6.238954,6.24205.。。),這些資料如果用四捨五入的方式,都可以得到一個資料6.24,即用一個資料(6.24)來就可以代表很多個資料。

瞭解了這個基本思想,我們可以看下面的一維向量量化的例子:

一維向量量化舉例

在這個數軸上,有很多資料,我們可以用-3來代表所有小於-2的資料,用-1代表-2到0之間的資料,用1代表0到2之間的資料,用3代表大於2的資料,故而整個數軸上的無線多個資料,都可以用這四個資料(-3,-1,1,3)來表示,我們可以對這四個數進行編碼,只需要兩個bit就可以,如(-3=00,-1=01,1=10,3=11),所以這就是1-dimensional, 2-bit VQ,其量化率rate=2bits/dimension.

下面看看稍微複雜一點的二維向量量化的例子:

二維向量量化的例子

由於是二維,故而平面上的任意一個點都可以表示為(x,y)這種座標形式,圖中,我們用藍色實線將整個二維平面劃分為16個區域,故而任意一個資料點都會落到這16個區域的某一個。我們可以用平面上的某些點來代表這個平面區域,故而得到16個紅點,這16個紅點的座標就代表了某一個區域內的所有二維點。

更進一步,我們就用4bit二進位制碼來編碼表示這16個數,故而這個問題是2-dimensional, 4-bit VQ, 其量化率也是rate=2bits/dimension.

此處圖中顯示的紅星,也就是16個代表,被稱為編碼向量(code vectors),而藍色邊界定的區域叫做編碼區域(encoding regions),所有這些編碼向量的集合被稱為碼書(code book), 所有編碼區域的集合稱為空間的劃分(partition of the space).

對於影像而言,可以認為影像中的每個畫素點就是一個資料,用k-means對這些資料進行聚類分析,比如將整幅影像聚為K類,那麼會得到K個不同的質心(關於質心的理解和直觀感受,可以參考我的上一篇文章【火爐煉AI】機器學習020-使用K-means演算法對資料進行聚類分析),或者說通俗一點,可以得到K個不同的資料代表,這些資料代表就可以代表整幅影像中的所有點的畫素值,故而我們只需要知道這K個資料代表就可以了(想想人大代表就明白這個道理了),從而可以極大的減少圖片的儲存空間(比如一張bmp的影像可能有2-3M,而壓縮成jpg後只有幾百K的大小,當然壓縮成jpg的過程還有其他壓縮方式,不僅僅是向量量化,但大體意思相同),當然,這個代表的過程會造成一定的影像畫素失真,失真的程度就是K的個數了。用圖片可以表示為:

向量量化壓縮影像的簡單說明

(以上內容部分來源於部落格向量量化(Vector Quantization)

2. 使用K-means對影像進行向量量化操作

根據上面第一部分對向量量化的介紹,我們可以對某一張圖片進行向量量化壓縮,可以從圖片中提取K個畫素代表,然後用這些代表來表示一張圖片。具體的程式碼為:

from sklearn.cluster import KMeans
# 構建一個函式來完成影像的向量量化操作
def image_VQ(image,K_nums): # 貌似很花時間。。
    # 構建一個KMeans物件
    kmeans=KMeans(n_clusters=K_nums,n_init=4)
    # 用這個KMeans物件來訓練資料集,此處的資料集就是影像
    img_data=image.reshape((-1,1))
    kmeans.fit(img_data)
    centroids=kmeans.cluster_centers_.squeeze() # 每一個類別的質心
    labels=kmeans.labels_ # 每一個類別的標記
    return np.choose(labels,centroids).reshape(image.shape)
複製程式碼

上面我們先建立一個函式來完成影像的向量量化壓縮操作,這個操作首先建立一個Kmeans物件,然後用這個KMeans物件來訓練影像資料,然後提起分類之後的每個類別的質心和標記,並使用這些質心來直接替換原始影像畫素,即可得到壓縮之後的影像。

為了檢視原始影像和壓縮後影像,我們將這兩幅圖都繪製到一行,繪製的函式為:

# 將原圖和壓縮圖都繪製出來,方便對比檢視效果
def plot_imgs(raw_img,VQ_img,compress_rate):
    assert raw_img.ndim==2 and VQ_img.ndim==2, "only plot gray scale images"
    plt.figure(12,figsize=(25,50))
    plt.subplot(121)
    plt.imshow(raw_img,cmap=`gray`)
    plt.title(`raw_img`)
    
    plt.subplot(122)
    plt.imshow(VQ_img,cmap=`gray`)
    plt.title(`VQ_img compress_rate={:.2f}%`.format(compress_rate))
    plt.show()
複製程式碼

為了使用方便,我們可以直接將壓縮影像函式和顯示影像函式封裝到一個更高階的函式中,方便我們直接呼叫和執行,如下所示:

import cv2
def compress_plot_img(img_path,num_bits):
    assert 1<=num_bits<=8, `num_bits must be between 1 and 8`
    K_nums=np.power(2,num_bits)
    
    # 計算壓縮率
    compression_rate=round(100*(8-num_bits)/8,2)
#     print(`compression rate is {:.2f}%`.format(compression_rate))
    
    image=cv2.imread(img_path,0) # 讀取為灰度圖
    VQ_img=image_VQ(image,K_nums)
    plot_imgs(image,VQ_img,compression_rate)
複製程式碼

準備好了各種操作函式之後,我們就可以直接呼叫compress_plot_img()函式來壓縮和顯示影像,下面是採用三種不同的位元位來壓縮得到的影像,可以對比看看效果。

影像壓縮率50%的效果對比圖
影像壓縮率75%的效果對比圖
影像壓縮率87.5%的效果對比圖

########################小**********結###############################

1, 對影像進行向量量化壓縮,其本質就是將影像資料劃分為K個不同類比,這種思想和K-means的思想一致,故而對影像進行向量量化是K-means演算法的一個重要應用。

2, 通過K-means演算法得到影像的K個類別的質心後,就可以用著K個不同質心來代替影像畫素,進而得到壓縮之後的,有少許失真的影像。

3, 從上述三幅圖的比較可以看出,影像壓縮率越大,影像失真的越厲害,最後的位元位為1時的影像可以說就是二值化圖,其畫素值非0即1,非1即0。

#################################################################

注:本部分程式碼已經全部上傳到(我的github)上,歡迎下載。

參考資料:

1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯

相關文章