1. 程式人生 > >使用K均值演算法進行圖片壓縮

使用K均值演算法進行圖片壓縮

K均值演算法

  上一期介紹了機器學習中的監督式學習,並用了離散迴歸與神經網路模型演算法來解決手寫數字的識別問題。今天我們介紹一種機器學習中的非監督式學習演算法——K均值演算法。

  所謂非監督式學習,是一種與監督式學習相對的演算法歸類,是指樣本並沒有一個與之對應的“標籤”。例如上一期中的識別手寫數字照片的例子,樣本是照片的畫素資料,而標籤則是照片代表的數字。非監督式學習因為沒有這個標籤,因此就沒有對樣本的一個準確的“答案”。非監督式學習主要是用來解決樣本的聚類問題。

  K均值演算法是一種迭代求解的聚類分析演算法,其步驟為:

  1. 隨機選取K個物件作為初始的聚類中心
  2. 計算每個物件與各個聚類中心之間的距離
  3. 把每個物件分配給距離它最近的聚類中心
  4. 將同一聚類中的物件的平均值作為新的聚類中心
  5. 從第2步開始迴圈,直到聚類中心不再變化

程式碼實現

隨機初始化

% 樣本隨機排列
randidx = randperm(size(X, 1));
% 取前K個隨機樣本作為聚類中心
centroids = X(randidx(1:K), :);

  其中X為樣本矩陣,每一行為一個樣本,每一列為樣本的維度,centroids就是隨機出的K個作為聚類中心的樣本矩陣。

分配聚類中心

% 遍歷所有樣本
for i = 1:size(X, 1)
    distance = 9999999999;
    for j = 1:K
        % 距離公式:A(a1, a2)與B(b1, b2)之間的距離為根號下(a1-b1)^2 + (a2 - b2)^2
        dist = sum((X(i, :) - centroids(j, :)) .^ 2);
        if dist < distance
            idx(i) = j;
            distance = dist;
        end
    endfor
endfor

  通過兩層迴圈,第一層為遍歷所有樣本,找到每個樣本所屬的聚類中心。第二層迴圈為遍歷所有聚類中心,計算樣本與聚類中心的距離,找出與類聚中心最小的距離,將該類聚中心作為樣本的類聚中心。

更新聚類中心

  • 方式一:迴圈樣本
count = zeros(K, 1);
for i = 1 : m
    id = idx(i);
    % 累加該聚類中心下的所有樣本
    centroids(id, :) = centroids(id, :) + X(i, :);
    count(id) = count(id) + 1;
endfor
% 均值作為新的聚類中心
centroids = centroids ./ count;
  • 方式二:迴圈類聚中心(效率更高)
for i = 1:K
    % 直接通過mean函式計算均值
    centroids(i,:) = mean(X(idx==i,:),1);
end

  可以看到,通過matlab內建的mean函式,可以更加便捷的計算出矩陣對應行的均值,並且計算過程也會比累加的方式執行更加高效。

步驟演示

  如上圖所示,有一些無標籤的二維座標點。通過一些基本的判斷,我們大致可以將上述的資料分為左上、中右部、左下三個聚類,但是有些中間位置的點可能不太好劃分,如下圖所示。

  但是如何才能讓機器為我們做出分類呢?機器又如何決策這些處於中間位置的點呢?於是我們來執行上述已經寫好的K均值演算法。

  如圖所示,是運行了K均值演算法的第一步:隨機初始化後的聚類。可以看到圖中的綠色、藍色、紅色分佈,因為聚類中心點是隨機的,初始的聚類中心並不正確,於是我們繼續執行K均值演算法的後面步驟。

  以綠色的聚類來舉例,綠色的聚類通過一次均值計算之後,新的聚類中心點上升了許多。根據新的聚類中心點,再計算樣本歸屬時,則會判斷出下方的點已經不再屬於綠色,而屬於藍色,因此就對之前的聚類進行了修正。

  再看之前比較有疑問的綠色聚類與紅色聚類之間的中間點(灰色框中所示),這幾個點在感官上距離兩個聚類都差不多,但根據與聚類中心點的計算對比,從數值上計算得出這幾個點實際上屬於紅色聚類。

  進行多次迴圈之後,聚類中心點不再變化,則說明K均值演算法應用成功,分出了3個聚類並用不同顏色表示。

圖片壓縮

  我們知道,圖片是由許多畫素點組成的,而每個畫素點需要有RGB值。所謂RGB值是一種顏色標準,是通過對紅(R)、綠(G)、藍(B)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的。每個顏色的強度值是0-255,剛好是1個位元組的大小。而3個顏色就是3個位元組的大小,所以每個畫素點所佔大小為3位元組,即24位元(故又稱24位真彩色圖,有1677萬多色)。

  以一張700*700畫素的圖為例,一共有490,000個畫素點,每個畫素點佔24位元,合計11,760,000位元。如果能夠應用K均值演算法來實現圖片的壓縮呢?

  壓縮的前提資料有冗餘,或者近似冗餘。一張圖片中數十萬的畫素點,肯定有許多畫素點的RGB值是比較相似的,即圖片中可能有某些位置都是同一個顏色。那麼就可以應用K均值演算法,將圖片分成許多個聚類,用一張聚類表來儲存每個聚類的顏色值,再用一張畫素表來儲存每個畫素所在的聚類,就可以達到壓縮的目的。

  我選擇了K=16,即有16個聚類,則原圖片從1677萬色變成了16色。聚類表佔用的大小為16 * 24bit,即384位元。16個聚類是2的4次方只需要4個位元就可以儲存聚類資訊,則畫素表的大小為490,000 * 4,即1,960,000。總大小為1,960,000+384=1,960,384位元,通過計算可得(壓縮後大小/壓縮前大小),壓縮率為16.67%。即如果是100KB的圖片,壓縮後的大小隻有16.67KB。

圖片資料處理

% 呼叫imread讀取圖片
A = double(imread('test.png'));
% 將畫素值處理到0-1
A = A / 255;
% 將資料從3維降到2維
img_size = size(A);
X = reshape(A, img_size(1) * img_size(2), 3);

獲取聚類中心

% 隨機初始化
initial_centroids = kMeansInitCentroids(X, K);
% 呼叫之前寫好的K均值演算法
[centroids, idx] = runkMeans(X, initial_centroids, max_iters);

壓縮

% 呼叫之前寫好的“分配聚類中心”
idx = findClosestCentroids(X, centroids);

解壓縮

% 通過聚類表獲取原始畫素值
X_recovered = centroids(idx,:);
% 將資料升至3維
X_recovered = reshape(X_recovered, img_size(1), img_size(2), 3);

壓縮效果展示

  可以看到,壓縮後的圖片仍然具有較清晰的輪廓,只是色彩沒有那麼豐富(只有16種顏色),某些細節有一些失真,但是不影響圖片的整體表達。

總結

  1. K均值演算法是最常用也是最簡單的非監督式學習聚類演算法
  2. 第一步的隨機聚類中心對聚類結果有一定影響,因此可以執行多次以達到比較好的聚類結果
  3. 將聚類演算法的思想作用在圖片上,可以達到圖片壓縮的目的。
  4. 如果想要有更好的圖片質量,可以提高聚類數量K,但這樣會得到較差的壓縮率,需要在實踐中以平衡。