在进行完预处理之后,我们可以开始用算法识别数字了。首先我们不做那么复杂的、一连串银行卡数字的识别,而是做一个数字的测试。
一、机器算法的引入-----K近邻算法
K最近邻(k-Nearest Neighbour,KNN)分类算法是一个理论上比较成熟的算法,也是最简单的机器学习算法之一。顾名思义,k最近邻实际上就是取和待分类物最相似的k个模板,然后这k个模板中所占比例最高的类别就是最后决定的类别。
思路就是这么简单,那么这个最近的距离如何计算呢?往往用欧氏距离等经典距离来衡量模板和待分类物的“距离”。
KNN很适合在有大量样本的情况下进行使用,当然,这也带来了速度较慢的问题。
于是KNN就非常简单了。
二、准备工作----模板准备
我们只做单个数字的检测,来检验我们所写的算法的正确性,所以类似单片机中串口自发自收的测试方法,我们可以用模板来识别模板,从而检测算法的正确性。
我们在画图板中写入0-9十个数字(大小并没有特殊要求,后面程序会进一步处理),并一一截图保存变成我们的样本,并放在工程目录下:
模板准备完毕。
三、整体思路
1>读取模板:我们这里有10个类别,即0-9十个数字,每个类别有一个样本,一共十个模板,我们需要读入程序中,用IplImage或者cvMat进行存储;
2>模板调整(示模板情况而定需不需要该步骤):也可以说是提取特征区域吧,因为模板是手动截图的,每个模板打大小不是严格一样的,但是作为模板匹配来说,我们需要的是统一的尺寸标准,所以我们需要将导入的模板图片调整到统一的、合适的尺寸。
在http://blog.csdn.net/wangyaninglm/article/details/17091901的博客上我看到了非常合适的解释图,在这里也分享给大家:
这里提取出来的ROI是模板的有效部分,但是大小还是不一定符合统一尺寸的要求,所以,我们要进行适当的放缩
3>训练数据:这是我认为的算法最关键的部分。这里有几个参数:
Classes:类型数量
Train_simples:每个类型的样本数量
trainData:模板的训练集,
trainClasses:训练集所对应的类别标号
接下来我想解释一下trainData,trainClasses是个啥,但是感觉说太多也并不一定能够说得清楚,所以,请见下图所示:
看了上图之后,相信大家对k最近邻算法会有自己的想法了。可以看出,得到训练集的关键就是得到样本的特征向量。有很多特征向量的提取方法,详细的会在后面阐述,这里只介绍最简单的一种方法:
当模板不大的时候,我们可以将模板(可以看做一个n*m的矩阵)展开成一个n*m的一维向量,这就是这个模板的特征。事实上,有了第二步的预处理(ROI的提取),模板几乎就只有数字那么大了,所以模板是很小的,要想得到全部的信息,该方法再好也再简单不过了。
4>得到了trainData、trainClasses后,就可以利用openCV的函数进行k最近邻的算法求解了
所有步骤结束,是不是很简单呢?
接下来看看实际的实现过程和结果------关键函数代码:
void getData()
{//得到训练数据
IplImage* src_image;
IplImage* prs_image = cvCreateImage(cvSize(new_width, new_height), 8, 1); ;
CvMat row,data;
CvMat rowb,datab;
int x,y;
int c = 0;
int i,j,k;
int m,n;
m=0;
k=0;
CvMat* srcImg = cvCreateMat(32,32,CV_32FC1);
ifstream file;
char filename[50];
char a[32][32];
string s;
for(m =0; m<classes;m++)//总共10个数字
{
c = 0;
for(n = 0; n<train_samples;n++)//每个数字50个样本
{
c++;
// 这里是读入图片样本
sprintf(file, "%d%d.bmp", i, j);
src_image = cvLoadImage(file);
if(!src_image)
{
printf("Error: Cant load image %s\n",file);
}
IplImage* gray_image = cvCreateImage(cvGetSize(src_image), 8, 1);
IplImage* binary_image = cvCreateImage(cvGetSize(src_image), 8, 1);
cvCvtColor(src_image, gray_image, CV_BGR2GRAY);
cvThreshold(gray_image, binary_image, 100, 255, CV_THRESH_BINARY);
//process file
//将模板按照尺寸大小将有效部分放缩
prs_image = preprocessing(prs_image);
//生成训练矩阵,每个图像作为一个向量
cvGetRow(trainClasses, &row,m*train_samples +n);//y
cvSet(&row,cvRealScalar(m));
//Set data
cvGetRow(trainData, &rowb,m*train_samples +n);//x
IplImage *img = cvCreateImage(cvGetSize(prs_image),IPL_DEPTH_32F, 1 );
//转换换 8 bits image to 32位浮点数图片取值区间为[0,1]
//将模板图像转换成一个一维向量作为特征向量
//scale = 0.0039215 = 1/255;
cvConvertScale(prs_image,img, 0.0039215, 0);
cvGetSubRect(img, &data,cvRect(0,0,new_width,new_height));
CvMat row_header, *row1;
//convert data matrix sizexsize to vecor
row1 =cvReshape( &data, &row_header, 0, 1 );
cvCopy(row1, &rowb,NULL);
}
}
}
IplImage *preprocessing(IplImage*imgSrc)
{//放缩模板图像的有效部分
IplImage* result;
IplImage* scaledResult;
CvMat data;
CvMat dataA;
CvRect bb;//bounding box
CvRect bba;//boundinb box maintain aspect ratio
//Find bounding box找到边界框
bb=findBB(imgSrc);
cvGetSubRect(imgSrc, &data,cvRect(bb.x,bb.y,bb.width,bb.height));
int size=(bb.width>bb.height)?bb.width:bb.height;
result=cvCreateImage( cvSize( size, size ), 8, 1 );
cvSet(result,CV_RGB(255,255,255),NULL);
//将图像放中间,大小归一化
int x=(int)floor((float)(size-bb.width)/2.0f);
int y=(int)floor((float)(size-bb.height)/2.0f);
cvGetSubRect(result, &dataA,cvRect(x,y,bb.width,bb.height));
cvCopy(&data, &dataA, NULL);
//Scale result
scaledResult=cvCreateImage( cvSize( new_width, new_height ), 8, 1 );
cvResize(result, scaledResult, CV_INTER_NN);
//Return processed data
return scaledResult;//直接返回处理后的图片
}
CvRect findBB(IplImage *img)
{//找模板图像有效图像的边框,方便抠下来
CvSeq *contour;
CvMemStorage *storage = cvCreateMemStorage(0);
IplImage* temp = cvCreateImage(cvGetSize(img), 8, 1);
cvCopy(img, temp);
int a = cvFindContours(temp, storage, &contour, sizeof(CvContour),CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
CvRect rect, tprect;
rect = cvBoundingRect( contour, 0 );
return rect;
}
有了上面代码的基础,我们就得到了trainData、trainClasses训练集,那么接下来我们要抠取待测图片的有效区域,并且生成相应的特征向量,当然这里生成特征向量的方法要和前面生成训练集时用的方法保持一致。
训练完之后我们就可以利用openCV函数对训练集和待测数据进行k最近邻计算具体函数见下:
int do_ocr(IplImage *img)
{
IplImage* pimage;
CvMat data;
pimage = preprocessing(img);
IplImage *image = cvCreateImage(cvGetSize(pimage),IPL_DEPTH_32F, 1 );
//转换换 8 bits image to 32位浮点数图片取值区间为[0,1]
//scale = 0.0039215 = 1/255;
cvConvertScale(pimage,image, 0.0039215, 0);
cvGetSubRect(image, &data,cvRect(0,0,new_width,new_height));
CvMat mathdr;
CvMat *vec;
vec = cvReshape(&data, &mathdr, 0, 1);
//SumMat(*vec);
CvMat* nearest = cvCreateMat( 1, K, CV_32FC1);
float ret = knn->find_nearest(vec, K, 0, 0, nearest, 0);
return (int)ret;
}
按照我上文中说的思路很容易得到我们想要的结果:
测试图片:
测试结果: