溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

發(fā)布時(shí)間:2021-12-16 11:13:10 來(lái)源:億速云 閱讀:277 作者:小新 欄目:開(kāi)發(fā)技術(shù)


手寫(xiě)數(shù)字?jǐn)?shù)據(jù)集 MNIST 介紹

為了保證完整性,從算法所用的訓(xùn)練數(shù)據(jù)講起,訓(xùn)練數(shù)據(jù)是由 MNIST 手寫(xiě)數(shù)字組成的,MNIST 數(shù)據(jù)集來(lái)自美國(guó)國(guó)家標(biāo)準(zhǔn)與技術(shù)研究所,由來(lái)自 250 個(gè)不同人手寫(xiě)的數(shù)字構(gòu)成,其中訓(xùn)練集包含 60000 張圖片,測(cè)試集包含 10000 張圖片,每個(gè)圖片都有其標(biāo)簽,圖片大小為 28*28。許多機(jī)器學(xué)習(xí)庫(kù)提供了加載 MNIST 數(shù)據(jù)集的方法,這里使用 keras 庫(kù)進(jìn)行加載:

# 導(dǎo)入 keras 庫(kù)
import keras
# 加載數(shù)據(jù)
(train_dataset, train_labels), (test_dataset, test_labels) = keras.datasets.mnist.load_data()
train_labels = np.array(train_labels, dtype=np.int32)
# 打印數(shù)據(jù)集形狀
print(train_dataset.shape, test_dataset.shape)
# 圖像預(yù)覽
for i in range(40):
    plt.subplot(4, 10, i+1)
    plt.imshow(train_dataset[i], cmap='gray')
    plt.title(train_labels[i], fontsize=10)
    plt.axis('off')
plt.show()

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

基準(zhǔn)模型——利用 KNN 算法識(shí)別手寫(xiě)數(shù)字

加載數(shù)據(jù)集后,我們嘗試使用 KNN 分類器識(shí)別數(shù)字,在原始方法中,我們首先使用原始像素值作為特征,因此圖像描述符的大小為 28 × 28 = 784。

首先利用 keras 加載所有數(shù)字圖像,為了了解數(shù)據(jù)訓(xùn)練的全部流程,我們將加載的訓(xùn)練數(shù)據(jù)集劃分為 訓(xùn)練數(shù)據(jù)集 + 測(cè)試數(shù)據(jù)集,每部分占比 50%:

# 加載數(shù)據(jù)集
(train_dataset, train_labels), (test_dataset, test_labels) = keras.datasets.mnist.load_data()
train_labels = np.array(train_labels, dtype=np.int32)
# 將原始圖像作為描述符
def raw_pixels(img):
    return img.flatten()
# 數(shù)據(jù)打散
shuffle = np.random.permutation(len(train_dataset))
train_dataset, train_labels = train_dataset[shuffle], train_labels[shuffle]
# 計(jì)算每個(gè)圖像的描述符,這里特征描述符是原始像素
raw_descriptors = []
for img in train_dataset:
    raw_descriptors.append(np.float32(raw_pixels(img)))
raw_descriptors = np.squeeze(raw_descriptors)
# 將數(shù)據(jù)拆分為訓(xùn)練和測(cè)試數(shù)據(jù)(各占 50%)
# 因此,使用 30000 個(gè)數(shù)字來(lái)訓(xùn)練分類器,30000 位數(shù)字來(lái)測(cè)試訓(xùn)練后的分類器
partition = int(0.5 * len(raw_descriptors))
raw_descriptors_train, raw_descriptors_test = np.split(raw_descriptors, [partition])
labels_train, labels_test = np.split(train_labels, [partition])

現(xiàn)在,我們就可以使用 knn.train() 方法訓(xùn)練 KNN 模型并使用 get_accuracy() 函數(shù)對(duì)其進(jìn)行測(cè)試:

# 訓(xùn)練 KNN 模型
knn = cv2.ml.KNearest_create()
knn.train(raw_descriptors_train, cv2.ml.ROW_SAMPLE, labels_train)
# 測(cè)試 kNN 模型
k = 5
ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)
# 根據(jù)真實(shí)值和預(yù)測(cè)值計(jì)算準(zhǔn)確率
def get_accuracy(predictions, labels):
    acc = (np.squeeze(predictions) == labels).mean()
    return acc * 100
