圖像識別之KNN算法的理解與應用(2)

2021-02-15 萌萌噠程序猴

在上一篇文章中,我們介紹了KNN算法的原理,並詳細闡述了使用Opencv的KNN算法模塊對手寫數字圖像進行識別,發現識別的準確率還是比較高的,達到90%以上,這是因為手寫數字圖像的特徵比較簡單的緣故。本文我們將使用KNN來對更加複雜的CIFAR-10數據集進行識別分類,並嘗試提高分類的準確率。

1. CIFAR-10數據集介紹

CIFAR-10是一個專門用於測試圖像分類的公開數據集,其包含的彩色圖像分為10種類型:飛機、轎車、鳥、貓、鹿、狗、蛙、馬、船、貨車。且這10種類型圖像的標籤依次為0、1、2、3、4、5、6、7、8、9。該數據集分為Python、Matlab、C/C++三個不同的版本,顧名思義,三個版本分別適用於對應的三種程式語言。因為我們使用的是C/C++語言,所以使用對應的C/C++版本就好,該版本的數據集包含6個bin文件,如下圖所示,其中data_batch_1.bin~data_batch_5.bin通常用於訓練,而test_batch.bin則用於訓練之後的識別測試。

如下圖所示,每個bin文件包含10000*3073個字節數據,在每個3073數據塊中,第一個字節是0~9的標籤,後面3072位元組則是彩色圖像的三通道數據:紅通道 --> 綠通道 --> 藍通道 (1024 --> 1024 --> 1024)。其中每1024位元組的數據就是一幀單通道的32*32圖像,3幀32*32位元組的單通道圖像則組成了一幀彩色圖像。所以總體來說,每一個bin文件包含了10000幀32*32的彩色圖像。

我們編程把每一個bin文件中包含的圖像解析出來,並保存成圖像文件。比如對於data_batch_1.bin文件,新建文件夾batch1,然後在batch1文件夾下面再新建名為0~9的10個文件夾,分別保存標籤為0~9的圖像。

上解析代碼:

void read_cifar_bin(char *bin_path, char *save_path){  const int img_num = 10000;  const int img_size = 3073;     const int img_size_1 = 1024;  const int data_size = img_num*img_size;  const int row = 32;  const int col = 32;    uchar *cifar_data = (uchar *)malloc(data_size);  if (cifar_data == NULL)  {    cout << "malloc failed" << endl;    return;  }
FILE *fp = fopen(bin_path, "rb"); if (fp == NULL) { cout << "fopen file failed" << endl; free(cifar_data); return; }
fread(cifar_data, 1, data_size, fp);
int cnt[10] = {0};
for (int i = 0; i < img_num; i++) {    cout << i << endl; long int offset = i*img_size; long int offset0 = offset + 1; long int offset1 = offset0 + img_size_1; long int offset2 = offset1 + img_size_1;     uchar label = cifar_data[offset];    Mat img(row, col, CV_8UC3);
for (int y = 0; y < row; y++) { for (int x = 0; x < col; x++) { int idx = y*col + x; img.at<Vec3b>(y, x) = Vec3b(cifar_data[offset2+idx], cifar_data[offset1+idx], cifar_data[offset0+idx]); } }
    char str[100] = {0};    sprintf(str, "%s/%d/%d.tif", save_path, label, cnt[label]);    imwrite(str, img);    cnt[label]++; }
fclose(fp); free(cifar_data);}

運行上述代碼分別解析數據集的6個bin文件,得到6*10000張圖像,這時我們有6個文件夾(對應6個bin文件):


以上每個文件夾下又包含了0~9的子文件夾,分別保存對應標籤的圖像:

2. CIFAR-10數據集的訓練與識別

把圖像從bin文件解析出來之後,就可以進行訓練和識別了,代碼與上篇文章類似:

void KNN_cifar_test(void){  char ad[128] = { 0 };  int testnum = 0, truenum = 0;  const int K = 8;  cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();  knn->setDefaultK(K);     knn->setIsClassifier(true);    knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);   
  Mat traindata, trainlabel; const int trainnum = 900;
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch1/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
srcimage = srcimage.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch2/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
srcimage = srcimage.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch3/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
srcimage = srcimage.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch4/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
srcimage = srcimage.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch5/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
srcimage = srcimage.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
traindata.convertTo(traindata, CV_32F); knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);
for (int i = 0; i < 10; i++) { for (int j = 0; j < 800; j++) { testnum++; sprintf_s(ad, "cifar/test_batch/%d/%d.tif", i, j); Mat testdata = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
testdata = testdata.reshape(1, 1); testdata.convertTo(testdata, CV_32F);
Mat result; int response = knn->findNearest(testdata, K, result); if (response == i) { truenum++; } } } cout << "測試總數" << testnum << endl; cout << "正確分類數" << truenum << endl; cout << "準確率:" << (float)truenum / testnum * 100 << "%" << endl;}

運行上述代碼,首先加載訓練數據,然後對8000張測試圖像進行分類,得到的結果如下。可以看到僅2349張圖像識別成功了,29.3625%的識別準確率是非常低的了。

3. 使用HOG特徵提高識別準確率

我們識別熟人的時候,主要根據臉部的主要特徵來識別的,比如嘴巴、鼻子、眼睛、臉型,或者臉上的痣等,但如果我們根據臉頰的某一小塊毫無特點的皮膚來識別,則很可能認錯人。同樣的道理,計算機的圖像識別也是一樣的,如果能提取圖像的主要特徵來進行識別,則可以大大提高識別的準確率。常使用的圖像特徵有Shift特徵、Surf特徵、HOG特徵等。本文中,我們通過提取圖像的HOG特徵來進行識別。

首先介紹一下HOG特徵。HOG特徵算法的核心思想是提取圖像的梯度加權直方圖。下面分步介紹其提取過程:

(1) 計算圖像的梯度圖與梯度方向。

圖像的梯度分為x方向梯度和y方向梯度,對於圖像中任意點(x,y),像素值為I(x,y),其x方向梯度和y方向梯度可分別按下式計算:

得到x方向梯度和y方向梯度之後,即可得到該點的梯度與梯度方向(夾角):

(2) 統計梯度加權直方圖。

這裡面涉及到三個概念:檢測窗口、檢測塊、檢測單元,我們分別介紹一下這三個概念。

檢測窗口:在圖像中選擇的一個固定大小的矩形窗口,該窗口從左往右、從上往下滑動,每次滑動都有一個固定的步長。

檢測塊:在檢測窗口中選擇的一個固定大小的矩形窗口,該窗口從左往右、從上往下滑動,每次滑動都有一個固定的步長。

檢測單元:把檢測塊分成若干個小塊,每一個小塊就是一個檢測單元。比如上圖中,把檢測快分為2*2的檢測單元。

梯度加權直方圖的統計,則是以檢測單元為單位的,統計每個檢測單元中所有點的梯度值與梯度方向。梯度方向的範圍為0~359°,通常將其分成9段:0~39、40~79、80~119、120~159、160~199、200~239、240~279、280~319、320~359。根據每一個點的梯度方向,將其劃分到對應的段中,同時該段的特徵值加上該點的梯度值,比如檢測單元中某個點的梯度值為25,梯度方向為67°,則將其劃分到40~79段中,同時把40~79段的特徵值加上25,這就是加權梯度直方圖。

因此,每一個檢測單元有9個特徵值,檢測窗口與檢測塊滑動過程中的所有檢測單元的特徵值,就組成了圖像的特徵值。

Opencv中已經實現了HOG算法。下面我們使用Opencv中的HOG算法模塊來檢測圖像的HOG特徵,並對HOG特徵進行識別,上代碼:

void KNN_cifar_test_hog(void){  char ad[128] = { 0 };  int testnum = 0, truenum = 0;  const int K = 8;  cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();  knn->setDefaultK(K);  knn->setIsClassifier(true);  knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);
  Mat traindata, trainlabel; const int trainnum = 900; HOGDescriptor *hog = new HOGDescriptor(cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), cvSize(2, 2), 9); vector<float> descriptors;
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch1/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE); hog->compute(srcimage, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors); srcimage = hogg.reshape(1, 1);
traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch2/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
hog->compute(srcimage, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors);
srcimage = hogg.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch3/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
hog->compute(srcimage, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors);
srcimage = hogg.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch4/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
hog->compute(srcimage, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors);
srcimage = hogg.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
for (int i = 0; i < 10; i++) { for (int j = 0; j < trainnum; j++) { printf("i=%d, j=%d\n", i, j); sprintf_s(ad, "cifar/batch5/%d/%d.tif", i, j); Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
hog->compute(srcimage, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors);
srcimage = hogg.reshape(1, 1); traindata.push_back(srcimage); trainlabel.push_back(i); } }
traindata.convertTo(traindata, CV_32F); knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);
for (int i = 0; i < 10; i++) { for (int j = 0; j < 800; j++) { testnum++; sprintf_s(ad, "cifar/test_batch/%d/%d.tif", i, j); Mat testdata = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
hog->compute(testdata, descriptors, Size(4, 4), Size(0, 0)); Mat hogg(descriptors);
testdata = hogg.reshape(1, 1); testdata.convertTo(testdata, CV_32F);
Mat result; int response = knn->findNearest(testdata, K, result); if (response == i) { truenum++; } } } cout << "測試總數" << testnum << endl; cout << "正確分類數" << truenum << endl; cout << "準確率:" << (float)truenum / testnum * 100 << "%" << endl;}

