上一篇我們把銀行卡號給分割提取出來,爲我們本篇機械模型訓練提供了素材,因爲我們最終是要把分割出的字符圖片給識別出他是具體的數字幾,而不能就僅僅把截取到的圖片展示出來,圖片他僅僅還是圖片,所以我們要知道具體的圖片對應的數字,所以就用到了,機械學習中的SVM 支持向量積的分類器。
百度百科支持向量機(Support Vector Machine, SVM)是一類按監督學習(supervised learning)方式對數據進行二元分類(binary classification)的廣義線性分類器(generalized linear classifier),其決策邊界是對學習樣本求解的最大邊距超平面(maximum-margin hyperplane) [1-3] 。
SVM使用鉸鏈損失函數(hinge loss)計算經驗風險(empirical risk)並在求解系統中加入了正則化項以優化結構風險(structural risk),是一個具有稀疏性和穩健性的分類器 [2] 。SVM可以通過核方法(kernel method)進行非線性分類,是常見的核學習(kernel learning)方法之一 [4] 。
SVM被提出於1964年,在二十世紀90年代後得到快速發展並衍生出一系列改進和擴展算法,在人像識別(face recognition)、文本分類(text categorization)等模式識別(pattern recognition)問題中有得到應用 [5-6] 。
所以當我們拿到分割後的數字後就能拿來訓練,把數字分類,最後會生成一個xml/txt文件,給我們用來識別。
通過上一節我們得到的圖片數字,我們先把她們分類把0,1,2,3...單獨分開來訓練標記。
1.數據訓練
首先我們現創建一個集合 vector<vector<Mat>> vm;保存我們的圖片數字。例如:
我們可以通過for循環用imread(“path/img_name.png”);讀取我們所有的圖片到集合中
vector<Mat> img0;保存我們的 所有截取到的0的圖片。
vector<Mat> img1;保存我們的 所有截取到的1的圖片。
.
.
.
一次類推保存我們的0,1,2,3....。
最後再把這些放進 vm 集合中。
從內存中把圖片讀取到vm集合中後我們拿到圖片集合去訓練
vector<Mat> trainingImages; //用來存放訓練圖像信息的容器
vector<int> trainingLabels; //保存訓練的圖片的標籤
void TrimTest::startTrim(vector<vector<Mat>>& vm) {
Mat classes;
getBubble(trainingImages,trainingLabels,vm);
Mat trainingData(trainingImages.size(),trainingImages[0].cols, CV_32FC1);
for (int i = 0; i < trainingImages.size(); i++)
{
Mat temp(trainingImages[i]);
temp.copyTo(trainingData.row(i));
}
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
//以下是設置SVM訓練模型的配置
Ptr<SVM> model = SVM::create();
model->setType(SVM::C_SVC);
model->setKernel(SVM::LINEAR);
model->setGamma(1);
model->setC(1);
model->setCoef0(0);
model->setNu(0);
model->setP(0);
model->setTermCriteria(TermCriteria(1, 20000, 0.0001));
Ptr<TrainData> tdata = TrainData::create(trainingData, ROW_SAMPLE, classes);
model->train(tdata);
model->save("/storage/emulated/0/number_svm.xml");
__android_log_print(ANDROID_LOG_ERROR,"訓練完成...","over---");
}
void getBubble(vector<Mat> &tImages, vector<int> &tLabels,vector<vector<Mat>>& vm)
{
for (int j = 0; j < vm.size(); j++) {
int size = vm[j].size();
for (int i = 0; i < size; i++) {
Mat gray = vm[j][i];
Mat reshapeMat;
reshapeMat = gray.reshape(1,1);
tImages.push_back(reshapeMat);
tLabels.push_back(j);//該樣本爲數字j
}
}
}
代碼流程分析:
- 從內存中把分割好的圖片數據給read到vm集合中,等待訓練。
- 讀取完所有分類好的集合圖片,通過getBubble()把所有圖片進行分類加上標籤比如把圖片0標籤爲0,這樣程序才知道我們本次訓練的是0,就相當於我們教他學習一樣,我們告訴這是0,這是1....,.reshape(1,1);是把Mat矩陣給排列到一行。
- 把圖片和標籤都存儲到tImages和tLables集合之後,再把集合中所有Mat放到一個Mat中並且類型必須是CV_32FC1
- 然後就是拿到我們的SVM 對象配置參數
- 最後調用train(tdata);和
save("/storage/emulated/0/number_svm.xml");把訓練結果以xml形式保存到手機內存中,等待我們識別時候使用。
2.數字識別
當我們訓練好數據成功之後,就會生成一個 number_svm.xml 文件,這個就是我們識別數字時候需要的。在這裏面SVM 把我們的數據進行分類。
Mat gray;
cvtColor(mat,gray,COLOR_BGRA2GRAY);
//大小尺寸一定要和訓練的數據一致
Ptr<ml::SVM>svm =SVM::load("/storage/emulated/0/number_svm.xml");
Mat p = gray.reshape(1, 1);
p.convertTo(p, CV_32FC1);
Mat m;
// float x = svm->predict(p,m);
float x = svm->predict(p);
return x;
通過把我們分割好的圖片數字進行:
- 轉成灰度圖
- 矩陣處理成一行
- 轉成 CV_32FC1 類型
-
predict(p); 識別
識別返回來的x就是我們所要的識別結果數字了。特別要注意我們訓練的圖片的尺寸要和我們最後識別的圖片尺寸保持一致,最好保存按照png格式。
到此銀行卡識別就結束了,至於相機方面我就不寫了,比較簡單,其實就是通過相機採集我們的銀行卡/身份證區域然後傳到我們的 native 層去一系列圖像處理方式分割出數字識別的。識別的過程中有些算法和一些處理方式大家可以自行發揮,尋找最優的識別效率。其實不管怎麼樣我們都還是要努力的去學習,學的多了,會的多了,工作效率也會提高很多,也更容易去自己想去的公司工作。努力,努力 o(∩∩)o...