[OpenCV-Python] OpenCV 中機器學習 部分 VIII

部分 VIII
機器學習

OpenCV-Python 中文教程(搬運)目錄html


46 K 近鄰(k-Nearest Neighbour )


46.1 理解 K 近鄰
目標
  • 本節咱們要理解 k 近鄰(kNN)的基本概念。
原理
  kNN 能夠說是最簡單的監督學習分類器了。想法也很簡單,就是找出測試數據在特徵空間中的最近鄰居。咱們將使用下面的圖片介紹它。python

    Understanding kNN
上圖中的對象能夠分紅兩組,藍色方塊和紅色三角。每一組也能夠稱爲一個 類。咱們能夠把全部的這些對象當作是一個城鎮中房子,而全部的房子分別屬於藍色和紅色家族,而這個城鎮就是所謂的特徵空間。(你能夠把一個特徵空間當作是全部點的投影所在的空間。例如在一個 2D 的座標空間中,每一個數據都兩個特徵 x 座標和 y 座標,你能夠在 2D 座標空間中表示這些數據。若是每一個數據都有 3 個特徵呢,咱們就須要一個 3D 空間。N 個特徵就須要 N 維空間,這個 N 維空間就是特徵空間。在上圖中,咱們能夠認爲是具備兩個特徵色2D 空間)。
如今城鎮中來了一個新人,他的新房子用綠色圓盤表示。咱們要根據他房子的位置把他歸爲藍色家族或紅色家族。咱們把這過程成爲 分類。咱們應該怎麼作呢?由於咱們正在學習看 kNN,那咱們就使用一下這個算法吧。
一個方法就是查看他最近的鄰居屬於那個家族,從圖像中咱們知道最近的是紅色三角家族。因此他被分到紅色家族。這種方法被稱爲簡單 近鄰,由於分類僅僅決定與它最近的鄰居。
可是這裏還有一個問題。紅色三角多是最近的,但若是他周圍還有不少藍色方塊怎麼辦呢?此時藍色方塊對局部的影響應該大於紅色三角。因此僅僅檢測最近的一個鄰居是不足的。因此咱們檢測 k 個最近鄰居。誰在這 k 個鄰居中佔據多數,那新的成員就屬於誰那一類。若是 k 等於 3,也就是在上面圖像中檢測 3 個最近的鄰居。他有兩個紅的和一個藍的鄰居,因此他仍是屬於紅色家族。可是若是 k 等於 7 呢?他有 5 個藍色和 2 個紅色鄰居,如今他就會被分到藍色家族了。k 的取值對結果影響很是大。更有趣的是,若是 k 等於 4呢?兩個紅兩個藍。這是一個死結。因此 k 的取值最好爲奇數。這中根據 k 個最近鄰居進行分類的方法被稱爲 kNN。
在 kNN 中咱們考慮了 k 個最近鄰居,可是咱們給了這些鄰居相等的權重,這樣作公平嗎?以 k 等於 4 爲例,咱們說她是一個死結。可是兩個紅色三角比兩個藍色方塊距離新成員更近一些。因此他更應該被分爲紅色家族。那用數學應該如何表示呢?咱們要根據每一個房子與新房子的距離對每一個房子賦予不一樣的權重。距離近的具備更高的權重,距離遠的權重更低。而後咱們根據兩個家族的權重和來判斷新房子的歸屬,誰的權重大就屬於誰。這被稱爲 修改過的kNN。
那這裏面些是重要的呢?
• 咱們須要整個城鎮中每一個房子的信息。由於咱們要測量新來者到全部現存房子的距離,並在其中找到最近的。若是那裏有不少房子,就要佔用很大的內存和更多的計算時間。
• 訓練和處理幾乎不須要時間。
如今咱們看看 OpenCV 中的 kNN。git


46.1.1 OpenCV 中的 kNN
  咱們這裏來舉一個簡單的例子,和上面同樣有兩個類。下一節咱們會有一個更好的例子。
這裏咱們將紅色家族標記爲 Class-0,藍色家族標記爲 Class-1。還要再建立 25 個訓練數據,把它們非別標記爲 Class-0 或者 Class-1。Numpy中隨機數產生器能夠幫助咱們完成這個任務。
而後藉助 Matplotlib 將這些點繪製出來。紅色家族顯示爲紅色三角藍色家族顯示爲藍色方塊。算法

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Feature set containing (x,y) values of 25 known/training data
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)

