OpenCV 2.4+ C++ SVM文字識別


預備知識

下面兩個都不是必備知識,但是如果你想了解更多內容,可參考這兩篇文章。

OpenCV 2.4+ C++ SVM介紹

OpenCV 2.4+ C++ SVM線性不可分處理

 

SVM划分的意義

到此,我們已經對SVM有一定的了解了。可是這有什么用呢?回到上一篇文章結果圖:

Training data and decision regions given by the SVM

這個結果圖的意義在於,他成功從二維划分了分類的區域。於是如果以后,有一個新的樣本在綠色區域,那么我們就可以把他當成是綠色的點。

由於這可以像更高維度推廣,所以如果我們能對樣品映射成高維度空間的點,當有足夠多的樣品時,我們同樣可以找到一個高維度的超平面划分,使得同一類樣品的映射點在同一區域,於是當有新樣品落在這些區域是,我們可以把它當成是這一類型的樣本。

 

通俗一點

可能我們能更加通俗一點。比如我們來識別男性和女性。

我們發現男性和女性可能頭發長度不一樣,可能胸圍不一樣,於是我們對樣本個體產生這樣的一種映射:

    人 —> (頭發長度, 胸圍)

於是我們將每個樣品映射到二維平面,其中“頭發的長度”和“胸圍的長度”分別是x軸和y軸。我們把這些樣品丟給SVM學習,則他會尋找出一個合理的x和y的區域來划分男性和女性。

當然,也有可能有些男的頭發比女的還長,有的男性的胸圍比女性還大,這些就是錯分點,它們也影響着划分。

最后,當我們把一個人映射到這個二維空間時,SVM就可以根據以往的學習,猜一猜這個人到底是什么性別。

我們學到了什么呢?

好吧,特征要找准一點,否則可能遇到下面的悲劇……

如果這是老板,你可就死翹翹了……

 

簡單的文字識別

當然計算機沒那么厲害能看出你的胸圍或者頭發長短。他需要一些他能讀懂的東西,特別計算機通常“看到”的是下面的這種東東……

A matrix of the mirror of a car

我們需要對文字找到他的特征,來映射到高維空間。

還記得小學時候練字的米字格么?這似乎暗示了我們,雖然每個人寫的字千差萬別,但是他們卻具有一定的特點。

我們嘗試這樣做,取一個字,選取一個包含該字的正方形區域,將這個正方形區域分割成8*8個小格,統計每個小格中像素的數量,以這些數量為維度進行映射。

OK,明白了原理讓我們開始吧。

被提醒了,那么補充一句:這個例子在實際中用來辨認是不可行的。

 

樣本獲取

由於通常文字樣本都是白底黑字的,而手寫也可以直接獲取寫入的數據而無視背景,所以我們並不需要對樣本進行提取,但我們需要對他定位,並弄成合適的大小。

比如,你沒法避免有人這么寫字……

坑爹啊,好好的那么大地方你就躲在左上角……

 

開始定位

void getROI(Mat& src, Mat& dst){
    int left, right, top, bottom;
    left = src.cols;
    right = 0;
    top = src.rows;
    bottom = 0;

    //得到區域
    for(int i=0; i<src.rows; i++)
    {
        for(int j=0; j<src.cols; j++)
        {
            if(src.at<uchar>(i, j) > 0)
            {
                if(j<left) left = j;
                if(j>right) right = j;
                if(i<top) top = i;
                if(i>bottom) bottom = i;
            }
        }
    }

    int width = right - left;
    int height = bottom - top;

    //創建存儲矩陣
    dst = Mat::zeros(width, height, CV_8UC1);

    Rect dstRect(left, top, width, height);
    dst(dstRect);
}

這段代碼通過遍歷所有圖像矩陣的元素,來獲取該樣本的定位和大小。並把樣本提取出來。

 

重新縮放

Mat dst = Mat::zeros(8, 8, CV_8UC1);
resize(src, dst, dst.size());

進行縮放,把所有樣本變成8*8的大小。為了簡便,我們把像素多少變成了像素的灰度值。

resize的API:

調整圖片大小

C++:  void  resize (InputArray  src, OutputArray  dst, Size  dsize, double  fx=0, double  fy=0, int  interpolation=INTER_LINEAR  )
參數
  • src – 輸入圖像。
  • dst – 輸出圖像;它有一個dsize (當其不為0時) 或者這個size由 src.size(),fxfy算出dst的類型和src相同。
  • dsize –

    輸出圖像的大小,如果取值為0,則:

    \texttt{dsize = Size(round(fx*src.cols), round(fy*src.rows))}

    dsize或者fxfy必須有一種大小決定方法不為0。

  • fx –

    水平軸縮放因子,當取值為0時,則為:

    \texttt{(double)dsize.width/src.cols}

  • fy –

    垂直軸縮放因子,當取值為0時,則為:

    \texttt{(double)dsize.height/src.rows}

  • interpolation –

    插值方法

    • INTER_NEAREST - 最近鄰值插入方法。
    • INTER_LINEAR - 雙線性插值(默認方式)。
    • INTER_AREA - 使用象素關系重采樣。當圖像縮小時候,該方法可以避免波紋出現。當圖像放大時,類似於 CV_INTER_NN 方法。
    • INTER_CUBIC - 立方插值。
    • INTER_LANCZOS4 - 8x8的Lanczos插入方法。

 

准備樣本數據

Mat data = Mat::zeros(total, 64, CV_32FC1);    //樣本數據矩陣  
Mat res = Mat::zeros(total, 1, CV_32SC1);    //樣本標簽矩陣

res.at<double>(k, 1) = label;    //對第k個樣本添加分類標簽

//對第k個樣本添加數據
for(int i = 0; i<8; i++)  {  
    for(int j = 0; j<8; j++)  {  
        res.at<double>(k, i * 8 + j) = dst.at<double>(i, j);  
    }  
}

將剛剛的結果,輸入樣本,並加上標簽。

 

訓練

CvSVM svm = CvSVM();   
CvSVMParams param;   
CvTermCriteria criteria;  
  
criteria= cvTermCriteria(CV_TERMCRIT_EPS, 1000,  FLT_EPSILON);   
param= CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 8.0, 1.0, 10.0, 0.5, 0.1, NULL, criteria);   
  
svm.train(data, res, Mat(), Mat(), param);  
svm.save( "SVM_DATA.xml" );  

開始訓練並保存訓練數據。

 

使用

CvSVM svm = CvSVM();   
svm.load( "SVM_DATA.xml" );
svm.predict(m);        //對樣本向量m檢測

 

 參考資料

 使用OPENCV訓練手寫數字識別分類器 . firefight . 2011-05-28


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM