【OpenCV】opencv3.0中的SVM訓練 mnist 手寫字體識別

前言:html

SVM(支持向量機)一種訓練分類器的學習方法ios

mnist 是一個手寫字體圖像數據庫,訓練樣本有60000個,測試樣本有10000個數據庫

LibSVM 一個經常使用的SVM框架框架

OpenCV3.0 中的ml包含了不少的ML框架接口,就試試了。ide

詳細的OpenCV文檔:http://docs.opencv.org/3.0-beta/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html函數

mnist數據下載:http://yann.lecun.com/exdb/mnist/學習

LibSVM下載:http://www.csie.ntu.edu.tw/~cjlin/libsvm/測試

========================我是分割線=============================字體

訓練的過程大體以下:網站

1. 讀取mnist訓練集數據

2. 訓練

3. 讀取mnist測試數據,對比預測結果,獲得錯誤率

 

具體實現:

1. mnist給出的數據文件是二進制文件

    四個文件,解壓後以下

   

  "train-images.idx3-ubyte" 二進制文件,存儲了頭文件信息以及60000張28*28圖像pixel信息(用於訓練)
  "train-labels.idx1-ubyte" 二進制文件,存儲了頭文件信息以及60000張圖像label信息
  "t10k-images.idx3-ubyte"二進制文件,存儲了頭文件信息以及10000張28*28圖像pixel信息(用於測試)
  "t10k-labels.idx1-ubyte"二進制文件,存儲了頭文件信息以及10000張圖像label信息

 

  由於OpenCV中沒有直接導入MINST數據的文件,因此須要本身寫函數來讀取

  首先要知道,MNIST數據的數據格式

  

   IMAGE FILE包含四個int型的頭部數據(magic number,number_of_images, number_of_rows, number_of_columns)

       餘下的每個byte表示一個pixel的數據,範圍是0-255(能夠在讀入的時候scale到0~1的區間

       LABEL FILE包含兩個int型的頭部數據(magic number, number of items)

       餘下的每個byte表示一個label數據,範圍是0-9

 

   注意(第一個坑):MNIST是大端存儲,然而大部分的Intel處理器都是小端存儲,因此對於int、long、float這些多字節的數據類型,就要一個一個byte地翻轉過來,才能正確顯示。

  

 1 //翻轉
 2 int reverseInt(int i) {
 3     unsigned char c1, c2, c3, c4;
 4 
 5     c1 = i & 255;
 6     c2 = (i >> 8) & 255;
 7     c3 = (i >> 16) & 255;
 8     c4 = (i >> 24) & 255;
 9 
10     return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
11 }
View Code

  而後讀取MNIST文件,可是它是二進制文件,打開方式

  因此不能用

  ifstream file(fileName);

  而要改爲

  ifstream file(fileName, ios::binary);

  注意(第二個坑):若是用第一條指令來打開文件,不會報錯,可是數據會出現錯誤,頭部數據仍然正確,可是後面的pixel數據大部分都是0,我剛開始沒注意,開始training的時候發現等了好久...真的是好久...(7+ hours)...估計是達到迭代終止的最大次數了,才停下來的

  嗯,stack overflow上也有相似的提問:

  

 

  注意(第三個坑):

  training時,IMAGE和LABEL的數據分別都放進一個MAT中存儲,可是隻能是CV32_F或者CV32_S的格式,否則會assertion報錯

  OPENCV給出的文檔中,例子是這樣的:(可是predict的時候又會要求label的格式是unsigned int)因此...能夠設置data的Mat格式爲CV_32FC1,label的Mat格式爲CV_32SC1

  

 

  順便地,圖像訓練數據的轉換存儲格式(http://stackoverflow.com/questions/14694810/using-opencv-and-svm-with-images?rq=1)

  

 

  最後,爲了驗證讀取數據的正確性,一個有效的辦法就是輸出第一個和最後一個數據(能夠輸出打印第一個/最後一個image以及label)

2. 訓練

  (此處我是直接對原圖像訓練,並無提取任何的特徵)

  也有人建議這裏應該對圖像作HOG特徵提取,再配合label訓練(我還沒試過...不知道效果如何...)

  

  opencv3.0和2.4的SVM接口有不一樣,基本能夠按照如下的格式來執行:

ml::SVM::Params params;
params.svmType = ml::SVM::C_SVC;
params.kernelType = ml::SVM::POLY;
params.gamma = 3;
Ptr<ml::SVM> svm = ml::SVM::create(params);
Mat trainData; // 每行爲一個樣本
Mat labels;    
svm->train( trainData , ml::ROW_SAMPLE , labels );
// ...

svm->save("....");//文件形式爲xml,能夠保存在txt或者xml文件中
Ptr<SVM> svm=statModel::load<SVM>("....");

Mat query; // 輸入, 1個通道
Mat res;   // 輸出
svm->predict(query, res);

 可是要注意,若是報錯的話最好去看opencv3.0的文檔,裏面有函數原型和解釋,我在實際操做的過程當中,也作了一些改動

   1)設置參數

    SVM的參數有不少,可是與C_SVC和RBF有關的就只有gamma和C,因此設置這兩個就好,終止條件設置和默認同樣,由經驗可得(實際上是查閱了不少的資料,把gamma設置成0.01,這樣訓練收斂速度會快不少)

Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::RBF);
svm->setGamma(0.01);
svm->setC(10.0);
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000,FLT_EPSILON));

  svm_type –指定SVM的類型,下面是可能的取值:

  CvSVM::C_SVC C類支持向量分類機。 n類分組 (n \geq 2),容許用異常值懲罰因子C進行不徹底分類。
  CvSVM::NU_SVC \nu類支持向量分類機。n相似然不徹底分類的分類器。參數爲 \nu 取代C(其值在區間【0,1】中,nu越大,決策邊界越平滑)。
  CvSVM::ONE_CLASS 單分類器,全部的訓練數據提取自同一個類裏,而後SVM創建了一個分界線以分割該類在特徵空間中所佔區域和其它類在特徵空間中所佔區域。
  CvSVM::EPS_SVR \epsilon類支持向量迴歸機。訓練集中的特徵向量和擬合出來的超平面的距離須要小於p。異常值懲罰因子C被採用。
  CvSVM::NU_SVR \nu類支持向量迴歸機。 \nu 代替了 p。

  kernel_type –SVM的內核類型,下面是可能的取值:

  CvSVM::LINEAR 線性內核。沒有任何向映射至高維空間,線性區分(或迴歸)在原始特徵空間中被完成,這是最快的選擇。K(x_i, x_j) = x_i^T x_j.
  CvSVM::POLY 多項式內核: K(x_i, x_j) = (\gamma x_i^T x_j + coef0)^{degree}, \gamma > 0.
  CvSVM::RBF 基於徑向的函數,對於大多數狀況都是一個較好的選擇: K(x_i, x_j) = e^{-\gamma ||x_i - x_j||^2}, \gamma > 0.
  CvSVM::SIGMOID Sigmoid函數內核:K(x_i, x_j) = \tanh(\gamma x_i^T x_j + coef0).

  degree – 內核函數(POLY)的參數degree。

  gamma – 內核函數(POLY/ RBF/ SIGMOID)的參數\gamma。

  coef0 – 內核函數(POLY/ SIGMOID)的參數coef0。

  Cvalue – SVM類型(C_SVC/ EPS_SVR/ NU_SVR)的參數C。

  nu – SVM類型(NU_SVC/ ONE_CLASS/ NU_SVR)的參數 \nu。

  p – SVM類型(EPS_SVR)的參數 \epsilon。

  class_weights – C_SVC中的可選權重,賦給指定的類,乘以C之後變成 class\_weights_i * C。因此這些權重影響不一樣類別的錯誤分類懲罰項。權重越大,某一類別的誤分類數據的懲罰項就越大。

  term_crit – SVM的迭代訓練過程的停止條件,解決部分受約束二次最優問題。您能夠指定的公差和/或最大迭代次數。

 

   2)訓練

