上一篇我们把银行卡号给分割提取出来,为我们本篇机械模型训练提供了素材,因为我们最终是要把分割出的字符图片给识别出他是具体的数字几,而不能就仅仅把截取到的图片展示出来,图片他仅仅还是图片,所以我们要知道具体的图片对应的数字,所以就用到了,机械学习中的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...