運行上述代碼,得到結果如下。可以看到,識別的準確率從之前的29.3625%提升到了53.0625%,提升幅度還是很可觀的,不過準確率還是達不到理想的水平,這是KNN算法本身的局限導致的,KNN算法對複雜圖像的識別並不擅長,因此在接下來的文章中我們將嘗試一下別的圖像識別算法。

如果感興趣,麻煩您動動手指識別下方的二維碼,關注本公眾號,多謝!




相關焦點

  • 圖像識別之KNN算法的理解與應用
    該算法既可以用於數據分類,也可以用於數據回歸預測,其核心思路是在訓練樣本中尋找距離最接近待分類樣本的K個樣本。然後,如果目的是分類,則統計這K個樣本中的各個類別數量,數量最多的類別即認為是待分類樣本的類別;如果目的是回歸預測,則計算這K個樣本的平均值作為預測值。圖像識別,本質上也是數據分類,也即把每一張圖像歸類,因此KNN算法可以應用於圖像識別。
  • 圖像識別之原理、過程、應用前景,精華篇
    3、圖像識別技術的分析隨著計算機技術的迅速發展和科技的不斷進步,圖像識別技術已經在眾多領域中得到了應用。2015年2月15日新浪科技發布一條新聞:「微軟最近公布了一篇關於圖像識別的研究論文,在一項圖像識別的基準測試中,電腦系統識別能力已經超越了人類。
  • Sk-learn之KNN算法綜合實戰
    但是想要更好的訓練模型,這就需要我們花一些精力來理解數據了,也就是常說的要深入理解業務。這樣才能明白數據指標間的相關性,做到更好的取捨。這些需要在工作慢慢的積累總結和思考。另外,現在我們使用的這些sklearn中自帶的數據集都是處理過的,但是在真實世界的項目中數據往往是混亂且不完整的,需要我們花很大部分精力來對數據進行清洗。
  • 【研究】圖像識別及應用
    2 圖像識別的應用場景有哪些?什麼是圖像識別圖像識別,是指利用計算機對圖像進行處理、分析和理解,以識別各種不同模式的目標和對像的技術。根據觀測到的圖像,對其中的物體分辨其類別,做出有意義的判斷。利用現代信息處理與計算技術來模擬和完成人類的認識、理解過程。
  • 圖像識別技術的行業應用
    ,今天,我們就一起來了解下什麼是圖像識別?圖像識別,是指利用計算機對圖像進行處理、分析和理解,以識別各種不同模式的目標和對象的技術,自動識別圖像中的對象,人物,位置和動作等。圖像識別用於執行任務,是應用深度學習算法的一種實踐應用。如何實現圖像識別?圖像識別對於人類和動物來說是很自然的,但是對於計算機來說卻是一項極其困難的任務。
  • KNN算法中的K有多重要
    , class_sep=0.8)數據集包含屬於2個類的1000個樣本。from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)第一個模型是k=1的knn模型。
  • KNN學習之圖像分類與KNN原理
    2.距離的度量: 距離的度量描述了測試樣本與訓練樣本的臨近程度,這個臨近程度就是K個樣本選擇的依據,在KNN算法中,如果特徵是連續的,那麼距離函數一般用曼哈頓距離(L1距離)或歐氏距離(L2距離),如果特徵是離散的,一般選用漢明距離。
  • 聚類(三):KNN算法(R語言)
    k最臨近(KNN)算法是最簡單的分類算法之一,屬於有監督的機器學習算法。
  • 計算機視覺:從入門到精通,極限剖析圖像識別學習算法
    本次課程將圍繞著計算機視覺中最常見的RCNN圖像識別算法進行極限剖析,從數學理論, 模型框架到實踐實操,讓你在短時間內從理論到實踐,掌握深度學習的基本知識和學習方法。· 目的:掌握神經網絡的基本原理,知其然亦知其所以然(從數學實踐到代碼的熟練和精通); · 手段:科學的方法。
  • R語言--鄰近算法KNN
    ❝KNN(k鄰近算法)是機器學習算法中常見的用於分類或回歸的算法。它簡單,訓練數據快,對數據分布沒有要求,使它成為機器學習中使用頻率較高的算法,並且,在深度學習大行其道的今天,傳統可解釋的簡單模型在工業大數據領域的應用更為廣泛。本文介紹KNN算法的基本原理和用R代碼實現。
  • 速度數百倍之差!有人斷言KNN面臨淘汰,更快更強的ANN將取而代之
    重磅乾貨,第一時間送達作者:Marie Stephen Leo本文轉載自:機器之心在模式識別領域中,K - 近鄰算法(K-Nearest Neighbor, KNN)是一種用於分類和回歸的非參數統計方法。
  • 圖像識別技術落地 探索應用場景
    在兩周前的《最強大腦》中,百度首席科學家吳恩達帶著小度機器人和人類選手比拼,在人臉識別項目以 3:2取勝。但是,在聯想和常識理解的能力上,圖像識別遠未能和人類比肩,相關公司正在積極切入垂直行業應用中。       圖像識別落地       圖像識別背後的技術就是新的機器學習方式,即深度學習。
  • 人工智慧之K近鄰算法(KNN)
    前言:人工智慧機器學習有關算法內容,請參見公眾號「科技優化生活」之前相關文章。人工智慧之機器學習主要有三大類:1)分類;2)回歸;3)聚類。今天我們重點探討一下K近鄰(KNN)算法。在實際應用中,K 值一般選擇一個較小的數值,通常採用交叉驗證的方法來選擇最優的 K 值。隨著訓練實例數目趨向於無窮和 K=1 時,誤差率不會超過貝葉斯誤差率的2倍,如果K也趨向於無窮,則誤差率趨向於貝葉斯誤差率。
  • 圖像識別 | 基於Amazon Rekognition的圖像識別應用
    導語:隨著人工智慧在世界範圍內的大爆發,機器學習和深度學習算法不斷精進,在圖像識別和機器翻譯等方面的應用方面,各大巨頭也是使出了渾身解數。
  • 機器學習之KNN分類算法介紹: Stata和R同步實現(附數據和代碼)
    限於篇幅,本文沒有詳細介紹KNN回歸的應用案例。1.3 KNN算法大概步驟(1)明確k的取值,且最好為奇數(偶數情況下可能出現ties);選擇近鄰距離的算法,默認是L2歐式距離。  (2)將原始數據切割為一定比例(如70%)的訓練數據集和(如30%)測試數據集。(4)基於測試數據做出預測,以評估學習算法模型的性能表現。1.4 KNN算法的優點(1)簡單但強大。
  • 人工智慧與圖像識別在醫療診斷中的應用
    最為常見的人工智慧,例如iPhone的Siri,特斯拉的自動駕駛,智能語音客服等,皆通過機器學習這類統計算法來實現。與傳統統計方法相比,機器學習的優勢在於它可以處理數量巨大、特徵繁多的數據;並基於學習過去的經驗與知識,在遇到新的問題時,通過歸納出的解決方法來處理問題。本文將探討人工智慧在關乎人類生老病死的醫療健康行業中的應用,重點講述醫療影像識別中的優劣勢及市場應用。
  • 「人工智慧核心之機器學習(2)」——python 實現 KNN
    歡迎大家關注公眾號【哈希大數據】1、KNN算法基本介紹K-Nearest Neighbor(k最鄰近分類算法),簡稱KNN,是最簡單的一種有監督的機器學習算法。也是一種懶惰學習算法,即開始訓練僅僅是保存所有樣本集的信息,直到測試樣本到達才開始進行分類決策。
  • 數據驅動優化:如何利用 KNN 算法驅動產品優化?
    在網際網路行業中常常有利用數據分析或者數據挖掘的結論來應用到產品中,驅動產品的優化,提升產品的各項KPI 指標, 在數據挖掘和數據分析的背後會涉及到一些數據挖掘或者機器學習的算法。本文主要是knn算法原理的介紹,以及在它在網際網路行業中的具體應用,後續會介紹這個算法的具體實現(R 語言和python 語言)。
  • KNN算法(三)-sklearn實現
    上篇講到KNN算法的實例。在python中sklearn庫可直接實現KNN算法。本篇介紹運用sklearn庫實現KNN算法。
  • 機器學習(二)-------KNN算法的sklearn KNN實踐
    一.Skelarn KNN參數概述要使用sklearnKNN算法進行分類,我們需要先了解sklearnKNN算法的一些基本參數,那麼這節就先介紹這些內容吧。前面說到過,通過調整 K 值,算法會有不同的效果。- weights(權重):最普遍的 KNN 算法無論距離如何,權重都一樣,但有時候我們想搞點特殊化,比如距離更近的點讓它更加重要。這時候就需要 weight 這個參數了,這個參數有三個可選參數的值,決定了如何分配權重。參數選項如下: • 'uniform':不管遠近權重都一樣,就是最普通的 KNN 算法的形式。