Mat trainData;
Mat labels;
trainData = read_mnist_image(trainImage);
labels = read_mnist_label(trainLabel);

svm->train(trainData, ROW_SAMPLE, labels);

 

   3)保存

svm->save("mnist_dataset/mnist_svm.xml");

 

3. 測試,比對結果

 (此處的FLT_EPSILON是一個極小的數,1.0 - FLT_EPSILON != 1.0)

Mat testData;
Mat tLabel;
testData = read_mnist_image(testImage);
tLabel = read_mnist_label(testLabel);

float count = 0;
for (int i = 0; i < testData.rows; i++) {
    Mat sample = testData.row(i);
    float res = svm1->predict(sample);
    res = std::abs(res - tLabel.at<unsigned int>(i, 0)) <= FLT_EPSILON ? 1.f : 0.f;
    count += res;
}
cout << "正確的識別個數 count = " << count << endl;
cout << "錯誤率爲..." << (10000 - count + 0.0) / 10000 * 100.0 << "%....\n";

這裏沒有使用svm->predict(query, res);

而後就查看了opencv的文檔,當傳入數據是Mat 而不是cvMat時,能夠利用predict的返回值(float)來判斷預測是否正確。

 

運行結果:

11000個訓練數據/1000個測試數據

  

 

2)2000個訓練數據/2000個測試數據

  

 

3)5000個訓練數據/5000個測試數據

  

 

410000個訓練數據/10000個測試數據

  

 

5)60000個訓練數據/10000個測試數據

  

 

最後,關於運行時間(在程序正確的前提下,訓練時長和初始的參數設置有關),給出我最的運行結果(1000張圖是11s左右,60000張是1300s ~ 2000s左右)

 

代碼:

 1 #ifndef MNIST_H  
 2 #define MNIST_H
 3 
 4 #include <iostream>
 5 #include <string>
 6 #include <fstream>
 7 #include <ctime>
 8 #include <opencv2/opencv.hpp>  
 9 
10 using namespace cv;
11 using namespace std;
12 
13 //小端存儲轉換
14 int reverseInt(int i);
15 
16 //讀取image數據集信息
17 Mat read_mnist_image(const string fileName);
18 
19 //讀取label數據集信息
20 Mat read_mnist_label(const string fileName);
21 
22 #endif
mnist.h
  1 #include "mnist.h"
  2 
  3 //計時器
  4 double cost_time;
  5 clock_t start_time;
  6 clock_t end_time;
  7 
  8 //測試item個數
  9 int testNum = 10000;
 10 
 11 int reverseInt(int i) {
 12     unsigned char c1, c2, c3, c4;
 13 
 14     c1 = i & 255;
 15     c2 = (i >> 8) & 255;
 16     c3 = (i >> 16) & 255;
 17     c4 = (i >> 24) & 255;
 18 
 19     return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
 20 }
 21 
 22 Mat read_mnist_image(const string fileName) {
 23     int magic_number = 0;
 24     int number_of_images = 0;
 25     int n_rows = 0;
 26     int n_cols = 0;
 27 
 28     Mat DataMat;
 29 
 30     ifstream file(fileName, ios::binary);
 31     if (file.is_open())
 32     {
 33         cout << "成功打開圖像集 ... \n";
 34 
 35         file.read((char*)&magic_number, sizeof(magic_number));
 36         file.read((char*)&number_of_images, sizeof(number_of_images));
 37         file.read((char*)&n_rows, sizeof(n_rows));
 38         file.read((char*)&n_cols, sizeof(n_cols));
 39         //cout << magic_number << " " << number_of_images << " " << n_rows << " " << n_cols << endl;
 40 
 41         magic_number = reverseInt(magic_number);
 42         number_of_images = reverseInt(number_of_images);
 43         n_rows = reverseInt(n_rows);
 44         n_cols = reverseInt(n_cols);
 45         cout << "MAGIC NUMBER = " << magic_number
 46             << " ;NUMBER OF IMAGES = " << number_of_images
 47             << " ; NUMBER OF ROWS = " << n_rows
 48             << " ; NUMBER OF COLS = " << n_cols << endl;
 49 
 50         //-test-
 51         //number_of_images = testNum;
 52         //輸出第一張和最後一張圖,檢測讀取數據無誤
 53         Mat s = Mat::zeros(n_rows, n_rows * n_cols, CV_32FC1);
 54         Mat e = Mat::zeros(n_rows, n_rows * n_cols, CV_32FC1);
 55 
 56         cout << "開始讀取Image數據......\n";
 57         start_time = clock();
 58         DataMat = Mat::zeros(number_of_images, n_rows * n_cols, CV_32FC1);
 59         for (int i = 0; i < number_of_images; i++) {
 60             for (int j = 0; j < n_rows * n_cols; j++) {
 61                 unsigned char temp = 0;
 62                 file.read((char*)&temp, sizeof(temp));
 63                 float pixel_value = float((temp + 0.0) / 255.0);
 64                 DataMat.at<float>(i, j) = pixel_value;
 65 
 66                 //打印第一張和最後一張圖像數據
 67                 if (i == 0) {
 68                     s.at<float>(j / n_cols, j % n_cols) = pixel_value;
 69                 }
 70                 else if (i == number_of_images - 1) {
 71                     e.at<float>(j / n_cols, j % n_cols) = pixel_value;
 72                 }
 73             }
 74         }
 75         end_time = clock();
 76         cost_time = (end_time - start_time) / CLOCKS_PER_SEC;
 77         cout << "讀取Image數據完畢......" << cost_time << "s\n";
 78 
 79         imshow("first image", s);
 80         imshow("last image", e);
 81         waitKey(0);
 82     }
 83     file.close();
 84     return DataMat;
 85 }
 86 
 87 Mat read_mnist_label(const string fileName) {
 88     int magic_number;
 89     int number_of_items;
 90 
 91     Mat LabelMat;
 92 
 93     ifstream file(fileName, ios::binary);
 94     if (file.is_open())
 95     {
 96         cout << "成功打開Label集 ... \n";
 97 
 98         file.read((char*)&magic_number, sizeof(magic_number));
 99         file.read((char*)&number_of_items, sizeof(number_of_items));
100         magic_number = reverseInt(magic_number);
101         number_of_items = reverseInt(number_of_items);
102 
103         cout << "MAGIC NUMBER = " << magic_number << "  ; NUMBER OF ITEMS = " << number_of_items << endl;
104 
105         //-test-
106         //number_of_items = testNum;
107         //記錄第一個label和最後一個label
108         unsigned int s = 0, e = 0;
109 
110         cout << "開始讀取Label數據......\n";
111         start_time = clock();
112         LabelMat = Mat::zeros(number_of_items, 1, CV_32SC1);
113         for (int i = 0; i < number_of_items; i++) {
114             unsigned char temp = 0;
115             file.read((char*)&temp, sizeof(temp));
116             LabelMat.at<unsigned int>(i, 0) = (unsigned int)temp;
117 
118             //打印第一個和最後一個label
119             if (i == 0) s = (unsigned int)temp;
120             else if (i == number_of_items - 1) e = (unsigned int)temp;
121         }
122         end_time = clock();
123         cost_time = (end_time - start_time) / CLOCKS_PER_SEC;
124         cout << "讀取Label數據完畢......" << cost_time << "s\n";
125 
126         cout << "first label = " << s << endl;
127         cout << "last label = " << e << endl;
128     }
129     file.close();
130     return LabelMat;
131 }
mnist.cpp
  1 /*
  2 svm_type –
  3 指定SVM的類型,下面是可能的取值:
  4 CvSVM::C_SVC C類支持向量分類機。 n類分組  (n \geq 2),容許用異常值懲罰因子C進行不徹底分類。
  5 CvSVM::NU_SVC \nu類支持向量分類機。n相似然不徹底分類的分類器。參數爲 \nu 取代C(其值在區間【0,1】中,nu越大,決策邊界越平滑)。
  6 CvSVM::ONE_CLASS 單分類器,全部的訓練數據提取自同一個類裏,而後SVM創建了一個分界線以分割該類在特徵空間中所佔區域和其它類在特徵空間中所佔區域。
  7 CvSVM::EPS_SVR \epsilon類支持向量迴歸機。訓練集中的特徵向量和擬合出來的超平面的距離須要小於p。異常值懲罰因子C被採用。
  8 CvSVM::NU_SVR \nu類支持向量迴歸機。 \nu 代替了 p。
  9 
 10 可從 [LibSVM] 獲取更多細節。
 11 
 12 kernel_type –
 13 SVM的內核類型,下面是可能的取值:
 14 CvSVM::LINEAR 線性內核。沒有任何向映射至高維空間,線性區分(或迴歸)在原始特徵空間中被完成,這是最快的選擇。K(x_i, x_j) = x_i^T x_j.
 15 CvSVM::POLY 多項式內核: K(x_i, x_j) = (\gamma x_i^T x_j + coef0)^{degree}, \gamma > 0.
 16 CvSVM::RBF 基於徑向的函數,對於大多數狀況都是一個較好的選擇: K(x_i, x_j) = e^{-\gamma ||x_i - x_j||^2}, \gamma > 0.
 17 CvSVM::SIGMOID Sigmoid函數內核:K(x_i, x_j) = \tanh(\gamma x_i^T x_j + coef0).
 18 
 19 degree – 內核函數(POLY)的參數degree。
 20 
 21 gamma – 內核函數(POLY/ RBF/ SIGMOID)的參數\gamma。
 22 
 23 coef0 – 內核函數(POLY/ SIGMOID)的參數coef0。
 24 
 25 Cvalue – SVM類型(C_SVC/ EPS_SVR/ NU_SVR)的參數C。
 26 
 27 nu – SVM類型(NU_SVC/ ONE_CLASS/ NU_SVR)的參數 \nu。
 28 
 29 p – SVM類型(EPS_SVR)的參數 \epsilon。
 30 
 31 class_weights – C_SVC中的可選權重,賦給指定的類,乘以C之後變成 class\_weights_i * C。因此這些權重影響不一樣類別的錯誤分類懲罰項。權重越大,某一類別的誤分類數據的懲罰項就越大。
 32 
 33 term_crit – SVM的迭代訓練過程的停止條件,解決部分受約束二次最優問題。您能夠指定的公差和/或最大迭代次數。
 34 
 35 */
 36 
 37 
 38 #include "mnist.h"
 39 
 40 #include <opencv2/core.hpp>
 41 #include <opencv2/imgproc.hpp>
 42 #include "opencv2/imgcodecs.hpp"
 43 #include <opencv2/highgui.hpp>
 44 #include <opencv2/ml.hpp>
 45 
 46 #include <string>
 47 #include <iostream>
 48 
 49 using namespace std;
 50 using namespace cv;
 51 using namespace cv::ml;
 52 
 53 string trainImage = "mnist_dataset/train-images.idx3-ubyte";
 54 string trainLabel = "mnist_dataset/train-labels.idx1-ubyte";
 55 string testImage = "mnist_dataset/t10k-images.idx3-ubyte";
 56 string testLabel = "mnist_dataset/t10k-labels.idx1-ubyte";
 57 //string testImage = "mnist_dataset/train-images.idx3-ubyte";
 58 //string testLabel = "mnist_dataset/train-labels.idx1-ubyte";
 59 
 60 //計時器
 61 double cost_time_;
 62 clock_t start_time_;
 63 clock_t end_time_;
 64 
 65 int main()
 66 {
 67     
 68     //--------------------- 1. Set up training data ---------------------------------------
 69     Mat trainData;
 70     Mat labels;
 71     trainData = read_mnist_image(trainImage);
 72     labels = read_mnist_label(trainLabel);
 73 
 74     cout << trainData.rows << " " << trainData.cols << endl;
 75     cout << labels.rows << " " << labels.cols << endl;
 76 
 77     //------------------------ 2. Set up the support vector machines parameters --------------------
 78     Ptr<SVM> svm = SVM::create();
 79     svm->setType(SVM::C_SVC);
 80     svm->setKernel(SVM::RBF);
 81     //svm->setDegree(10.0);
 82     svm->setGamma(0.01);
 83     //svm->setCoef0(1.0);
 84     svm->setC(10.0);
 85     //svm->setNu(0.5);
 86     //svm->setP(0.1);
 87     svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));
 88 
 89     //------------------------ 3. Train the svm ----------------------------------------------------
 90     cout << "Starting training process" << endl;
 91     start_time_ = clock();
 92     svm->train(trainData, ROW_SAMPLE, labels);
 93     end_time_ = clock();
 94     cost_time_ = (end_time_ - start_time_) / CLOCKS_PER_SEC;
 95     cout << "Finished training process...cost " << cost_time_ << " seconds..." << endl;
 96     
 97     //------------------------ 4. save the svm ----------------------------------------------------
 98     svm->save("mnist_dataset/mnist_svm.xml");
 99     cout << "save as /mnist_dataset/mnist_svm.xml" << endl;
