稍微瞭解支持向量機器 (Support Vector Machine)和非線性支持向量機器 (Non-linear SVMs)後,來使用OpenCV做個小小的實驗。
參考網路上的程式碼,它做了二個特徵(也就是X軸和Y軸)的分類,所以可以直接在影像上看到結果,而且分類的分隔線是筆直的線條。
這裡使用三個類別(紅、綠、藍),設定50個sample(然而程式平均每個類別產生18個sample),程式執行後產生6個支持向量,顯示維度500*700的影像。
程式碼:
/**
Theme: SVM Experiment
Compiler: Dev C++ 4.9.9.2
Library: OpenCV 2.0
Date: 101/11/04
Author: HappyMan
Blog: https://cg2010studio.wordpress.com/
*/
#include "ml.h"
#include "highgui.h"
#define TRAIN_SAMPLE_COUNT 50
#define SIGMA 60
int main( int argc, char** argv )
{
//Setup Matrices for TrainData set and Class Labels.
//Most of OpenCV Machine Learning algorithms accept CV_32FC1 matrix format as their input/ouput
CvMat *trainClass=cvCreateMat(TRAIN_SAMPLE_COUNT,1,CV_32FC1);
CvMat *trainData=cvCreateMat(TRAIN_SAMPLE_COUNT,2,CV_32FC1);
// cvCreateMat(列數數據,行數數據,CvMat資料結構參數)
//Creating a image to represent outputs
IplImage *frame = cvCreateImage(cvSize(500,700), IPL_DEPTH_8U, 3);
//a vector to use for predicting data
CvMat *sample=cvCreateMat(1,2,CV_32FC1);
//Setting up Train Data
CvMat subtrainData;
cvGetRows(trainData,&subtrainData,0,TRAIN_SAMPLE_COUNT/3);
CvRNG rng_state = cvRNG(-1);
// cvGetTickCount()系統時間數據
// cvRNG()亂數產生器
CvMat trainData_col;
cvGetCols(&subtrainData,&trainData_col,0,1);
cvRandArr(&rng_state,&trainData_col,CV_RAND_NORMAL,cvScalar(100),cvScalar(SIGMA));
cvGetCols(&subtrainData,&trainData_col,1,2);
cvRandArr(&rng_state,&trainData_col,CV_RAND_NORMAL,cvScalar(300),cvScalar(SIGMA));
// cvRandArr(CvRNG資料結構,IplImage或CvMat資料結構,均勻分佈參數,隨機範圍下限,隨機範圍上限)
// cvRandArr(CvRNG資料結構,IplImage或CvMat資料結構,常態分佈參數,平均數,標準差)
// cvGetRows(IplImage資料結構或CvMat資料結構,空的CvMat資料結構,開始列數Int型別,結束列數Int型別)
// cvGetCols(IplImage資料結構或CvMat資料結構,空的CvMat資料結構,開始欄數Int型別,結束欄數Int型別)
cvGetRows(trainData,&subtrainData,TRAIN_SAMPLE_COUNT/3,2*TRAIN_SAMPLE_COUNT/3);
cvRandArr(&rng_state,&subtrainData,CV_RAND_NORMAL,cvScalar(400),cvScalar(SIGMA));
cvGetRows(trainData,&subtrainData,2*TRAIN_SAMPLE_COUNT/3,TRAIN_SAMPLE_COUNT);
cvGetCols(&subtrainData,&trainData_col,0,1);
cvRandArr(&rng_state,&trainData_col,CV_RAND_NORMAL,cvScalar(300),cvScalar(SIGMA));
cvGetCols(&subtrainData,&trainData_col,1,2);
cvRandArr(&rng_state,&trainData_col,CV_RAND_NORMAL,cvScalar(100),cvScalar(SIGMA));
//Setting up train classes
CvMat subclassData;
cvGetRows(trainClass,&subclassData,0,TRAIN_SAMPLE_COUNT/3);
cvSet(&subclassData,cvScalar(1));
cvGetRows(trainClass,&subclassData,TRAIN_SAMPLE_COUNT/3,2*TRAIN_SAMPLE_COUNT/3);
cvSet(&subclassData,cvScalar(2));
cvGetRows(trainClass,&subclassData,2*TRAIN_SAMPLE_COUNT/3,TRAIN_SAMPLE_COUNT);
cvSet(&subclassData,cvScalar(3));
//Setting up SVM parameters
CvSVMParams params;
params.kernel_type=CvSVM::LINEAR;
params.svm_type=CvSVM::C_SVC;
params.C=1;
params.term_crit=cvTermCriteria(CV_TERMCRIT_ITER,100,0.000001);
CvSVM svm;
//Training the model
bool res=svm.train(trainData,trainClass,cv::Mat(),cv::Mat(),params);
//using the model to to pridict some data
for (int i = 0; i < frame->height; i++)
{
for (int j = 0; j < frame->width; j++)
{
//setting sample data values
*((float*)CV_MAT_ELEM_PTR(*sample,0,0)) = j;
*((float*)CV_MAT_ELEM_PTR(*sample,0,1)) = i;
float response = svm.predict(sample);
uchar *ptr = (uchar *) (frame->imageData + i * frame->widthStep);
//checking class labels against predicted class.
if(response == 1)
{
ptr[3*j]= 255;
ptr[3*j+1] = 100;
ptr[3*j+2] = 100;
}
if(response == 2)
{
ptr[3*j]= 100;
ptr[3*j+1] = 255;
ptr[3*j+2] = 100;
}
if(response == 3)
{
ptr[3*j]= 100;
ptr[3*j+1] = 100;
ptr[3*j+2] = 255;
}
}
}
//making all sample points visible on the image.
for (int i = 0; i < (TRAIN_SAMPLE_COUNT / 3); i++)
{
CvPoint2D32f p1 = cvPoint2D32f(CV_MAT_ELEM(*trainData,float,i,0),CV_MAT_ELEM(*trainData,float,i,1));
cvDrawCircle(frame,cvPointFrom32f(p1),2,cvScalar(255, 0, 0),-1);
CvPoint2D32f p2 = cvPoint2D32f(CV_MAT_ELEM(*trainData,float,TRAIN_SAMPLE_COUNT / 3+i,0),CV_MAT_ELEM(*trainData,float,TRAIN_SAMPLE_COUNT / 3+i,1));
cvDrawCircle(frame,cvPointFrom32f(p2),2,cvScalar(0, 255, 0),-1);
CvPoint2D32f p3 = cvPoint2D32f(CV_MAT_ELEM(*trainData,float,2*TRAIN_SAMPLE_COUNT / 3+i,0),CV_MAT_ELEM(*trainData,float,2*TRAIN_SAMPLE_COUNT / 3+i,1));
cvDrawCircle(frame,cvPointFrom32f(p3),2,cvScalar(0, 0, 255),-1);
}
//Showing support vectors
int c = svm.get_support_vector_count();
for (int i = 0; i < c; i++)
{
const float *v = svm.get_support_vector(i);
CvPoint2D32f p1 = cvPoint2D32f(v[0], v[1]);
cvDrawCircle(frame,cvPointFrom32f(p1),4,cvScalar(128, 128, 128),2);
}
cvNamedWindow( "SVM Tutorial", CV_WINDOW_AUTOSIZE );
cvShowImage( "SVM Tutorial", frame );
cvWaitKey();
}
執行結果:

接下來我將影像維度為500*500,設定cvTermCriteria的max_iter。
typedef struct CvTermCriteria
{
int type; /* CV_TERMCRIT_ITER 和CV_TERMCRIT_EPS二值之一,或者二者的組合 */
int max_iter; /* 最大迭代次數 */
double epsilon; /* 結果的精確性 */
}
CvTermCriteria;
max_iter = 1

max_iter = 2

max_iter = 3

max_iter = 4

max_iter = 5

max_iter = 100

迭代過程一直在找尋最佳的支持向量,可以看到不同的max_iter變化的狀況。最後一張圖就是程式碼執行後的結果圖,只不過切掉下面那個支持向量。
均勻分佈參數和常態分佈參數可參考:產生亂數影像 (Generate Random Number for Image)。
本程式的重點在於CvSVMParams參數的設置,可參考OpenCV官方文件:CvSVMParams::CvSVMParams、中文網站:CvSVMParams。
struct CvSVMParams
{
CvSVMParams();
CvSVMParams( int _svm_type, int _kernel_type,
double _degree, double _gamma, double _coef0,
double _C, double _nu, double _p,
CvMat* _class_weights, CvTermCriteria _term_crit );
int svm_type;
int kernel_type;
double degree; // for poly
double gamma; // for poly/rbf/sigmoid
double coef0; // for poly/sigmoid
double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR
double nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR
double p; // for CV_SVM_EPS_SVR
CvMat* class_weights; // for CV_SVM_C_SVC
CvTermCriteria term_crit; // termination criteria
};
svm_type,SVM的類型:
- CvSVM::C_SVC – n(n>=2)分類器,允許用異常值懲罰因數C進行不完全分類。
- CvSVM::NU_SVC – n類似然不完全分類的分類器。參數nu取代了c,其值在區間[0, 1]中,nu越大,決策邊界越平滑。
- CvSVM::ONE_CLASS – 單分類器,所有的訓練數據提取自同一個類里,然後SVM建立了一個分界線以分割該類在特徵空間中所占區域和其它類在特徵空間中所占區域。
- CvSVM::EPS_SVR – 回歸。 訓練集中的特徵向量和擬合出來的超平面的距離需要小於p。異常值懲罰因數C被採用。
- CvSVM::NU_SVR – 回歸;nu 代替了p。
kernel_type,核類型:
- CvSVM::LINEAR – 沒有任何向映射至高維空間,線性區分(或回歸)在原始特徵空間中被完成,這是最快的選擇。 d(x,y) = x‧y == (x,y)
- CvSVM::POLY – 多項式核: d(x,y) = (gamma*(x‧y)+coef0)degree
- CvSVM::RBF – 徑向基,對於大多數情況都是一個較好的選擇:d(x,y) = exp(-gamma*|x-y|2)
- CvSVM::SIGMOID – sigmoid函數被用作核函數: d(x,y) = tanh(gamma*(x‧y)+coef0)
- degree, gamma, coef0:都是核函數的參數,具體的參見上面的核函數的方程。
- C, nu, p:在一般的SVM優化求解時的參數。
- class_weights:可選權重,賦給指定的類別。一般乘以C以後去影響不同類別的錯誤分類懲罰項。權重越大,某一類別的誤分類數據的懲罰項就越大。
- term_crit:SVM的迭代訓練過程的中止。(解決了部分受約束二次最優問題)
- 該結構需要初始化,並傳遞給CvSVM的訓練函數。
以上參數都可以設定來玩玩看,實在有點複雜,還是先看程式跑出來的樣子會比較有感覺呢!
參考:SVM using OpenCV。
Comments on: "[OpenCV] SVM 實驗" (6)
如果,我現在丟入一點是黑色的,並不在這三個顏色當中,有辦法判斷出來嗎?目前的想法是在SVM最後的分類結果設定一個threshold,但是如果利用openCV不知道該如何撰寫。
讚讚
很有趣的問題,可惜我就沒有多研究了~
讚讚
int c = svm.get_support_vector_count();
for (int i = 0; i < c; i++)
{
const float *v = svm.get_support_vector(i);
CvPoint2D32f p1 = cvPoint2D32f(v[0], v[1]);
cvDrawCircle(frame,cvPointFrom32f(p1),4,cvScalar(128, 128, 128),2);
}
請問為什麼get_support_vector()回傳的數值,總在圖像的左上角?(意味著X,Y(v[0],v[1])的坐標軸數值很小(接近零)。但我跑出來的圖像卻是都判斷正確
讚讚
[…] 既SVM實驗後,接著實驗高階的SVM。首先還是要瞭解支持向量機器 (Support Vector Machine)和非線性支持向量機器 (Non-linear SVMs),先前的SVM實驗也可以參考。 […]
讚讚
[…] 剛把OpenCV更新到2.4.3版,來試驗一下它的SVM好不好用。首先當然要瞭解支持向量機器 (Support Vector Machine)和非線性支持向量機器 (Non-linear SVMs),先前的SVM實驗也可以參考。 […]
讚讚
[…] 剛把OpenCV更新到2.4.3版,來試驗一下它的SVM好不好用。首先當然要瞭解支持向量機器 (Support Vector Machine)和非線性支持向量機器 (Non-linear SVMs),先前的SVM實驗也可以參考。 […]
讚讚