# Labels each one either Red or Blue with numbers 0 and 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)

# Take Red families and plot them
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')

# Take Blue families and plot them
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')

plt.show()

你可能會獲得一個與上面相似的圖形,但不會徹底同樣,由於你使用了隨機數產生器,每次你運行代碼都會獲得不一樣的結果。
下面就是 kNN 算法分類器的初始化,咱們要傳入一個訓練數據集,以及與訓練數據對應的分類來訓練 kNN 分類器(構建搜索樹)。
最後要使用 OpenCV 中的 kNN 分類器,咱們給它一個測試數據,讓它來進行分類。在使用 kNN 以前,咱們應該對測試數據有所瞭解。咱們的數據應該是大小爲數據數目乘以特徵數目的浮點性數組。而後咱們就能夠經過計算找到測試數據最近的鄰居了。咱們能夠設置返回的最近鄰居的數目。返回值包括:
  1. 由 kNN 算法計算獲得的測試數據的類別標誌(0 或 1)。若是你想使用最近鄰算法,只須要將 k 設置爲 1,k 就是最近鄰的數目。
  2. k 個最近鄰居的類別標誌。
  3. 每一個最近鄰居到測試數據的距離。
讓咱們看看它是如何工做的。測試數據被標記爲綠色。數組

newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')

knn = cv2.KNearest()
knn.train(trainData,responses)
ret, results, neighbours ,dist = knn.find_nearest(newcomer, 3)

print "result: ", results,"\n"
print "neighbours: ", neighbours,"\n"
print "distance: ", dist

plt.show()

下面是我獲得的結果:app

result:  [[ 1.]]
neighbours:  [[ 1.  1.  1.]]
distance:  [[ 53.  58.  61.]]

 


這說明咱們的測試數據有 3 個鄰居,他們都是藍色,因此它被分爲藍色家族。結果很明顯,以下圖所示:dom

    kNN Demo
若是咱們有大量的數據要進行測試,能夠直接傳入一個數組。對應的結果一樣也是數組。機器學習

# 10 new comers
newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
ret, results,neighbours,dist = knn.find_nearest(newcomer, 3)
# The results also will contain 10 labels.

 


46.2 使用 kNN 對手寫數字 OCR


目標
  • 要根據咱們掌握的 kNN 知識建立一個基本的 OCR 程序
  • 使用 OpenCV 自帶的手寫數字和字母數據測試咱們的程序
46.2.1 手寫數字的 OCR
  咱們的目的是建立一個能夠對手寫數字進行識別的程序。爲了達到這個目的咱們須要訓練數據和測試數據。OpenCV 安裝包中有一副圖片(/samples/python2/data/digits.png), 其中有 5000 個手寫數字(每一個數字重複 500遍)。每一個數字是一個 20x20 的小圖。因此第一步就是將這個圖像分割成 5000個不一樣的數字。咱們在將拆分後的每個數字的圖像重排成一行含有 400 個像素點的新圖像。這個就是咱們的特徵集,全部像素的灰度值。這是咱們能建立的最簡單的特徵集。咱們使用每一個數字的前 250 個樣本作訓練數據,剩餘的250 個作測試數據。讓咱們先準備一下:ide

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('digits.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# Now we split the image to 5000 cells, each 20x20 size
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]

# Make it into a Numpy array. It size will be (50,100,20,20)
x = np.array(cells)

# Now we prepare train_data and test_data.
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)

# Create labels for train and test data
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()

# Initiate kNN, train the data, then test it with test data for k=1
knn = cv2.KNearest()
knn.train(train,train_labels)
ret,result,neighbours,dist = knn.find_nearest(test,k=5)

# Now we check the accuracy of classification
# For that, compare the result with test_labels and check which are wrong
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print accuracy

如今最基本的 OCR 程序已經準備好了,這個示例中咱們獲得的準確率爲91%。改善準確度的一個辦法是提供更多的訓練數據,尤爲是判斷錯誤的那些數字。爲了不每次運行程序都要準備和訓練分類器,咱們最好把它保留,這樣在下次運行是時,只須要從文件中讀取這些數據開始進行分類就能夠了。
Numpy 函數 np.savetxt,np.load 等能夠幫助咱們搞定這些。函數

# save the data
np.savez('knn_data.npz',train=train, train_labels=train_labels)

# Now load the data
with np.load('knn_data.npz') as data:
    print data.files
    train = data['train']
    train_labels = data['train_labels']

在個人系統中,佔用的空間大概爲 4.4M。因爲咱們如今使用灰度值(unint8)做爲特徵,在保存以前最好先把這些數據裝換成 np.uint8 格式,這樣就只須要佔用 1.1M 的空間。在加載數據時再轉會到 float32。


46.2.2 英文字母的 OCR
  接下來咱們來作英文字母的 OCR。和上面作法同樣,可是數據和特徵集有一些不一樣。如今 OpenCV 給出的不是圖片了,而是一個數據文件(/samples/cpp/letter-recognition.data)。若是打開它的話,你會發現它有 20000 行,第同樣看上去就像是垃圾。實際上每一行的第一列是咱們的一個字母標記。接下來的 16 個數字是它的不一樣特徵。這些特徵來源於UCI Machine LearningRepository。你能夠在此頁找到更多相關信息。
有 20000 個樣本可使用,咱們取前 10000 個做爲訓練樣本,剩下的10000 個做爲測試樣本。咱們應在先把字母表轉換成 asc 碼,由於咱們不正直接處理字母。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Load the data, converters convert the letter to a number
data= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
                    converters= {0: lambda ch: ord(ch)-ord('A')})

# split the data to two, 10000 each for train and test
train, test = np.vsplit(data,2)

# split trainData and testData to features and responses
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])

# Initiate the kNN, classify, measure accuracy.
knn = cv2.KNearest()
knn.train(trainData, responses)
ret, result, neighbours, dist = knn.find_nearest(testData, k=5)

correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print accuracy

準確率達到了 93.22%。一樣你能夠經過增長訓練樣本的數量來提升準確率。

 

47 支持向量機


47.1 理解 SVM
目標
  • 對 SVM 有一個直觀理解
原理
47.1.1 線性數據分割
  以下圖所示,其中含有兩類數據,紅的和藍的。若是是使用 kNN,對於一個測試數據咱們要測量它到每個樣本的距離,從而根據最近鄰居分類。測量全部的距離須要足夠的時間,而且須要大量的內存存儲訓練樣本。可是分類下圖所示的數據真的須要佔用這麼多資源嗎?

    Test Data
咱們在考慮另一個想法。咱們找到了一條直線,f (x) = ax 1 + bx 2 + c,它能夠將全部的數據分割到兩個區域。當咱們拿到一個測試數據 X 時,咱們只須要把它代入 f (x)。若是 |f (X)| > 0,它就屬於藍色組,不然就屬於紅色組。
咱們把這條線稱爲 決定邊界(Decision_Boundary)。很簡單並且內存使用效率也很高。這種使用一條直線(或者是高位空間種的超平面)上述數據分紅兩組的方法成爲 線性分割。

    Decision Boundary
從上圖中咱們看到有不少條直線能夠將數據分爲藍紅兩組,那一條直線是最好的呢?直覺上講這條直線應該是與兩組數據的距離越遠越好。爲何呢?
由於測試數據可能有噪音影響(真實數據 + 噪聲)。這些數據不該該影響分類的準確性。因此這條距離遠的直線抗噪聲能力也就最強。因此 SVM 要作就是找到一條直線,並使這條直線到(訓練樣本)各組數據的最短距離最大。下圖中加粗的直線通過中心。
要找到決定邊界,就須要使用訓練數據。咱們須要全部的訓練數據嗎?不,只須要那些靠近邊界的數據,如上圖中一個藍色的圓盤和兩個紅色的方塊。咱們叫他們 支持向量,通過他們的直線叫作 支持平面。有了這些數據就足以找到決定邊界了。咱們擔憂全部的數據。這對於數據簡化有幫助。
We need not worry about all the data. It helps in data reduction.
到底發生了什麼呢?首先咱們找到了分別表明兩組數據的超平面。例如,藍色數據能夠用 w^Tx+b_0 > 1 表示,而紅色數據能夠用 w^Tx+b_0 < -1 表示,ω 叫作 權重向量( w=[w_1, w_2,..., w_n]),x 爲 特徵向量(x = [x_1,x_2,..., x_n])。b 0 被成爲 bias(截距?)。權重向量決定了決定邊界的走向,而 bias 點決定了它(決定邊界)的位置。決定邊界被定義爲這兩個超平面的中間線(平面),表達式爲 w^Tx+b_0 = 0。從支持向量到決定邊界的最短距離爲 distance_{support \, vectors}=\frac{1}{||w||}
邊緣長度爲這個距離的兩倍,咱們須要使這個邊緣長度最大。咱們要建立一個新的函數 L(w, b_0)並使它的值最小:

      \min_{w, b_0} L(w, b_0) = \frac{1}{2}||w||^2 \; \text{subject to} \; t_i(w^Tx+b_0) \geq 1 \; \forall i
其中 t i 是每一組的標記,t_i \in [-1,1].。


47.1.2 非線性數據分割
  想象一下,若是一組數據不能被一條直線分爲兩組怎麼辦?例如,在一維空間中 X 類包含的數據點有(-3,3),O 類包含的數據點有(-1,1)。很明顯不可能使用線性分割將 X 和 O 分開。可是有一個方法能夠幫咱們解決這個問題。使用函數 f(x) = x^2 對這組數據進行映射,獲得的 X 爲 9,O 爲 1,這時就可使用線性分割了。
或者咱們也能夠把一維數據轉換成兩維數據。咱們可使用函數f(x)=(x,x^2)對數據進行映射。這樣 X 就變成了(-3,9)和(3,9)而 O 就變成了(-1,1)和(1,1)。一樣能夠線性分割,簡單來講就是在低維空間不能線性分割的數據在高維空間頗有可能能夠線性分割。
一般咱們能夠將 d 維數據映射到 D 維數據來檢測是否能夠線性分割(D>d)。這種想法能夠幫助咱們經過對低維輸入(特徵)空間的計算來得到高維空間的點積。咱們能夠用下面的例子說明。
假設咱們有二維空間的兩個點:p = (p 1 ,p 2 ) 和 q = (q 1 ,q 2 )。用 Ø 表示映射函數,它能夠按以下方式將二維的點映射到三維空間中:

      \phi (p) = (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2)
\phi (q) = (q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2)
咱們要定義一個核函數 K (p,q),它能夠用來計算兩個點的內積,以下所示這說明三維空間中的內積能夠經過計算二維空間中內積的平方來得到。這能夠擴展到更高維的空間。因此根據低維的數據來計算它們的高維特徵。在進行完映射後,咱們就獲得了一個高維空間數據。

      K(p,q)  = \phi(p).\phi(q) &= \phi(p)^T , \phi(q) \\
                          &= (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2).(q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2) \\
                          &= p_{1}^2 q_{1}^2 + p_{2}^2 q_{2}^2 + 2 p_1 q_1 p_2 q_2 \\
                          &= (p_1 q_1 + p_2 q_2)^2 \\
          \phi(p).\phi(q) &= (p.q)^2
除了上面的這些概念以外,還有一個問題須要解決,那就是分類錯誤。僅僅找到具備最大邊緣的決定邊界是不夠的。咱們還須要考慮錯誤分類帶來的偏差。有時咱們找到的決定邊界的邊緣可能不是最大的可是錯誤分類是最少的。因此咱們須要對咱們的模型進行修正來找到一個更好的決定邊界:最大的邊緣,最小的錯誤分類。評判標準就被修改成:

      min \; ||w||^2 + C(distance \; of \; misclassified \; samples \; to \; their \; correct \; regions)

下圖顯示這個概念。對於訓練數據的每個樣本又增長了一個參數 ξ i 。它表示訓練樣本到他們所屬類(實際所屬類)的超平面的距離。對於那些分類正確的樣本這個參數爲 0,由於它們會落在它們的支持平面上。

    Misclassification
如今新的最優化問題就變成了:

      \min_{w, b_{0}} L(w,b_0) = ||w||^{2} + C \sum_{i} {\xi_{i}} \text{ subject to } y_{i}(w^{T} x_{i} + b_{0}) \geq 1 - \xi_{i} \text{ and } \xi_{i} \geq 0 \text{ } \forall i
參數 C 的取值應該如何選擇呢?很明顯應該取決於你的訓練數據。雖然沒有一個統一的答案,可是在選取 C 的取值時咱們仍是應該考慮一下下面的規則:
  • 若是 C 的取值比較大,錯誤分類會減小,可是邊緣也會減少。其實就是錯誤分類的代價比較高,懲罰比較大。(在數據噪聲很小時咱們能夠選取較大的 C 值。)
  • 若是 C 的取值比較小,邊緣會比較大,但錯誤分類的數量會升高。其實就是錯誤分類的代價比較低,懲罰很小。整個優化過程就是爲了找到一個具備最大邊緣的超平面對數據進行分類。(若是數據噪聲比較大時,應該考慮)

47.2 使用 SVM 進行手寫數據 OCR
目標
本節咱們仍是要進行手寫數據的 OCR,但此次咱們使用的是 SVM 而不是 kNN。


手寫數字的 OCR
  在 kNN 中咱們直接使用像素的灰度值做爲特徵向量。此次咱們要使用方向梯度直方圖Histogram of Oriented Gradients (HOG)做爲特徵向量。
在計算 HOG 前咱們使用圖片的二階矩對其進行抗扭斜(deskew)處理。
因此咱們首先要定義一個函數 deskew(),它能夠對一個圖像進行抗扭斜處理。下面就是 deskew() 函數:

def deskew(img):
    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv2.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
    return img

下圖顯示了對含有數字 0 的圖片進行抗扭斜處理後的效果。左側是原始圖像,右側是處理後的結果。

    Deskew
接下來咱們要計算圖像的 HOG 描述符,建立一個函數 hog()。爲此咱們計算圖像 X 方向和 Y 方向的 Sobel 導數。而後計算獲得每一個像素的梯度的方向和大小。把這個梯度轉換成 16 位的整數。將圖像分爲 4 個小的方塊,對每個小方塊計算它們的朝向直方圖(16 個 bin),使用梯度的大小作權重。這樣每個小方塊都會獲得一個含有 16 個成員的向量。4 個小方塊的 4 個向量就組成了這個圖像的特徵向量(包含 64 個成員)。這就是咱們要訓練數據的特徵向量。

def hog(img):
    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
    mag, ang = cv2.cartToPolar(gx, gy)

    # quantizing binvalues in (0...16)
    bins = np.int32(bin_n*ang/(2*np.pi))

    # Divide to 4 sub-squares
    bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
    mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)
    return hist

最後,和前面同樣,咱們將大圖分割成小圖。使用每一個數字的前 250 個做
爲訓練數據,後 250 個做爲測試數據。所有代碼以下所示:

import cv2
import numpy as np

SZ=20
bin_n = 16 # Number of bins

svm_params = dict( kernel_type = cv2.SVM_LINEAR,
                    svm_type = cv2.SVM_C_SVC,
                    C=2.67, gamma=5.383 )

affine_flags = cv2.WARP_INVERSE_MAP|cv2.INTER_LINEAR

def deskew(img):
    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv2.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
    return img

def hog(img):
    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
    mag, ang = cv2.cartToPolar(gx, gy)
    bins = np.int32(bin_n*ang/(2*np.pi))    # quantizing binvalues in (0...16)
    bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
    mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)     # hist is a 64 bit vector
    return hist

img = cv2.imread('digits.png',0)

cells = [np.hsplit(row,100) for row in np.vsplit(img,50)]

# First half is trainData, remaining is testData
train_cells = [ i[:50] for i in cells ]
test_cells = [ i[50:] for i in cells]

######     Now training      ########################

deskewed = [map(deskew,row) for row in train_cells]
hogdata = [map(hog,row) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1,64)
responses = np.float32(np.repeat(np.arange(10),250)[:,np.newaxis])

svm = cv2.SVM()
svm.train(trainData,responses, params=svm_params)
svm.save('svm_data.dat')

######     Now testing      ########################

deskewed = [map(deskew,row) for row in test_cells]
hogdata = [map(hog,row) for row in deskewed]
testData = np.float32(hogdata).reshape(-1,bin_n*4)
result = svm.predict_all(testData)

#######   Check Accuracy   ########################
mask = result==responses
correct = np.count_nonzero(mask)
print correct*100.0/result.size

準確率達到了 94%。你能夠嘗試一下不一樣的參數值,看看能不能達到更高的準確率。或者也能夠讀一下這個領域的文章並用代碼實現它。



48 K 值聚類


48.1 理解 K 值聚類
目標
  • 本節咱們要學習 K 值聚類的概念以及它是如何工做的。
原理
  我將用一個最經常使用的例子來給你們介紹 K 值聚類。


48.1.1 T 恤大小問題
  話說有一個公司要生產一批新的 T 恤。很明顯他們要生產不一樣大小的 T 恤來知足不一樣顧客的需求。因此這個公司收集了不少人的身高和體重信息,並把這些數據繪製在圖上,以下所示:

    T-shirt Problem

確定不能把每一個大小的 T 恤都生產出來,因此他們把全部的人分爲三組:小,中,大,這三組要覆蓋全部的人。咱們可使用 K 值聚類的方法將全部人分爲 3 組,這個算法能夠找到一個最好的分法,並能覆蓋全部人。若是不能覆蓋所有人的話,公司就只能把這些人分爲更多的組,多是 4 個或 5 個甚至更多。以下圖:

    People Grouped into Different Sizes

48.1.2 它是如何工做的?
  這個算法是一個迭代過程,咱們會藉助圖片逐步介紹它。
考慮下面這組數據(你也能夠把它當成 T 恤問題),咱們須要把他們分紅兩組。

    Test Data
  第一步:隨機選取兩個重心點,C 1 和 C 2 (有時能夠選取數據中的兩個點做爲起始重心)。
  第二步:計算每一個點到這兩個重心點的距離,若是距離 C 1 比較近就標記爲 0,若是距離 C 2 比較近就標記爲 1。(若是有更多的重心點,能夠標記爲「2」,「3」等)
在咱們的例子中咱們把屬於 0 的標記爲紅色,屬於 1 的標記爲藍色。咱們就會獲得下面這幅圖。
  第三步:從新計算全部藍色點的重心,和全部紅色點的重心,並以這兩個點更新重心點的位置。(圖片只是爲了演示說明而已,並不表明實際數據)重複步驟 2,更新全部的點標記。
咱們就會獲得下面的圖:
    Initial Centroid Selection and Data Collection
繼續迭代步驟 2 和 3,直到兩個重心點的位置穩定下來。(固然也能夠經過設置迭代次數,或者設置重心移動距離的閾值來終止迭代。)。此時這些點到它們相應重心的距離之和最小此時這些點到它們相應重心的距離之和最小。簡單來講,C 1 到紅色點的距離與 C 2 到藍色點的距離之和最小。

    minimize \;\bigg[J = \sum_{All\: Red_Points}distance(C1,Red\_Point) + \sum_{All\: Blue\_Points}distance(C2,Blue\_Point)\bigg]
最終結果以下圖所示:

    New Centroid Calculated and Data Re-laballed
這就是對 K 值聚類的一個直觀解釋。要想知道更多細節和數據解釋,你應該讀一本關於機器學習的教科書或者參考更多資源中的連接。這只是 K 值聚類的基礎。如今對這個算法有不少改進,好比:如何選取好的起始重心點,怎樣加速迭代過程等。
更多資源
1. Machine Learning Course, Video lectures by Prof. Andrew Ng
(Some of the images are taken from this)



48.2 OpenCV 中的 K 值聚類
目標
  • 學習使用 OpenCV 中的函數 cv2.kmeans() 對數據進行分類


48.2.1 理解函數的參數
輸入參數
理解函數的參數
輸入參數
  1. samples: 應該是 np.float32 類型的數據,每一個特徵應該放在一列。
  2. nclusters(K): 聚類的最終數目。
  3. criteria: 終止迭代的條件。當條件知足時,算法的迭代終止。它應該是一個含有 3 個成員的元組,它們是(typw,max_iter,epsilon):
    • type 終止的類型:有以下三種選擇:
      – cv2.TERM_CRITERIA_EPS 只有精確度 epsilon 知足是中止迭代。
      – cv2.TERM_CRITERIA_MAX_ITER 當迭代次數超過閾值時中止迭代。
      – cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER上面的任何一個條件知足時中止迭代。
    • max_iter 表示最大迭代次數。
    • epsilon 精確度閾值。
  4. attempts: 使用不一樣的起始標記來執行算法的次數。算法會返回緊密度最好的標記。緊密度也會做爲輸出被返回。
  5. flags:用來設置如何選擇起始重心。一般咱們有兩個選擇:cv2.KMEANS_PP_CENTERS和 cv2.KMEANS_RANDOM_CENTERS。
輸出參數
  1. compactness:緊密度,返回每一個點到相應重心的距離的平方和。
  2. labels:標誌數組(與上一節提到的代碼相同),每一個成員被標記爲 0,1等
  3. centers:由聚類的中心組成的數組。
如今咱們用 3 個例子來演示如何使用 K 值聚類。

48.2.2 僅有一個特徵的數據
  假設咱們有一組數據,每一個數據只有一個特徵(1 維)。例如前面的 T 恤問題,咱們只使用人們的身高來決定 T 恤的大小。
