一、提取方法的修正
上回说道,我们使用cvFindContours函数来找银行卡上的数字的外包矩形,从而从银行卡上将数字抠下来进行识别,但是,使用后会发现有如下两个问题:
(1)不好筛选
提供的图片大小不一样,那么数字的外包矩形框的大小也就不一样,如果简单地采用面积的办法进行筛选,那么这种方法的适应性是非常的差的。
(2)外包矩形框的不确定性
我们来看看数字8的外包矩形框
不看不知道,一看吓一跳,一个数字八竟然有四个外包矩形框,这个我们的提取工作带来了非常大的不便,所以我们考虑换一种提取方式。
基于灰度变化的数字提取方法:
数字与数字的间隔往往是背景颜色或者是背景像素点个数和的一个波峰,即前景像素点个数和的一个波谷,我们利用这样的特点来分割图片。这里考虑到银行卡类型比较单一,如果将卡大小控制在一定的范围内,那么两数字之间一般会出现大部分背景像素点,即这里暂时不考虑数字与数字相连的情况。
那么首先,我们通过获得有效区域(见“再预处理”一文),得到了数字的高度,接下来,我们用上述方法得到每个数字的宽度,于是乎就能将数字抠取下来了。
二、样本模板修正
从网上下了一个手写数字的样本,天真地以为应该是可以的,随便打开一个看看,如下图所示:
大家可能觉得形状扭曲或者其他什么,而我注意到的是它的填充情况,可以发现,这个样本上下基本与图像边框相切,但是两边却非常空余,结合一下我们所采取的数字抠取得办法,得到的数字一定是上下左右都是切和图像边框的,我不禁冷汗直冒。为什么呢?因为k最近邻是通过距离来计算相似度的,那么,如下图,可以想象得到,红色的0和黑色的0在距离计算后得到的结果一定是差个十万八千里的:
所以,根据我们数字抠取得方法特点来看,我决定自己制作简单的样本,这个样本完全模拟抠取后的数字图片来,包括大小、胖瘦、粗细、上下左右切合图像边框等等,因为考虑到银行卡号是比较单一的,所以,我相信这样做是合理的!
三、关键函数实现及其结果
(1)数字分割
IplImage *Filter(IplImage*imgSrc, IplImage*src)
{//过滤图像
int a = 0, b = 0;//保存有效行号
int h = 0;
int state = 0;//标志位,0则表示还未到有效行,1则表示到了有效行,2表示搜寻完毕
for (int y = 0; y < imgSrc->height; y++)
{
int count = 0;
for (int x = 0; x < imgSrc->width; x++)
{
if (cvGet2D(imgSrc, y, x).val[0] == 0)
count = count + 1;
}
if (state == 0)//还未到有效行
{
if (count >= 10)//找到了有效行
{//有效行允许十个像素点的噪声
a = y;
state = 1;
}
}
else if (state == 1)
{
if (count <= 10)//找到了有效行
{//有效行允许十个像素点的噪声
b = y;
state = 2;
}
}
}
numX1 = a;
numX2 = b;
CvRect roi = cvRect(0, a, src->width, b - a);
IplImage *res = cvCreateImage(cvSize(roi.width, roi.height), 8, 1);
IplImage *orig = cvCreateImage(cvSize(roi.width, roi.height), 8, 1);
cvSetImageROI(src, roi);
cvCopy(src, res);
cvThreshold(res, res, 30, 255, CV_THRESH_BINARY);//24
cvErode(res, res, NULL, 1);
return res;
}
void findRect(IplImage* srcA)
{//将银行数字分割,黑点为前景点,白点为背景点。
int count = 0;
int judgeA = 0;//0代表当前要判断有效列,1代表当前要判断无效列
int i = 0;
IplImage* src = cvCreateImage(cvGetSize(srcA), 8, 1);
cvSmooth(srcA, src, CV_MEDIAN);
for (int x = 0; x < src->width; x++)
{
count = 0;
for (int y = 0; y < src->height; y++)
{
int q = ((uchar*)(src->imageData + y*src->widthStep))[x];
if (q == 0)//遇到前景点就加1
{
//cout << q << endl;
count ++;
}
}
if (judgeA == 0)
{
if (count >= 5)//有效行到了
{
judgeA = 1;
aa[i] = x;
}
}
else if(judgeA == 1)
{
if (count < 5)
{
judgeA = 0;
bb[i] = x;
i++;
}
}
}
cout << " i = " << i << endl;
for (int m = 0; m < i; m++)
{
cvLine(src, cvPoint(aa[m], 0), cvPoint(aa[m], src->height), cvScalar(0, 0, 0));
cvLine(src, cvPoint(bb[m], 0), cvPoint(bb[m], src->height), cvScalar(0, 0, 0));
}
/*cvNamedWindow("img");
cvShowImage("img", src);
cvWaitKey(0);
*/
}
分割结果:
注意:过程中灵活使用中值滤波可以有效减少噪声的干扰,特别是不可见噪声
(2)外包矩形框查找修正成resize
在上一回我们这样实现的,即先得到外包矩形框,然后再讲外包矩形框resize得到模板的尺寸。现在为了更简便和准确,我们直接resize(因为考虑到我们前面做了一系列操作,数字已经在一个合适的范围内了,不会出现大部分空白现象)
(3)顺序调整
算法实现的顺序并不是从上到下,从左到右的,而是随机的,所以我们需要在识别数字的时候讲数字的位置记录下来,最后根据位置进行数字排序,再进行显示
void sortBankNum()//对银行卡进行排序
{
int i,j,temp;
for(j=0;j<=18;j++)
{
for (i=0;i<19-j;i++)
if (bankX[i]>bankX[i+1])
{
temp=bankX[i];
bankX[i]=bankX[i+1];
bankX[i+1]=temp;
temp=bankNum[i];
bankNum[i]=bankNum[i+1];
bankNum[i+1]=temp;
}
}
}
最后银行卡识别结果:
问题:适应性,这里的适应性主要指两方面:(1)预处理适应性:不同光线情况下得到的图片处理效果截然不同,也直接影响到了识别效果;(2)样本适应性:样本是否能适应所有的银行卡,虽然理论上数字都是一样的,但是处理过后的数字特征发生了一定的变换
这将在后面进行完善!!!!!
相关推荐
3.1.2 发票、身份证、银行卡识别 3.1.3 人脸类别和表情识别 3.1.4 打靶识别 3.1.5 字符识别(字母、数字、手写体、汉字、验证码) 3.1.6 病灶识别 3.1.7 花朵、药材、水果蔬菜识别 3.1.8 指纹、手势、虹膜识别...
接下来,图像分析阶段使用机器学习算法,如支持向量机(SVM)、神经网络或者最近邻算法(K-NN),对特征向量进行分类。这些算法能够学习和理解大量的笔迹样本,建立起一个模型,用于区分不同人的笔迹。在验证阶段,...
标签中的“最近邻”(K-Nearest Neighbors, KNN)是一种常用的监督学习算法,通过查找训练集中与新样本最接近的K个邻居,根据这些邻居的类别来预测新样本的类别。KNN在手写数字识别中也是常见的方法,因为数字的邻近...
在研究中,作者选取了四种常用的分类算法进行预测模型的构建,这些算法包括决策树、随机森林、KNN(K最近邻)和SVM(支持向量机)。这些算法在数据挖掘领域被广泛用于分类和预测任务。 - 决策树是一种基本的分类...
9. 用户隐私保护:由于恶意软件可能会侵犯用户隐私,如通讯录、地理位置信息、阅读习惯和银行卡信息等,因此恶意软件检测不仅是技术问题,也是法律和道德问题。保护用户隐私已经成为移动安全领域的一个重要目标。 ...
算法例如决策树、支持向量机(SVM)、k最近邻(k-NN)和神经网络等被广泛应用于分类任务。 4. 聚类:聚类是将数据集中的对象分组成多个簇的过程,使得同一簇内的对象比其他簇内的对象更相似。常见的聚类算法有k-...
分类算法是数据挖掘中的一项重要技术,k-最近邻(k-NN)算法是一种基本的分类与回归方法。为了在大规模数据集上有效地应用k-NN算法,需要将其MapReduce化。即将k-NN算法分解为Map和Reduce两个步骤,利用Map-Reduce框架...