一、提取方法的修正
上回说道,我们使用cvFindContours函数来找银行卡上的数字的外包矩形,从而从银行卡上将数字抠下来进行识别,但是,使用后会发现有如下两个问题:
(1)不好筛选
提供的图片大小不一样,那么数字的外包矩形框的大小也就不一样,如果简单地采用面积的办法进行筛选,那么这种方法的适应性是非常的差的。
(2)外包矩形框的不确定性
不看不知道,一看吓一跳,一个数字八竟然有四个外包矩形框,这个我们的提取工作带来了非常大的不便,所以我们考虑换一种提取方式。
基于灰度变化的数字提取方法:
数字与数字的间隔往往是背景颜色或者是背景像素点个数和的一个波峰,即前景像素点个数和的一个波谷,我们利用这样的特点来分割图片。这里考虑到银行卡类型比较单一,如果将卡大小控制在一定的范围内,那么两数字之间一般会出现大部分背景像素点,即这里暂时不考虑数字与数字相连的情况。
那么首先,我们通过获得有效区域(见“再预处理”一文),得到了数字的高度,接下来,我们用上述方法得到每个数字的宽度,于是乎就能将数字抠取下来了。
二、样本模板修正
从网上下了一个手写数字的样本,天真地以为应该是可以的,随便打开一个看看,如下图所示:
大家可能觉得形状扭曲或者其他什么,而我注意到的是它的填充情况,可以发现,这个样本上下基本与图像边框相切,但是两边却非常空余,结合一下我们所采取的数字抠取得办法,得到的数字一定是上下左右都是切和图像边框的,我不禁冷汗直冒。为什么呢?因为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)样本适应性:样本是否能适应所有的银行卡,虽然理论上数字都是一样的,但是处理过后的数字特征发生了一定的变换
这将在后面进行完善!!!!!