咱們先來產生一些隨機數據,並使用 Matplotlib 將它們繪製出來。

import numpy as np
import cv2
from matplotlib import pyplot as plt

x = np.random.randint(25,100,25)
y = np.random.randint(175,255,25)
z = np.hstack((x,y))
z = z.reshape((50,1))
z = np.float32(z)
plt.hist(z,256,[0,256]),plt.show()

如今咱們有一個長度爲 50,取值範圍爲 0 到 255 的向量 z。我已經將向量 z 進行了重排,將它變成了一個列向量。當每一個數據含有多個特徵是這會頗有用。而後咱們數據類型轉換成 np.float32。
咱們獲得下圖:

    Test Data
如今咱們使用 KMeans 函數。在這以前咱們應該首先設置好終止條件。個人終止條件是:算法執行 10 次迭代或者精確度 epsilon = 1.0。

# Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)

# Set flags (Just to avoid line break in the code)
flags = cv2.KMEANS_RANDOM_CENTERS

# Apply KMeans
compactness,labels,centers = cv2.kmeans(z,2,None,criteria,10,flags)

返回值有緊密度(compactness), 標誌和中心。在本例中個人到的中心是 60 和 207。標誌的數目與測試數據的多少是相同的,每一個數據都會被標記上「0」,「1」等。這取決與它們的中心是什麼。如今咱們能夠根據它們的標誌將把數據分兩組。

A = z[labels==0]
B = z[labels==1]

 


如今將 A 組數用紅色表示,將 B 組數據用藍色表示,重心用黃色表示。

# Now plot 'A' in red, 'B' in blue, 'centers' in yellow
plt.hist(A,256,[0,256],color = 'r')
plt.hist(B,256,[0,256],color = 'b')
plt.hist(centers,32,[0,256],color = 'y')
plt.show()

下面就是結果:

    Result of KMeans Clustering

含有多個特徵的數據
在前面的 T 恤例子中咱們只考慮了身高,如今咱們也把體重考慮進去,也就是兩個特徵。
在前一節咱們的數據是一個單列向量。每個特徵被排列成一列,每一行對應一個測試樣本。
在本例中咱們的測試數據適應 50x2 的向量,其中包含 50 我的的身高和體重。第一列對應與身高,第二列對應與體重。第一行包含兩個元素,第一個是第一我的的身高,第二個是第一我的的體重。剩下的行對應與其餘人的身高和體重。以下圖所示:

    Feature Representation
如今咱們來編寫代碼:

import numpy as np
import cv2
from matplotlib import pyplot as plt

X = np.random.randint(25,50,(25,2))
Y = np.random.randint(60,85,(25,2))
Z = np.vstack((X,Y))

# convert to np.float32
Z = np.float32(Z)

# define criteria and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret,label,center=cv2.kmeans(Z,2,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

# Now separate the data, Note the flatten()
A = Z[label.ravel()==0]
B = Z[label.ravel()==1]

# Plot the data
plt.scatter(A[:,0],A[:,1])
plt.scatter(B[:,0],B[:,1],c = 'r')
plt.scatter(center[:,0],center[:,1],s = 80,c = 'y', marker = 's')
plt.xlabel('Height'),plt.ylabel('Weight')
plt.show()

下面是我獲得的結果:

    Result of KMeans Clustering

48.2.3 顏色量化
  顏色量化就是減小圖片中顏色數目的一個過程。爲何要減小圖片中的顏色呢?減小內存消耗!有些設備的資源有限,只能顯示不多的顏色。在這種狀況下就須要進行顏色量化。咱們使用 K 值聚類的方法來進行顏色量化。沒有什麼新的知識須要介紹了。如今有 3 個特徵:R,G,B。因此咱們須要把圖片數據變造成 Mx3(M 是圖片中像素點的數目)的向量。聚類完成後,咱們用聚類中心值替換與其同組的像素值,這樣結果圖片就只含有指定數目的顏色了。下面是代碼:

import numpy as np
import cv2

img = cv2.imread('home.jpg')
Z = img.reshape((-1,3))

# convert to np.float32
Z = np.float32(Z)

# define criteria, number of clusters(K) and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))

cv2.imshow('res2',res2)
cv2.waitKey(0)
cv2.destroyAllWindows()

下面是 K=8 的結果:

    Color Quantization

相關文章
相關標籤/搜索