100 
101     
102     //------------------------ 5. load the svm ----------------------------------------------------
103     cout << "開始導入SVM文件...\n";
104     Ptr<SVM> svm1 = StatModel::load<SVM>("mnist_dataset/mnist_svm.xml");
105     cout << "成功導入SVM文件...\n";
106 
107 
108     //------------------------ 6. read the test dataset -------------------------------------------
109     cout << "開始導入測試數據...\n";
110     Mat testData;
111     Mat tLabel;
112     testData = read_mnist_image(testImage);
113     tLabel = read_mnist_label(testLabel);
114     cout << "成功導入測試數據!!!\n";
115 
116     
117     float count = 0;
118     for (int i = 0; i < testData.rows; i++) {
119         Mat sample = testData.row(i);
120         float res = svm1->predict(sample);
121         res = std::abs(res - tLabel.at<unsigned int>(i, 0)) <= FLT_EPSILON ? 1.f : 0.f;
122         count += res;
123     }
124     cout << "正確的識別個數 count = " << count << endl;
125     cout << "錯誤率爲..." << (10000 - count + 0.0) / 10000 * 100.0 << "%....\n";
126     
127     system("pause");
128     return 0;
129 }
main.cpp

 

 

一些網站(資料):(其實都很容易搜索到的=_=, 可是搬了人家的東西,就仍是貼一下...

http://blog.csdn.net/augusdi/article/details/9005352

http://blog.csdn.net/arthur503/article/details/19974057

http://blog.csdn.net/laihonghuan/article/details/49387237

http://docs.opencv.org/3.0-beta/modules/ml/doc/support_vector_machines.html#prediction-with-svm

http://stackoverflow.com/questions/14694810/using-opencv-and-svm-with-images?rq=1

http://docs.opencv.org/2.4/modules/ml/doc/support_vector_machines.html#cvsvm-train

http://blog.csdn.net/u010869312/article/details/44927721

http://blog.csdn.net/heroacool/article/details/50579955

http://docs.opencv.org/3.0-beta/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html

http://guyvercz.blog.163.com/blog/static/252545292011112974915402/

http://stackoverflow.com/questions/12993941/how-can-i-read-the-mnist-dataset-with-c?lq=1

相關文章
相關標籤/搜索