學習SVM(一) SVM模型訓練與分類的OpenCV實現
簡介
學習SVM(一) SVM模型訓練與分類的OpenCV實現
學習SVM(二) 如何理解支援向量機的最大分類間隔
學習SVM(三)理解SVM中的對偶問題
學習SVM(四) 理解SVM中的支援向量(Support Vector)
學習SVM(五)理解線性SVM的鬆弛因子
Andrew Ng 在史丹佛大學的機器學習公開課上這樣評價支援向量機:
support vector machines is the supervised learning algorithm that many people consider the most effective off-the-shelf supervised learning algorithm.That point of view is debatable,but there are many people that hold that point of view.
可見,在監督學習演算法中支援向量機有著非常廣泛的應用,而且在解決影像分類問題時有著優異的效果。
OpenCV整合了這種學習演算法,它被包含在ml模組下的CvSVM類中,下面我們用OpenCV實現SVM的資料準備、模型訓練和載入模型實現分類,為了理解起來更加直觀,我們用三個工程來實現。
資料準備
在OpenCV的安裝路徑下,搜尋digits,可以得到一張圖片,圖片大小為1000*2000,有0-9的10個數字,每5行為一個數字,總共50行,共有5000個手寫數字,每個數字塊大小為20*20。 下面將把這些數字中的0和1作為二分類的準備資料。其中0有500張,1有500張。
用下面的程式碼將圖片準備好,在寫入路徑提前建立好資料夾:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
char ad[128]={0};
int filename = 0,filenum=0;
Mat img = imread("digits.png");
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
int b = 20;
int m = gray.rows / b; //原圖為1000*2000
int n = gray.cols / b; //裁剪為5000個20*20的小圖塊
for (int i = 0; i < m; i++)
{
int offsetRow = i*b; //行上的偏移量
if(i%5==0&&i!=0)
{
filename++;
filenum=0;
}
for (int j = 0; j < n; j++)
{
int offsetCol = j*b; //列上的偏移量
sprintf_s(ad, "D:\\data\\%d\\%d.jpg",filename,filenum++);
//擷取20*20的小塊
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
imwrite(ad,tmp);
}
}
return 0;
}
最後可以得到這樣的結果:
組織的二分類資料形式為:
--D:
--data
--train_image
--0(400張)
--1(400張)
--test_image
--0(100張)
--1(100張)
訓練資料800張,0,1各400張;測試資料200張,0,1各100張
模型訓練
資料準備完成之後,就可以用下面的程式碼訓練了:
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h>
using namespace std;
using namespace cv;
void getFiles( string path, vector<string>& files);
void get_1(Mat& trainingImages, vector<int>& trainingLabels);
void get_0(Mat& trainingImages, vector<int>& trainingLabels);
int main()
{
//獲取訓練資料
Mat classes;
Mat trainingData;
Mat trainingImages;
vector<int> trainingLabels;
get_1(trainingImages, trainingLabels);
get_0(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
//配置SVM訓練器引數
CvSVMParams SVM_params;
SVM_params.svm_type = CvSVM::C_SVC;
SVM_params.kernel_type = CvSVM::LINEAR;
SVM_params.degree = 0;
SVM_params.gamma = 1;
SVM_params.coef0 = 0;
SVM_params.C = 1;
SVM_params.nu = 0;
SVM_params.p = 0;
SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
//訓練
CvSVM svm;
svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
//儲存模型
svm.save("svm.xml");
cout<<"訓練好了!!!"<<endl;
getchar();
return 0;
}
void getFiles( string path, vector<string>& files )
{
long hFile = 0;
struct _finddata_t fileinfo;
string p;
if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) != -1)
{
do
{
if((fileinfo.attrib & _A_SUBDIR))
{
if(strcmp(fileinfo.name,".") != 0 && strcmp(fileinfo.name,"..") != 0)
getFiles( p.assign(path).append("\\").append(fileinfo.name), files );
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
}
}while(_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
char * filePath = "D:\\data\\train_image\\1";
vector<string> files;
getFiles(filePath, files );
int number = files.size();
for (int i = 0;i < number;i++)
{
Mat SrcImage=imread(files[i].c_str());
SrcImage= SrcImage.reshape(1, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(1);
}
}
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
char * filePath = "D:\\data\\train_image\\0";
vector<string> files;
getFiles(filePath, files );
int number = files.size();
for (int i = 0;i < number;i++)
{
Mat SrcImage=imread(files[i].c_str());
SrcImage= SrcImage.reshape(1, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(0);
}
}
整個訓練過程可以分為一下幾個部分:
資料準備:
該例程中一個定義了三個子程式用來實現資料準備工作:
getFiles()用來遍歷資料夾下所有檔案,可以參考:
http://blog.csdn.net/chaipp0607/article/details/53914954
getBubble()用來獲取有氣泡的圖片和與其對應的Labels,該例程將Labels定為1。
getNoBubble()用來獲取沒有氣泡的圖片與其對應的Labels,該例程將Labels定為0。
getBubble()與getNoBubble()將獲取一張圖片後會將圖片(特徵)寫入到容器中,緊接著會將標籤寫入另一個容器中,這樣就保證了特徵和標籤是一一對應的關係push_back(0)
或者push_back(1)
其實就是我們貼標籤的過程。
trainingImages.push_back(SrcImage);
trainingLabels.push_back(0);
在主函式中,將getBubble()與getNoBubble()寫好的包含特徵的矩陣拷貝給trainingData,將包含標籤的vector容器進行型別轉換後拷貝到trainingLabels裡,至此,資料準備工作完成,trainingData與trainingLabels就是我們要訓練的資料。
Mat classes;
Mat trainingData;
Mat trainingImages;
vector<int> trainingLabels;
getBubble(trainingImages, trainingLabels);
getNoBubble(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
特徵選取
其實特徵提取和資料的準備是同步完成的,我們最後要訓練的也是正負樣本的特徵。本例程中同樣在getBubble()與getNoBubble()函式中完成特徵提取工作,只是我們簡單粗暴將整個圖的所有畫素作為了特徵,因為我們關注更多的是整個的訓練過程,所以選擇了最簡單的方式完成特徵提取工作,除此中外,特徵提取的方式有很多,比如LBP,HOG等等。
SrcImage= SrcImage.reshape(1, 1);
我們利用reshape()函式完成特徵提取,原型如下:
Mat reshape(int cn, int rows=0) const;
可以看到該函式的引數非常簡單,cn為新的通道數,如果cn = 0,表示通道數不會改變。引數rows為新的行數,如果rows = 0,表示行數不會改變。我們將引數定義為reshape(1, 1)的結果就是原影像對應的矩陣將被拉伸成一個一行的向量,作為特徵向量。
引數配置
引數配置是SVM的核心部分,在Opencv中它被定義成一個結構體型別,如下:
struct CV_EXPORTS_W_MAP CvSVMParams
{
CvSVMParams();
CvSVMParams(
int svm_type,
int kernel_type,
double degree,
double coef0,
double Cvalue,
double p,
CvMat* class_weights,
CvTermCriteria term_crit );
CV_PROP_RW int svm_type;
CV_PROP_RW int kernel_type;
CV_PROP_RW double degree; // for poly
CV_PROP_RW double gamma; // for poly/rbf/sigmoid
CV_PROP_RW double coef0; // for poly/sigmoid
CV_PROP_RW double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR
CV_PROP_RW double nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR
CV_PROP_RW double p; // for CV_SVM_EPS_SVR
CvMat* class_weights; // for CV_SVM_C_SVC
CV_PROP_RW CvTermCriteria term_crit; // termination criteria
};
所以在例程中我們定義了一個結構體變數用來配置這些引數,而這個變數也就是CVSVM類中train函式的第五個引數,下面對引數進行說明。 SVM_params.svm_type
:SVM的型別: C_SVC
表示SVM分類器,C_SVR
表示SVM迴歸 SVM_params.kernel_type
:核函式型別
線性核LINEAR
:
d(x,y)=(x,y)
多項式核POLY
:
d(x,y)=(gamma*(x’y)+coef0)degree
徑向基核RBF
:
d(x,y)=exp(-gamma*|x-y|^2)
sigmoid核SIGMOID
:
d(x,y)= tanh(gamma*(x’y)+ coef0)
SVM_params.degree:核函式中的引數degree,針對多項式核函式;
SVM_params.gama:核函式中的引數gamma,針對多項式/RBF/SIGMOID核函式;
SVM_params.coef0:核函式中的引數,針對多項式/SIGMOID核函式;
SVM_params.c:SVM最優問題引數,設定C-SVC
,EPS_SVR
和NU_SVR
的引數;
SVM_params.nu:SVM最優問題引數,設定NU_SVC
, ONE_CLASS
和NU_SVR
的引數;
SVM_params.p:SVM最優問題引數,設定EPS_SVR
中損失函式p的值.
訓練模型
CvSVM svm;
svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
通過上面的過程,我們準備好了待訓練的資料和訓練需要的引數,其實可以理解為這個準備工作就是在為svm.train()函式準備實參的過程。來看一下svm.train()函式,Opencv將SVM封裝成CvSVM庫,這個庫是基於臺灣大學林智仁(Lin Chih-Jen)教授等人開發的LIBSVM封裝的,由於篇幅限制,不再全部貼上庫的定義,所以一下程式碼只是CvSVM庫中的一部分資料和函式:
class CV_EXPORTS_W CvSVM : public CvStatModel
{
public:
virtual bool train(
const CvMat* trainData,
const CvMat* responses,
const CvMat* varIdx=0,
const CvMat* sampleIdx=0,
CvSVMParams params=CvSVMParams() );
virtual float predict(
const CvMat* sample,
bool returnDFVal=false ) const;
我們就是應用類中定義的train函式完成模型訓練工作。
儲存模型
svm.save("svm.xml");
儲存模型只有一行程式碼,利用save()函式,我們看下它的定義:
CV_WRAP virtual void save( const char* filename, const char* name=0 ) const;
該函式被定義在CvStatModel類中,CvStatModel是ML庫中的統計模型基類,其他 ML 類都是從這個類中繼承。
總結:到這裡我們就完成了模型訓練工作,可以看到真正用於訓練的程式碼其實很少,OpenCV最支援向量機的封裝極大地降低了我們的程式設計工作。
載入模型實現分類
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h>
using namespace std;
using namespace cv;
void getFiles( string path, vector<string>& files );
int main()
{
int result = 0;
char * filePath = "D:\\data\\test_image\\0";
vector<string> files;
getFiles(filePath, files );
int number = files.size();
cout<<number<<endl;
CvSVM svm;
svm.clear();
string modelpath = "svm.xml";
FileStorage svm_fs(modelpath,FileStorage::READ);
if(svm_fs.isOpened())
{
svm.load(modelpath.c_str());
}
for (int i = 0;i < number;i++)
{
Mat inMat = imread(files[i].c_str());
Mat p = inMat.reshape(1, 1);
p.convertTo(p, CV_32FC1);
int response = (int)svm.predict(p);
if (response == 0)
{
result++;
}
}
cout<<result<<endl;
getchar();
return 0;
}
void getFiles( string path, vector<string>& files )
{
long hFile = 0;
struct _finddata_t fileinfo;
string p;
if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) != -1)
{
do
{
if((fileinfo.attrib & _A_SUBDIR))
{
if(strcmp(fileinfo.name,".") != 0 && strcmp(fileinfo.name,"..") != 0)
getFiles( p.assign(path).append("\\").append(fileinfo.name), files );
}
else
{ files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
}
}while(_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
在上面我們把該介紹的都說的差不多了,這個例程中只是用到了load()函式用於模型載入,載入的就是上面例子中生成的模型,load()
被定義在CvStatModel
這個基類中:
svm.load(modelpath.c_str());
load的路徑是string modelpath = "svm.xml"
,這意味著svm.mxl檔案應該在測試工程的根目錄下面,但是因為訓練和預測是兩個獨立的工程,所以必須要拷貝一下這個檔案。最後用到predict()
函式用來預測分類結果,predict()
被定義在CVSVM
類中。
注意:
1.為什麼要建立三個獨立的工程呢?
主要是考慮寫在一起話,程式碼量會比較大,邏輯沒有分開清晰,當跑通上面的程式碼之後,就可以隨意的改了。
2.為什麼加上資料準備?
之前有評論說道資料的問題,提供資料後實驗能更順利一些,因為本身程式碼沒有什麼含金量,這樣可以更順利的執行起來工程,並修改它。
3.一些容易引起異常的情況:
(1):注意生成的.xml記得拷貝到預測工程下;
(2):注意準備好資料路徑和程式碼是不是一致;
(3):注意訓練的特徵要和測試的特徵一致;
相關文章
- opencv svm分類OpenCV
- Opencv中SVM樣本訓練、歸類流程及實現OpenCV
- opencv SVM分類DemoOpenCV
- opencv中的SVM影像分類(一)OpenCV
- 【Svm機器學習篇】Opencv3.4.1與C++實現對分類問題的訓練與預測】機器學習OpenCVC++
- 學習OpenCV——SVMOpenCV
- Opencv 用SVM訓練檢測器OpenCV
- opencv中的SVM影像分類(二)OpenCV
- OpenCV 與 SVMOpenCV
- 『sklearn學習』不同的 SVM 分類器
- 基於Theano的深度學習框架keras及配合SVM訓練模型深度學習框架Keras模型
- 自己訓練SVM分類器進行HOG行人檢測HOG
- SVM多分類器的實現(Opencv3,C++)OpenCVC++
- opencv SVM的使用OpenCV
- OpenCV的SVM用法OpenCV
- 用Python實現一個SVM分類器策略Python
- opencv中SVMOpenCV
- opencv SVM 使用OpenCV
- Spark MLlib SVM 文字分類器實現Spark文字分類
- 學習Opencv2.4.9(四)---SVM支援向量機OpenCV
- opencv + SVM 程式碼OpenCV
- OpenCV筆記(3)實現支援向量機(SVM)OpenCV筆記
- 【機器學習PAI實踐十】深度學習Caffe框架實現影象分類的模型訓練機器學習AI深度學習框架模型
- SVM實現多分類的三種方案
- opencv中svm原始碼OpenCV原始碼
- 學習SVM(五)理解線性SVM的鬆弛因子
- 學習SVM(四) 理解SVM中的支援向量(Support Vector)
- 【opencv3】 svm實現手寫體與人臉識別OpenCV
- 我的OpenCV學習筆記(六):使用支援向量機(SVM)OpenCV筆記
- 使用sklearn實現svm--用於機械故障分類
- 用初次訓練的SVM+HOG分類器在負樣本原圖上檢測HardExampleHOG
- python中的scikit-learn庫來實現SVM分類器。Python
- OpenCV進階---介紹SVMOpenCV
- OpenCV中使用SVM簡介OpenCV
- 機器學習實戰-SVM模型實現人臉識別機器學習模型
- OpenCV中的SVM引數優化OpenCV優化
- 分類演算法-支援向量機 SVM演算法
- 樸素貝葉斯/SVM文字分類文字分類