Python OpenCV實戰之與機器學習的碰撞

0. 前言

機器學習是人工智能的子集,它為計算機以及其它具有計算能力的系統提供自動預測或決策的能力,諸如虛擬助理、車牌識別系統、智能推薦系統等機器學習應用程序給我們的日常生活帶來瞭便捷的體驗。機器學習的蓬勃發展,得益於以下三個關鍵因素:1) 海量數據集;2) 算法的快速發展;3) 計算機硬件的發展。在本文中,我們將學習 OpenCV 提供的常見機器學習算法和技術,用於解決計算機視覺項目中的實際問題,例如分類和回歸問題。

1. 機器學習簡介

機器學習是利用計算機編程,從歷史數據中學習以對新數據進行預測的過程。機器學習可以分為三類——監督學習、無監督學習和半監督學習,這些技術所包含的算法如下圖所示:

1.1 監督學習

監督學習使用的樣本都有相應的期望輸出值(或稱為樣本標簽),由於我們知道每個訓練數據的正確標簽,因此監督學習可以根據預測與相應的期望輸出之間的差異來校正這些預測。基於這些校準,算法可以從錯誤中學習以調整其內部參數,以擬合出最接近樣本集合與相應的期望輸出之間的函數。

監督學習問題可以進一步分為以下分類和回歸:

  1. 分類:當輸出變量是類別時,可以認為該問題是分類問題。在分類問題中,算法將輸入映射到輸出標簽。
  2. 回歸:當輸出變量為實數時,在回歸問題中,算法將輸入映射到連續的實數輸出。

在監督學習中,主要需要考慮以下問題:

偏差-方差的權衡 (Bias-variance trade-off):模型對數據欠擬合的模型具有高偏差,而對數據過擬合的模型具有高方差:

偏差是由於學習算法中的錯誤假設而產生的誤差,可以定義為模型的預測與期望的正確值之間的差異。具有高偏差的模型無法找到數據中的所有模式(欠擬合),因此它不能很好地擬合訓練集,也不會很好地擬合測試集。

方差定義為算法學習錯誤事物的傾向,其會同時擬合數據中的真實信號以及噪聲。因此,具有高方差的模型(過擬合)非常適合訓練集,但無法泛化到測試集,因為它學習瞭數據中的噪聲。

模型復雜度和訓練數據量:模型復雜度是指機器學習算法試圖的復雜度。模型的復雜度通常由訓練數據決定:例如,如果使用少量數據來訓練模型,那麼低復雜度的模型更可取,這是因為高復雜度的模型會導致過擬合。

輸入空間的維度:在處理高維空間數據時,學習可能非常困難,因為會有許多額外的特征會混淆學習過程,也稱為維度災難。因此,在處理高維空間數據時,常見的方法是修改學習算法,使其具有高偏差和低方差。

1.2 無監督學習

在無監督學習中,樣本集合缺少每個樣本對應的輸出值(樣本集合沒有被標記、分類或歸類)。無監督學習的目標是對樣本集合中的結構或分佈進行建模和推斷。因此,在無監督學習中,算法利用數據中進行推斷,並試圖揭示其中的隱藏分佈信息。聚類和降維是無監督學習中最常用的兩種算法。

1.3 半監督學習

半監督學習可以看作是監督學習和無監督學習之間的折衷,因為它同時使用標記和未標記的數據進行訓練。許多現實世界的機器學習問題可以歸類為半監督,因為正確標記所有數據可能非常困難或耗時,而未標記的數據更容易收集。

2. K均值 (K-Means) 聚類

OpenCV 提供瞭 cv2.kmeans() 函數實現 K-Means 聚類算法,該算法找到簇的中心並將輸入樣本分組到簇周圍。

K-Means 聚類算法的目標是將 n 個樣本劃分(聚類)為 K 個簇,其中每個樣本都屬於具有最近均值的簇,cv2.kmeans() 函數用法如下:

retval, bestLabels, centers=cv.kmeans(data, K, bestLabels, criteria, attempts, flags[, centers])

data 表示用於聚類的輸入數據,它是 np.float32 數據類型,每一列包含一個特征;K 指定最後需要的簇數;算法終止標準由 criteria 參數指定,該參數設置最大迭代次數或所需精度,當滿足這些標準時,算法終止。criteria 是具有三個參數 (type, max_item, epsilon) 的元組:

criteria 參數的標準示例如下:

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

上述語句表示,最大迭代次數設置為 20 (max_iterm = 20),所需精度為 1.0 (epsilon = 1.0)。

attempts 參數指定使用不同的初始標簽執行算法的次數。flags 參數指定初始化簇中心的方法,其可選值包括:cv2.KMEANS_RANDOM_CENTERS 每次選擇隨機初始化簇中心;cv2.KMEANS_PP_CENTERS 使用 Arthur 等人提出的 K-Means++ 中心初始化。

cv2.kmeans() 返回以下內容:

返回值 解釋
bestLabels 整數數組,用於存儲每個樣本的簇索引
center 包含每個簇中心的數組
compactness 每個點到其簇中心的距離平方和

2.1 K-Means 聚類示例

作為示例,我們將使用 K-Means 聚類算法對一組 2D 點進行聚類。這組 2D 點由 240 個點組成,使用兩個特征進行瞭描述:

# 2D數據
data = np.float32(np.vstack((np.random.randint(0, 50, (80, 2)), np.random.randint(40, 90, (80, 2)), np.random.randint(70, 110, (80, 2)))))
# 可視化
plt.scatter(data[:, 0], data[:, 1], c='c')
plt.show()

如上圖所示,數據將作為聚類算法的輸入,每個數據點有兩個特征對應於 (x, y) 坐標,例如,這些坐標可以表示 240 人人的身高和體重,而 K-Means 聚類算法用於決定衣服的尺寸(例如 K=3,則相應表示尺寸為 S、M 或 L)。

接下來,我們將數據劃分為 2 個簇。第一步是定義算法終止標準,將最大迭代次數設置為 20 (max_iterm = 20),epsilon 設置為 1.0 (epsilon = 1.0):

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

然後調用 cv2.kmeans() 函數應用 K-Means 算法:

ret, label, center = cv2.kmeans(data, 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

由於返回值 label 存儲每個樣本的聚類索引,因此,我們可以根據 label 將數據拆分為不同的集群:

A = data[label.ravel() == 0]
B = data[label.ravel() == 1]

最後繪制 A 和 B 以及聚類前後的數據,以便更好地理解聚類過程:

fig = plt.figure(figsize=(12, 6))
plt.suptitle("K-means clustering algorithm", fontsize=14,
fontweight='bold')
# 繪制原始數據
ax = plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1], c='c')
plt.title("data")
# 繪制聚類後的數據和簇中心
ax = plt.subplot(1, 2, 2)
plt.scatter(A[:, 0], A[:, 1], c='b')
plt.scatter(B[:, 0], B[:, 1], c='g')
plt.scatter(center[:, 0], center[:, 1], s=100, c='m', marker='s')
plt.title("clustered data and centroids (K = 2)")
plt.show()

接下來,我們修改參數 K 進行聚類並進行相應的可視化。例如需要將數據分為三個簇,則首先應用相同的過程對數據進行聚類,隻需要修改參數 (K=3) 將數據分為 3 個簇:

ret, label, center = cv2.kmeans(data, 3, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

然後,當使用標簽輸出分離數據時,將數據分為三組:

A = data[label.ravel() == 0]
B = data[label.ravel() == 1]
C = data[label.ravel() == 2]

最後一步是顯示 A 、 B 和 C ,以及簇中心和訓練數據:

fig = plt.figure(figsize=(12, 6))
plt.suptitle("K-means clustering algorithm", fontsize=14,
fontweight='bold')
# 繪制原始數據
ax = plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1], c='c')
plt.title("data")
# 繪制聚類後的數據和簇中心
ax = plt.subplot(1, 2, 2)
plt.scatter(A[:, 0], A[:, 1], c='b')
plt.scatter(B[:, 0], B[:, 1], c='g')
plt.scatter(C[:, 0], C[:, 1], c='r')
plt.scatter(center[:, 0], center[:, 1], s=100, c='m', marker='s')
plt.title("clustered data and centroids (K = 3)")
plt.show()

我們也可以將簇數設置為 4,觀察算法運行結果:

ret, label, center = cv2.kmeans(data, 4, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

3. K最近鄰

k-最近鄰 (k-nearest neighbours, kNN) 是監督學習中最簡單的算法之一,kNN 可用於分類和回歸問題。在訓練階段,kNN 存儲所有訓練樣本的特征向量和類別標簽。在測試階段,將未標記的向量分類為距離最近的 k 個訓練樣本中出現頻率最高的類標簽,其中 k 是用戶定義的常數:

如上圖所示,如果 k = 5,則綠色圓圈(未標記的測試樣本)將被歸類為三角形,因為離其最近的 5 個樣本中有 3 個三角形但隻有 1 個菱形;如果 k = 9,則綠色圓圈將被歸類為菱形,因為離其最近的 9 個樣本中有 5 個菱形但隻有 4 個三角形。

在 OpenCV 中,使用 kNN 分類器首先需要使用 cv2.ml.KNearest_create() 創建 kNN 分類器,然後提供數據和標簽以使用 train() 方法訓練 kNN分類器。最後,使用 findNearest() 方法用於查找測試樣本鄰居,使用如下:

retval, results, neighborResponses, dist=cv2.ml_KNearest.findNearest(samples, k[, results[, neighborResponses[, dist]]])

其中,samples 是輸入樣本,k 設置為最近鄰居的個數,results 存儲每個輸入樣本的預測值,neighborResponses 存儲對應的鄰居,dist 存儲輸入樣本到相應鄰居的距離。

3.1 K最近鄰示例

接下來,為瞭演示 kNN 算法,首先隨機創建一組點並分配一個標簽 (0 或 1)。標簽 0 將代表紅色三角形,而標簽 1 將代表藍色方塊;然後,使用 kNN 算法根據 k 個最近鄰對樣本點進行分類。

第一步是創建具有相應標簽的點集和要分類的樣本點:

# 點集由50個點組成
data = np.random.randint(0, 100, (50, 2)).astype(np.float32)
# 為1每個點創建標簽 (0:紅色, 1:藍色)
labels = np.random.randint(0, 2, (50, 1)).astype(np.float32)
# 創建要分類的樣本點
sample = np.random.randint(0, 100, (1, 2)).astype(np.float32)

接下來,創建 kNN 分類器,訓練分類器,並找到要分類樣本點的 k 個最近鄰居:

# 創建 kNN 分類器
knn = cv2.ml.KNearest_create()
# 訓練 kNN 分類器
knn.train(data, cv2.ml.ROW_SAMPLE, labels)
# 找到要分類樣本點的 k 個最近鄰居
k = 3
ret, results, neighbours, dist = knn.findNearest(sample, k)
# 打印結果
print("result: {}".format(results))
print("neighbours: {}".format(neighbours))
print("distance: {}".format(dist))

# 可視化
fig = plt.figure(figsize=(8, 6))
red_triangles = data[labels.ravel() == 0]
plt.scatter(red_triangles[:, 0], red_triangles[:, 1], 200, 'r', '^')
blue_squares = data[labels.ravel() == 1]
plt.scatter(blue_squares[:, 0], blue_squares[:, 1], 200, 'b', 's')
plt.scatter(sample[:, 0], sample[:, 1], 200, 'g', 'o')
plt.show()

獲得結果如下所示:

result: [[0.]]

neighbours: [[0. 0. 1.]]

distance: [[13. 40. 65.]]

因此,綠點被歸類為紅色三角形,可視化效果如下所示:

4. 支持向量機

支持向量機 (Support Vector Machine, SVM) 是一種監督學習技術,它通過根據指定的類對訓練數據進行最佳分離,從而在高維空間中構建一個或一組超平面。

已二維平面為例,在下圖中看到,其中綠線是能夠將兩個類分開的最佳超平面,因為其到兩個類中的最近元素的距離是最大的:

上圖第一種情況下,決策邊界是一條線,而在第二種情況下,決策邊界是一條圓形曲線,虛線代表其他決策邊界,但它們並非最好地將兩個類分開的決策邊界。

OpenCV 中的 SVM 實現基於 LIBSVM,使用 cv2.ml.SVM_create() 函數創建空模型,然後為模型分配主要參數:

svmType :設置 SVM 類型,可選值如下:

  • SVM_C_SVC:C CC-支持向量分類,可用於 n 分類 (n≥2) 問題
  • NU_SVC: v vv-支持向量分類
  • ONE_CLASS: 分佈估計(單類 SVM)
  • EPS_SVR: ϵ \epsilonϵ-支持向量回歸
  • NU_SVR: v vv-支持向量回歸

kernelType :這設置瞭 SVM 的核類型,可選值如下:

  • LINEAR : 線性核
  • POLY :多項式核
  • RBF : Radial Basis Function (RBF),大多數情況下是不錯的選擇
  • SIGMOID : Sigmoid 核
  • CHI2 : 指數 Chi2 核,類似於 RBF 核
  • INTER : 直方圖交集核;運行速度較快的核

degree : 核函數的 degree 參數 (用於 POLY 核)

gamma :核函數的 γ \gammaγ 參數(用於 POLY/RBF/SIGMOID/CHI2 核)

coef0 : 核函數的 coef0 參數 (用於 POLY/SIGMOID 核)

Cvalue : SVM 優化問題的 C 參數 (用於 C_SVC/EPS_SVR/NU_SVR 類型)

nu : SVM 優化問題的 v vv 參數 (用於 NU_SVC/ONE_CLASS/NU_SVR 類型)

p : SVM 優化問題的 ϵ \epsilonϵ 參數 (用於 EPS_SVR 類型)

classWeights : C_SVC 問題中的可選權重,分配給特定的類

termCrit :迭代 SVM 訓練過程的終止標準

核函數選擇通常取決於數據集,通常可以首先使用 RBF 核進行測試,因為該核將樣本非線性地映射到更高維空間,可以方便的處理類標簽和屬性之間的關系是非線性的情況。

默認構造函數使用以下值初始化 SVM:

svmType: C_SVC, kernelType: RBF, degree: 0, gamma: 1, coef0: 0, C: 1, nu: 0, p: 0, classWeights: 0, termCrit: TermCriteria(MAX_ITER+EPS, 1000, FLT_EPSILON )

4.1 支持向量機示例

為瞭解如何在 OpenCV 中使用 SVM,首先需要創建訓練數據和標簽:

labels = np.array([1, 1, -1, -1, -1])
data = np.matrix([[800, 40], [850, 400], [500, 10], [550, 300], [450, 600]], dtype=np.float32)

以上代碼創建瞭 5 個點,前 2 個點被指定為 1 類,而另外 3 個被指定為 -1 類。接下來使用 svm_init() 函數初始化 SVM 模型:

def svm_init(C=12.5, gamma=0.50625):
    """ 創建 SVM 模型並為其分配主要參數,返回模型 """
    model = cv2.ml.SVM_create()
    model.setGamma(gamma)
    model.setC(C)
    model.setKernel(cv2.ml.SVM_LINEAR)
    model.setType(cv2.ml.SVM_C_SVC)
    model.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6))
    return model
# 初始化 SVM 模型
svm_model = svm_init(C=12.5, gamma=0.50625)

創建的 SVM 核類型設置為 LINEAR,SVM 的類型設置為 C_SVC。

然後,編寫 svm_train() 函數訓練 SVM 模型:

def svm_train(model, samples, responses):
    # 使用 samples 和 responses 訓練模型
    model.train(samples, cv2.ml.ROW_SAMPLE, responses)
    return model
# 訓練 SVM
svm_train(svm_model, data, labels)

然後創建一個圖像,並繪制 SVM 響應:

def show_svm_response(model, image):

    colors = {1: (255, 255, 0), -1: (0, 255, 255)}

    for i in range(image.shape[0]):
        for j in range(image.shape[1]):
            sample = np.matrix([[j, i]], dtype=np.float32)
            response = svm_predict(model, sample)

            image[i, j] = colors[response.item(0)]
    
    cv2.circle(image, (800, 40), 10, (255, 0, 0), -1)
    cv2.circle(image, (850, 400), 10, (255, 0, 0), -1)

    cv2.circle(image, (500, 10), 10, (0, 255, 0), -1)
    cv2.circle(image, (550, 300), 10, (0, 255, 0), -1)
    cv2.circle(image, (450, 600), 10, (0, 255, 0), -1)

    support_vectors = model.getUncompressedSupportVectors()
    for i in range(support_vectors.shape[0]):
        cv2.circle(image, (support_vectors[i, 0], support_vectors[i, 1]), 15, (0, 0, 255), 6)
# 創建圖像
img_output = np.zeros((640, 1200, 3), dtype="uint8")
# 顯示 SVM 響應
show_svm_response(svm_model, img_output)

如上圖所示,SVM 使用訓練數據進行瞭訓練,可用於對圖像中所有點進行分類。SVM 將圖像劃分為黃色和青色區域,可以看到兩個區域之間的邊界對應於兩個類之間的最佳間隔,因為到兩個類中最近元素的距離最大,支持向量用紅線邊框顯示。

小結

在本文中,首先介紹機器學習的概念及其相關話題,然後總結瞭機器學習中的三種主要方法,並總結瞭分類、回歸和聚類問題的三種最常見的技術。最後,我們通過示例瞭解瞭常用機器學習算法,具體而言,包括 K-Means 聚類算法、kNN 算法和 SVM 算法。 

以上就是Python OpenCV實戰之與機器學習的碰撞的詳細內容,更多關於OpenCV 機器學習的資料請關註WalkonNet其它相關文章!

推薦閱讀: