1. 程式人生 > >SVM實現手寫數字識別

SVM實現手寫數字識別

SVM簡介

    知乎上的一個回答我認為是史上最NB最形象的SVM含義解釋,想看介紹戳這裡(裡面的第一個回答),再看看百科就能知道個大概了。

開發環境

    Windows10 + VS2013 + Qt580 + OpenCV300

主要程式碼

    利用opencv-SVM演算法和Mnist資料集封裝成一個單例模式的數字識別檢測器。

DigitsDetector.h
#ifndef DIGITSDETECTOR
#define DIGITSDETECTOR

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/core/core.hpp> 
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/flann/flann.hpp>
#include <opencv.hpp>
#include <opencv2/ml/ml.hpp>

#include <iostream>
#include <string>
#include <fstream>

using namespace std;
using namespace cv;
using namespace cv::ml;

class DigitsDetector
{
public:
	static DigitsDetector *I();

public:
	Mat ReadMnistImage(const string pathName);
	Mat ReadMnistLabel(const string pathName);
	void CreateSVM();
	bool Train(Mat data, Mat label);
	float Predict(Mat testdata);

protected:
	int CvtToLittleEndian(int i);

private:
	DigitsDetector();
	~DigitsDetector();
	static DigitsDetector *inst;

	Ptr<SVM> svm;
};

#endif // !DIGITSDETECTOR

DigitsDetector.cpp
#include "DigitsDetector.h"

DigitsDetector *DigitsDetector::inst = new DigitsDetector();

DigitsDetector::DigitsDetector(){ 

}

DigitsDetector::~DigitsDetector(){

	if (inst != NULL){

		delete inst;
		inst = NULL;
	}
}

DigitsDetector *DigitsDetector::I(){

	if (inst == NULL) inst = new DigitsDetector();
	return inst;
}

Mat DigitsDetector::ReadMnistImage(const string pathName){

	int magicNumber = 0;
	int imageNumber = 0;
	int rows = 0;
	int cols = 0;
	Mat dataMat;
	ifstream file(pathName, ios::binary);
	if (file.is_open() == true){

		file.read((char*)&magicNumber, sizeof(magicNumber));
		file.read((char*)&imageNumber, sizeof(imageNumber));
		file.read((char*)&rows, sizeof(rows));
		file.read((char*)&cols, sizeof(cols));
		magicNumber = CvtToLittleEndian(magicNumber);
		imageNumber = CvtToLittleEndian(imageNumber);
		rows = CvtToLittleEndian(rows);
		cols = CvtToLittleEndian(cols);

		// 每張數字影象為一個一維向量,構成imageNumber * (rows * cols)的矩陣
		dataMat = Mat::zeros(imageNumber, rows * cols, CV_32FC1);
		for (int i = 0; i < imageNumber / 1; i++){

			for (int j = 0; j < rows * cols; j++){

				unsigned char temp = 0;
				file.read((char*)&temp, sizeof(temp));
				float value = float((temp + 0.0) / 255.0);
				dataMat.at<float>(i, j) = value;
			}
		}
	}
	file.close();
	return dataMat;
}

Mat DigitsDetector::ReadMnistLabel(const string pathName){

	int magicNumber;
	int labelNumber;
	Mat labelMat;
	ifstream file(pathName, ios::binary);
	if (file.is_open() == true){

		file.read((char*)&magicNumber, sizeof(magicNumber));
		file.read((char*)&labelNumber, sizeof(labelNumber));
		magicNumber = CvtToLittleEndian(magicNumber);
		labelNumber = CvtToLittleEndian(labelNumber);

		labelMat = Mat::zeros(labelNumber, 1, CV_32SC1);
		for (int i = 0; i < labelNumber / 1; i++){

			unsigned char temp = 0;
			file.read((char*)&temp, sizeof(temp));
			labelMat.at<unsigned int>(i, 0) = (unsigned int)temp;
		}
	}
	file.close();
	return labelMat;
}

int DigitsDetector::CvtToLittleEndian(int i){

	unsigned char c1, c2, c3, c4;
	c1 =i & 255;
	c2 = (i >> 8) & 255;
	c3 = (i >> 16) & 255;
	c4 = (i >> 24) & 255;
	return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
}

void DigitsDetector::CreateSVM(){

	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));
}

bool DigitsDetector::Train(Mat data, Mat label){

	return svm->train(data, ROW_SAMPLE, label);
}
float DigitsDetector::Predict(Mat testdata){

	return svm->predict(testdata);
}

實驗效果

訓練Mnist訓練集過程

    訓練過程時間有些長,需要耐心等待。我的測試環境為:windows10+i7-6700+8GRAM,debug版本的訓練時間大約10分鐘,release版本大約5分鐘,訓練過程如下圖:


測試Mnist測試集的過程和結果

    測試10000張Mnist的測試集,也需要一小段時間,測試結果的準確率為98.33%,如下圖。


測試現場手寫數字

    在畫板上利用滑鼠手寫測試數字,點選“開始識別當前數字”識別,實驗效果如下圖:


總結

    訓練過程比較耗時,但是識別率比較高(相比於KNN好不少,KNN實現可以參考上一篇文章《KNN實現手寫數字識別》)。和KNN的區別可以應用知乎上的一句神總結:svm, 就像是在河北和北京之間有一條邊界線,如果一個人居住在北京一側就預測為北京人,在河北一側,就預測為河北人。但是住在河北的北京人和住在北京的河北人就會被誤判。knn,就是物以類聚,人以群分。如果你的朋友裡大部分是北京人,就預測你也是北京人。

附件

    原始碼工程戳這裡(注:release裡面的可執行程式可以直接執行)。

更多參考

    SVM百度百科:https://baike.baidu.com/item/svm/4385807?fr=aladdin

    SVM筆試題:https://blog.csdn.net/szlcw1/article/details/52259668

    SVM最牛逼的解釋:https://www.zhihu.com/question/21094489

    SVM車牌識別:https://blog.csdn.net/u010429424/article/details/75335212

    支援向量機通俗導論(理解SVM的三層境界):https://blog.csdn.net/v_july_v/article/details/7624837

    opencv3-svm引數詳解:http://livezingy.com/svm-in-opencv3-1/