acc = get_accuracy(result, labels_test)
print("Accuracy: {}".format(acc))

我們可以看到當(dāng) K = 5 時(shí),KNN 模型可以獲得 96.48% 的準(zhǔn)確率,但我們?nèi)匀豢梢詫?duì)其進(jìn)行改進(jìn),以獲取更高性能。

改進(jìn)模型1——參數(shù) K 對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響

我們已經(jīng)知道在 KNN 算法中,一個(gè)影響算法性能的重要參數(shù)就是 K,因此,我們可以首先嘗試使用不同的 K 值,查看其對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響。

為了比較不同 K 值時(shí)模型的準(zhǔn)確率,我們首先需要?jiǎng)?chuàng)建一個(gè)字典來(lái)存儲(chǔ)測(cè)試不同 K 值時(shí)的準(zhǔn)確率:

from collections import defaultdict
results = defaultdict(list)

接下來(lái),計(jì)算 knn.findNearest() 方法,改變 K 參數(shù),并將結(jié)果存儲(chǔ)在字典中:

# K 取值范圍為 (1, 9)
for k in range(1, 10):
    ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)
    acc = get_accuracy(result, labels_test)
    print(" {}".format("%.2f" % acc))
    results['50'].append(acc)

最后,繪制結(jié)果:

ax = plt.subplot(1, 1, 1)
ax.set_xlim(0, 10)
dim = np.arange(1, 10)
for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label="50%")
    
plt.legend(loc='upper left', title="% training")
plt.title('Accuracy of the K-NN model varying k')
plt.xlabel("number of k")
plt.ylabel("accuracy")
plt.show()

程序運(yùn)行結(jié)果如下圖所示:

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

如上圖所示,改變 K 參數(shù)獲得的準(zhǔn)確率也是不同的,因此,在應(yīng)用程序用可以通過(guò)調(diào)整 K 參數(shù)來(lái)獲取最佳性能。

改進(jìn)模型2——訓(xùn)練數(shù)據(jù)量對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響

在機(jī)器學(xué)習(xí)中,使用更多的數(shù)據(jù)訓(xùn)練分類器通常會(huì)提高模型的性能,這是由于分類器可以更好地學(xué)習(xí)特征的結(jié)構(gòu)。在 KNN 分類器中,增加訓(xùn)練數(shù)也會(huì)增加在特征空間中找到測(cè)試數(shù)據(jù)正確匹配的概率。

接下來(lái),我們就修改=用于訓(xùn)練和測(cè)試模型的圖像百分比,來(lái)觀察訓(xùn)練數(shù)據(jù)量對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響:

# 劃分訓(xùn)練數(shù)據(jù)集和測(cè)試數(shù)據(jù)集
split_values = np.arange(0.1, 1, 0.1)
# 存儲(chǔ)結(jié)果準(zhǔn)確率
results = defaultdict(list)
# 創(chuàng)建模型
knn = cv2.ml.KNearest_create()
# 不同訓(xùn)練數(shù)據(jù)量對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響
for split_value in split_values:
    # 將數(shù)據(jù)集劃分為訓(xùn)練和測(cè)試數(shù)據(jù)集
    partition = int(split_value * len(raw_descriptors))
    raw_descriptors_train, raw_descriptors_test = np.split(raw_descriptors, [partition])
    labels_train, labels_test = np.split(train_labels, [partition])
    # 訓(xùn)練 KNN 模型
    print('Training KNN model - raw pixels as features')
    knn.train(raw_descriptors_train, cv2.ml.ROW_SAMPLE, labels_train)
    # 同時(shí)對(duì)于每種劃分測(cè)試不同 K 值影響
    for k in range(1, 10):
        ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)
        acc = get_accuracy(result, labels_test)
        print("{}".format("%.2f" % acc))
        results[int(split_value * 100)].append(acc)

訓(xùn)練算法的數(shù)字圖像的百分比為10%、20%、…、90%,測(cè)試算法的數(shù)字百分比為90%、80%、…、10%,最后,繪制結(jié)果:

ax = plt.subplot(1, 1, 1)
ax.set_xlim(0, 10)
dim = np.arange(1, 10)
for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label=str(key) + "%")

plt.legend(loc='upper left', title="% training")
plt.title('Accuracy of the KNN model varying both k and the percentage of images to train/test')
plt.xlabel("number of k")
plt.ylabel("accuracy")
plt.show()

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

從上圖可以看出,隨著訓(xùn)練圖像數(shù)量的增加,準(zhǔn)確率也會(huì)增加。因此當(dāng)條件允許的情況下,可以通過(guò)增加訓(xùn)練數(shù)據(jù)量來(lái)提高模型性能。

雖然可以看到準(zhǔn)確率雖然已經(jīng)可以到達(dá)97%以上,但是我們不能就此止步。

改進(jìn)模型3——預(yù)處理對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響

在以上示例中,我們均使用原始像素值作為特征來(lái)訓(xùn)練分類器。在機(jī)器學(xué)習(xí)中,訓(xùn)練分類器之前的一個(gè)通??梢詫?duì)輸入數(shù)據(jù)進(jìn)行某種預(yù)處理,用以提高分類器訓(xùn)練性能,因此,接下來(lái)我們應(yīng)用預(yù)處理以查看其對(duì)識(shí)別手寫(xiě)數(shù)字精確度的影響。

預(yù)處理函數(shù) desew() 如下:

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 * SIZE_IMAGE * skew], [0, 1, 0]])
    img = cv2.warpAffine(img, M, (SIZE_IMAGE, SIZE_IMAGE), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)

    return img

desew() 函數(shù)通過(guò)使用其二階矩對(duì)數(shù)字進(jìn)行去歪斜。更具體地說(shuō),可以通過(guò)兩個(gè)中心矩的比值 (mu11/mu02) 計(jì)算偏斜的度量。計(jì)算出的偏斜用于計(jì)算仿射變換,從而消除數(shù)字的偏斜。接下來(lái)對(duì)比預(yù)處理的前后圖片效果:

for i in range(10):
    plt.subplot(2, 10, i+1)
    plt.imshow(train_dataset[i], cmap='gray')
    plt.title(train_labels[i], fontsize=10)
    plt.axis('off')
    plt.subplot(2, 10, i+11)
    plt.imshow(deskew(train_dataset[i]), cmap='gray')
    plt.axis('off')
plt.show()

在下圖的第一行顯示了原始數(shù)字圖像,第二行顯示了預(yù)處理后的數(shù)字圖像:

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

通過(guò)應(yīng)用此預(yù)處理,識(shí)別的準(zhǔn)確率得到提高,準(zhǔn)確率曲線如下圖所示:

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

可以看到經(jīng)過(guò)預(yù)處理的分類器準(zhǔn)確率甚至可以接近98%,考慮到我們僅僅是使用了簡(jiǎn)單的 KNN 模型,效果已經(jīng)很不錯(cuò)了,但是我們還可以進(jìn)一步提高模型性能。

改進(jìn)模型4——使用高級(jí)描述符作為圖像特征提高 KNN 算法準(zhǔn)確率

在以上示例中,我們一直使用原始像素值作為特征描述符。在機(jī)器學(xué)習(xí)中,一種常見(jiàn)的方法是使用更高級(jí)的描述符,接下來(lái)將使用定向梯度直方圖 (Histogram of Oriented Gradients, HOG) 作為圖像特征用以提高 KNN 算法準(zhǔn)確率。

特征描述符是圖像的一種表示,它通過(guò)提取描述基本特征(例如形狀、顏色或紋理等)的有用信息來(lái)簡(jiǎn)化圖像。通常,特征描述符將圖像轉(zhuǎn)換為長(zhǎng)度為 n 的特征向量,HOG 是一種用于計(jì)算機(jī)視覺(jué)的流行特征描述符。

接下來(lái)定義 get_hog() 函數(shù)獲取 HOG 描述符:

(train_dataset, train_labels), (test_dataset, test_labels) = keras.datasets.mnist.load_data()
SIZE_IMAGE = train_dataset.shape[1]
train_labels = np.array(train_labels, dtype=np.int32)
def get_hog():
    hog = cv2.HOGDescriptor((SIZE_IMAGE, SIZE_IMAGE), (8, 8), (4, 4), (8, 8), 9, 1, -1, 0, 0.2, 1, 64, True)
    print("hog descriptor size: {}".format(hog.getDescriptorSize()))
    return hog

然后使用 HOG 特征訓(xùn)練 KNN 模型

hog = get_hog()

hog_descriptors = []
for img in train_dataset:
    hog_descriptors.append(hog.compute(deskew(img)))
hog_descriptors = np.squeeze(hog_descriptors)

訓(xùn)練完成的模型的準(zhǔn)確率,如下圖所示:

Python-OpenCV中如何利用?KNN?算法識(shí)別手寫(xiě)數(shù)字

通過(guò)上述改進(jìn)過(guò)程,可以看到編寫(xiě)機(jī)器學(xué)習(xí)模型時(shí)的一個(gè)好方法是從解決問(wèn)題的基本基線模型開(kāi)始,然后通過(guò)添加更好的預(yù)處理、更高級(jí)的特征描述符或其他機(jī)器學(xué)習(xí)技術(shù)來(lái)迭代改進(jìn)模型。最后,如果條件允許,可以收集更多數(shù)據(jù)用于訓(xùn)練和測(cè)試模型。

完整代碼

最終完整代碼如下所示,改進(jìn)過(guò)程中的其他代碼可以根據(jù)上述講解對(duì)以下代碼進(jìn)行簡(jiǎn)單修改獲得:

import cv2
import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict
import keras

(train_dataset, train_labels), (test_dataset, test_labels) = keras.datasets.mnist.load_data()

SIZE_IMAGE = train_dataset.shape[1]

train_labels = np.array(train_labels, dtype=np.int32)
def get_accuracy(predictions, labels):
    acc = (np.squeeze(predictions) == labels).mean()
    return acc * 100
    
def raw_pixels(img):
    return img.flatten()

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 * SIZE_IMAGE * skew], [0, 1, 0]])
    img = cv2.warpAffine(img, M, (SIZE_IMAGE, SIZE_IMAGE), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)

    return img

def get_hog():
    hog = cv2.HOGDescriptor((SIZE_IMAGE, SIZE_IMAGE), (8, 8), (4, 4), (8, 8), 9, 1, -1, 0, 0.2, 1, 64, True)
    print("hog descriptor size: {}".format(hog.getDescriptorSize()))

    return hog

shuffle = np.random.permutation(len(train_dataset))
train_dataset, train_labels = train_dataset[shuffle], train_labels[shuffle]
# 高級(jí)圖像描述符
hog = get_hog()
hog_descriptors = []
for img in train_dataset:
    hog_descriptors.append(hog.compute(deskew(img)))
hog_descriptors = np.squeeze(hog_descriptors)
# 數(shù)據(jù)劃分
split_values = np.arange(0.1, 1, 0.1)

# 創(chuàng)建字典用于存儲(chǔ)準(zhǔn)確率
results = defaultdict(list)

# 創(chuàng)建 KNN 模型
knn = cv2.ml.KNearest_create()

for split_value in split_values:
    partition = int(split_value * len(hog_descriptors))
    hog_descriptors_train, hog_descriptors_test = np.split(hog_descriptors, [partition])
    labels_train, labels_test = np.split(train_labels, [partition])

    print('Training KNN model - HOG features')
    knn.train(hog_descriptors_train, cv2.ml.ROW_SAMPLE, labels_train)

    # 存儲(chǔ)準(zhǔn)確率
    for k in np.arange(1, 10):
        ret, result, neighbours, dist = knn.findNearest(hog_descriptors_test, k)
        acc = get_accuracy(result, labels_test)
        print(" {}".format("%.2f" % acc))
        results[int(split_value * 100)].append(acc)

fig = plt.figure(figsize=(12, 5))
plt.suptitle("k-NN handwritten digits recognition", fontsize=14, fontweight='bold')

ax = plt.subplot(1, 1, 1)
ax.set_xlim(0, 10)
dim = np.arange(1, 10)

for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label=str(key) + "%")

plt.legend(loc='upper left', title="% training")
plt.title('Accuracy of the k-NN model varying both k and the percentage of images to train/test with pre-processing '
          'and HoG features')
plt.xlabel("number of k")
plt.ylabel("accuracy")
plt.show()


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI