預備知識 html
下面兩個都不是必備知識,可是若是你想了解更多內容,可參考這兩篇文章。 學習
OpenCV 2.4+ C++ SVM線性不可分處理 .net
SVM劃分的意義 code
到此,咱們已經對SVM有必定的瞭解了。但是這有什麼用呢?回到上一篇文章結果圖: xml
這個結果圖的意義在於,他成功從二維劃分了分類的區域。因而若是之後,有一個新的樣本在綠色區域,那麼咱們就能夠把他當成是綠色的點。 htm
因爲這能夠像更高維度推廣,因此若是咱們能對樣品映射成高維度空間的點,當有足夠多的樣品時,咱們一樣能夠找到一個高維度的超平面劃分,使得同一類樣品的映射點在同一區域,因而當有新樣品落在這些區域是,咱們能夠把它當成是這一類型的樣本。 blog
通俗一點 圖片
可能咱們能更加通俗一點。好比咱們來識別男性和女性。 ci
咱們發現男性和女性可能頭髮長度不同,可能胸圍不同,因而咱們對樣本個體產生這樣的一種映射:
人 —> (頭髮長度, 胸圍)
因而咱們將每一個樣品映射到二維平面,其中「頭髮的長度」和「胸圍的長度」分別是x軸和y軸。咱們把這些樣品丟給SVM學習,則他會尋找出一個合理的x和y的區域來劃分男性和女性。
固然,也有可能有些男的頭髮比女的還長,有的男性的胸圍比女性還大,這些就是錯分點,它們也影響着劃分。
最後,當咱們把一我的映射到這個二維空間時,SVM就能夠根據以往的學習,猜一猜這我的究竟是什麼性別。
咱們學到了什麼呢?
好吧,特徵要找準一點,不然可能遇到下面的悲劇……
若是這是老闆,你就可死翹翹了……
簡單的文字識別
固然計算機沒那麼厲害能看出你的胸圍或者頭髮長短。他須要一些他能讀懂的東西,特別計算機一般「看到」的是下面的這種東東……
咱們須要對文字找到他的特徵,來映射到高維空間。
還記得小學時候練字的米字格麼?這彷佛暗示了咱們,雖然每一個人寫的字千差萬別,可是他們卻具備必定的特色。
咱們嘗試這樣作,取一個字,選取一個包含該字的正方形區域,將這個正方形區域分割成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(),fx和fy算出。dst的類型和src相同。
- dsize –
輸出圖像的大小,若是取值爲0,則:
dsize或者fx和fy必須有一種大小決定方法不爲0。
- fx –
水平軸縮放因子,當取值爲0時,則爲:
- fy –
垂直軸縮放因子,當取值爲0時,則爲:
- 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