手写数字识别系统的设计与实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
手写数字识别系统的设计与实现
摘要本手写数字识别系统是一个以VISUAL STUDIO C++ 6.0为编译环境,使用MFC进行图形图像界面开发的系统。
主要功能是通过在点击手写数字识别菜单下的绘制数字标签弹出的绘制数字窗口中完成数字的手写,在此窗口中可以进行数字的保存及清屏,然后通过文件菜单中的打开标签打开所绘制的数字,从而进行数字的预处理,其中包括灰度化及二值化处理,然后进行特征提取,最后实现数字的识别。
本系统的界面设计友好,流程正确,功能也较为完善。
实验结果表明,本系统具有较高的识别率。
关键词:绘制数字;预处理;特征提取;特征库;数字识别
目录
前言 (1)
概述 (2)
1 需求分析 (4)
1.1 功能需求分析 (4)
1.2 性能需求分析 (4)
1.3 数据需求分析 (5)
1.4 相关软件介绍 (5)
2 手写数字识别系统的设计与基本原理 (6)
2.1 系统整体功能模块设计 (6)
2.2 手写数字识别系统的基本原理 (6)
2.2.1 数字图像的绘制 (6)
2.2.2 图像的预处理 (6)
2.2.3 图像的特征提取 (7)
2.2.4 特征库的建立 (8)
2.2.5 图像数字的识别 (8)
3 手写数字识别系统程序设计 (8)
3.1 数字图像的绘制 (8)
3.2数字的特征提取 (15)
3.3 模板特征库的建立 (18)
3.4 数字的识别 (20)
总结 (23)
致谢 (24)
参考文献 (25)
前言
自上世纪六十年代以来,计算机视觉与图像处理越来越受到人们的关注,并逐渐成为一门重要的学科领域。
而作为它们的研究对象的数字图像,也因为它含有研究目标的丰富信息而成为越来越重要的研究对象。
图像识别的目标是用计算机自动完成某些信息的处理,用来替代人工去处理图像分类及识别的任务。
手写数字识别是图像识别学科下的一个分支,是图像处理和模式识别领域研究的课题之一,由于其具有很强的实用性一直是多年来的研究热点。
由于手写体数字的随意性很大,例如,笔画的粗细,字体的大小,倾斜等等都直接影响到字符的正确识别,所以手写体数字识别是一个很有挑战性的课题。
在过去的数十年中,研究者们提出了许多的识别方法,取得了较大的成果。
手写体数字识别实用性很强,在大规模数据统计(如例行年检,人口普查),财务,税务,邮件分拣等等应用领域中都有广阔的应用前景。
本课题拟研究手写体数字识别的理论和方法,开发一个小型的手写体数字识别系统。
在研究手写体数字识别理论和方法的基础上,开发这样一个小型的手写体数字识别系统需要完成以下主要方面的研究与设计工作:手写数字绘制的问题、数字的预处理问题、特征提取问题、特征库的建立问题、数字识别问题。
概述
此手写数字识别系统的需要实现手写数字的绘制功能、手写数字的特征提取功能、数字的模板特征库的建立功能以及手写数字的识别功能。
在近几年国内外对手写数字识别系统的研究已经取得了进展,一些新的理论例如基于Hopfield 神经网络、基于小波技术、基于BP 神经网络以及支持向量机的研究应用在建立手写数字识别系统平台,并且在多数数据库中取得了较好的测试结果。
但是目前仍然存在亟需深入研究解决的问题:1) 识别的准确度需要达到较好的水平2) 识别的效率要达到很高的水平。
数字识别输入的数据通常是很大的,而高精度与高速度是相互矛盾。
这些难点存在的原因是:1) 数字的笔划简单,而且其笔划差别相对较小,字形相差不大,使得准确区分某些数字有一些困难;2) 数字虽然只有10 种,且笔划简单,但同一数字写法却千差万别,全世界的各个国家各个地区的人都在用,则其书写上带有区域特性,很难做出可以兼顾世界各种写法的、识别率极高的通用性数字识别系统。
3)特征库的训练不够,导致识别率不高。
手写数字识别的研究不仅存在很大的应用价值,由于手写数字识别本身的特点,对它的研究也存在着重要的理论价值:
1) 阿拉伯数字作为唯一被世界各国通用的符号,所以对手写体数字识别的研究基本上与文化背景无关,各地的研究工作者可以说是基于同一平台开展工作的,有利于研究的比较和探讨。
2) 手写数字识别应用广泛,如税表系统,银行支票自动处理和邮政编码自动识别等。
在以前,这些工作需要大量的手工录入,投入的人力物力都相对较多,而且劳动强度较大。
为了适应无纸化办公的需要,大大提高工作效率,研究实现手写数字识别系统是必须要做的。
3) 由于数字类别只有0-9共10 个,比其他字符识别率较高,可将其用于验证新的理论或做深入的分析研究。
许多机器学习和模式识别领域的新理论和算法都是先用手写数字识别进行检验,验证其理论的有效性,然后才会将其应用到更为复杂的领域当中。
在这方面的典型例子就是人工神经网络和支持向量机。
4) 手写数字的识别方法很容易将其推广到其它一些相关的问题上,如对英文之类拼音文字的识别。
事实上,有许多学者就是把数字和英文字母的识别放在一起研究的。
在过去的数几年中,研究者提出了许许多多的识别方法,按提取的数字特征的不同,可以将这些方法分为两类:基于结构特征的方法和基于统计特征的方法。
统计特征通常包括点密度的测量、矩、特征区域等;结构特征通常包括圆、端点、交叉点、笔划、轮廓等,一般来说,两类特征各有优势。
例如,使用统计特征的分类器易于训练,而且对于使用统计特征的分类器,在给定的训练集上能够得到相对较高的识别率;而结构特征的主要优点之一是能描述字符的结构,在识别过程中能有效地结合几何和结构的知识,因此能够得到可靠性较高的识别结果。
在此次的设计中使用的是统计特征。
基于以上所述,本次毕业设计课题为手写数字识别系统的设计与实现。
其功能是将人工手绘的数字图像转换成可编辑的文本信息。
该系统包括手写数字绘制模块、图像预处理模块、特征提取模块、训练模块和识别模块。
涉及模式识别、图像处理、人工智能、统计学、心理学和计算机科学等相关内容。
通过对图像处理和识别算法进行不断地研究和实践,以降低误识率和拒识率。
本文主要介绍手写数字识别系统的设计与实现,首先需要了解手写数字识别系统
现阶段的发展情况和研究现状,然后对系统进行分析,主要从功能需求分析、性能需求分析、数据需求分析和相关软件介绍四方面入手,从而使得对系统有初步的认识,然后对系统的整体设计模块进行介绍,进而对系统的各个功能模块具体的设计原理进行详细介绍,之后对本次所设计出的系统进行介绍并对相关代码进行说明,最后总结本系统的优缺点及今后工作展望等,整篇文章通俗易懂,条理清晰,可以使读者轻松阅读,并理解实现过程。
1 需求分析
综合用户在实际应用中的需求,对系统的运作流程进行了整理,并通过对流程的分析得出了如下的需求分析。
1.1 功能需求分析
根据对用户需求的分析,系统应包含以下功能:
1)数字的绘制
在绘制数字的窗口中实现数字的手写,并对其坐标值进行保存,利用复位按钮可实现数字的清除工作。
2)数字的预处理
在手写数字图像识别系统中,图像的预处理跟一般图像系统不同,我们不需要对图像进行灰度化处理、去噪处理等基本操作,我们利用程序保存的坐标值就可以对生成一张二值化图像,相当于图像处理系统的二值化处理。
3)特征的提取
在第二步中我们得到了手写数字的二值化图像,进行特征提取前需要对此图
像的数据区域进行定位,在程序中我们遍历此二值化图像,找到手写数字区域的上、下、左、右边界,重新生成一张数字图片,利用新生成的数字图片
分成8*8的区域,统计每个区域的目标像素个数和整个小区域像素个数,计算目标像素个数与整个小区域像素的比值,得到64个特征值,作为这个手写数字的特征值。
4)特征库的训练
我们需要训练一个特征库,作为识别的标准。
系统中我们手写一个数字提取出它的特征值,再输入此手写数字,将数字与这些特征值相对应存储到特征库里面,特征库我们使用的是Access数据库,字段是数字及这个数字所对应所有特征值。
特征库越丰富,识别率越高。
5)数字识别
在手写数字识别中,我们使用的方法是模板匹配法,其实质就是提取出手写数字的特征值,利用这些特征值与特征库的数字的特征值进行比对,找出待识别数字特征值与特征库里存储的特征值最接近的数字,作为识别结果。
1.2 性能需求分析
1)正确性:根据手写数字识别系统的设计流程,流程中的每个步骤在系统中都必须有所体现,以保证程序的正确性。
2)精确性:根据手写数字识别系统的应用领域,该系统的识别结果必须有很高的识别精度,这样才能真正的实现该系统的价值。
3)效率性:根据该系统的应用领域可知,系统一旦投入应用需要处理大量的数据,所以对系统的处理速度也有很高的要求。
1.3 数据需求分析
根据手写数字识别系统的设计步骤可知该系统采用的是模板匹配法进行手写体数字识别。
模板匹配法是图像识别中最具有代表性的方法之一。
它是将从待识别的图像提取的若干特征量与模板对应的特征量进行比较,计算图像和模板特征量之间的距离,用最小距离法判定所属类。
而模板匹配通常需要事先建立标准模板库。
这里,模板库中的标准模板是数字样本的特征向量。
特征库的存储是利用Access数据库,并且利用MFC ADO技术连接数据库,不需要进行硬件配置。
数据库如图1.1所示。
图1.1 数据库
1.4 相关软件介绍
本课题是基于Visual C++6.0的,它是Microsoft 公司开发的Visual Studio 集成开发环境中功能最为强大、代码效率最高的开发工共。
利用VisualC++6.0可以两种方式编写Win32应用程序,一种方式是基于Windows API 的C 编程方式,另一种是基于MFC 的C++编程方式。
本系统采用的是基于MFC 的编程方式。
2 手写数字识别系统的设计与基本原理
2.1 系统整体功能模块设计
整体模块如图2.1所示:
图2.1 整体模块
数
字
图
像
的
绘
制 训练特征库 二值化处理 特征提取 数字识别
主界面
2.2 手写数字识别系统的基本原理
下面分别介绍各部分工作的基本原理:
2.2.1 数字图像的绘制
手写数字绘制功能的实现方案是:通过Visual C++中的CStatic控件来建立画布,用MFC中的封装类CDC实现手写数字功能。
在对话框中,响应鼠标事件的消息,分别是鼠标按下事件MouseDown,鼠标移动事件MouseMove,鼠标抬起事件MouseUp,判断当前鼠标点是否在CStatic控件上,在的话程序将此点的坐标值保存。
手写数字的绘制开始是鼠标按下事件,结束是以左鼠标抬起为标志。
2.2.2 图像的预处理
图像的预处理是为了突出手写体数字的特征。
在本次设计中主要包括:图像二值化处理。
图像的二值化处理就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果。
在手写数字识别系统中,我们在VC可视化编程界面中在一个固定大小的控件中手写了一个数字,在程序中获得的只是以这个控件左上角为原点的一系列坐标。
在内存中我们开辟一个大小跟这个控件区域大小相同的二维数组(以像素为单位,即生成一张图片的长跟宽跟这个矩形区域相等),这样内存中图像的数据区域的二维数组就跟手写区域的坐标相同,我们再取出手写区域的坐标值,将这些坐标值对应到图像图像数据区域中,并且将它的灰度值置为255(白色),将图像数据区域的其它坐标值下的灰度值置为0(黑色),这样我们就得到了一张手写数字的二值化图像。
在数字图像处理中,二值图像占有非常重要的地位,图像的二值化有利于图像的进一步处理,使图像变得简单,而且数据量减小,能凸显出感兴趣的目标的轮廓。
2.2.3 图像的特征提取
若直接把预处理后的数据作为输入量,进行分类计算时数据时数据量大,同时由于手写字体的多样化及图像本身和预处理过程中附带的某些干扰的影响,对系统的容错能力要求较高。
特征提取的目的就是从分析数字的拓扑结构入手,把它的某些结构特征提取出来,使数字的位移、大小变化、字形畸形等干扰相对较小,也就是把那些反映数字特征的关键信息提供给系统,这样就等于间接地增加了系统的容错能力,而且经过特征提取后数据量也大大减少了,这样就提高了识
别的效率。
手写数字识别的特征提取极大程度地影响着分类器的设计和性能,以及识别的效果和效率。
为了保证所要求的分类识别的正确率和节省资源,希望依据最少的特征达到所要求的分类识别的正确率。
在进行手写数字识别的过程中,特征提取应遵循以下原则:
1)特征应能尽量包含字符的有用信息。
2)特征的提取方法应简单而且提取快速。
3)各个特征之间的相关性应尽可能小。
4)特征数量尽可能少。
5)特征应有较好的抗干扰能力。
考虑到算法的实时性、快速性和准确性,在此次设计中采用的是一种简单的模板法对待测样本进行特征提取。
步骤为:1、搜索数据区域,找出手写体数字的上下左右边界。
2、将搜索到的数字区域平分成8*8共64个小区域。
3、计算8*8的每一个小区域中白色像素所占比例,即用每一个小区域内的白色像素个数除以该小区域的面积总数(总像素数),即得特征值,第一行的8个比例值是特征的前8特征值,第二行对应着特征的9~16个,以此类推。
2.2.4 特征库的建立
在手写数字识别系统中,我们首先要建立一个特征库,我们手写一个数字,并且取得这个数字的特征值,然后再想程序输入这个数字,在程序中将此输入数字与所有特征值相对应,作为模板库的一条记录,初始化模板库之后,就可以对手写数字进行识别,在识别的过程中我们不断的丰富模板库,如果手写数字识别成功则不需要将此数字存储到模板库中,如果识别失败就需要将此数字存储到模板库中,这样我们的模板库将越来越丰富。
2.2.5 图像数字的识别
在手写数字图像特征提取结束后,即可进行数字的识别。
这也是手写数字识别系统设计的最后一个环节,在本次设计中采用模板匹配法进行手写体数字识别。
模板匹配法是图像识别中最具有代表性的方法之一。
它是将从待识别的图像提取的若干特征量与模板对应的特征量进行比较,计算图像和模板特征量之间的距离,用最小距离法判定所属类。
模板匹配通常事先建立标准模板库。
这里,模
板库中的标准模板是数字样本的特征向量。
具体过程是:对于一个待测试的样本X,计算X和训练集中的某样本Xj
(0<j<m,m为训练集中的样本数)之间的距离。
循环计算待测样本和训练集中各已知样本之间的距离,比较所有的距离值,找出距离待测样本最近的已知样本,其中所对应的样本所属的类别就是待测样本X所属的类别。
3 手写数字识别系统程序设计
本次设计使用Visual C++语言来实现该系统,其用户界面分别介绍如下:3.1 数字图像的绘制
运行程序后得到系统的初始界面,如图3.1所示。
在绘图区域通过响应鼠标事件来完成手写数字的绘制问题,如图3.2所示,通过 CPen 创建对象pen并进行相关值的设置如pen(PS_SOLID,3,RGB(0,0,255))可以创建出不同颜色和粗细的画笔,消息响应事件是通过函数OnLButtonDown(UINT nFlags, CPoint point)、OnMouseMove(UINT nFlags, CPoint point)、OnLButtonUp(UINT nFlags, CPoint point)来实现的。
手写数字的绘制过程中通过建立堆栈即可实现手写数字的保存,所用函数为AddPointStack(const CPoint &point) 。
绘制数字后还可以对所绘制数字进行清除,通过函数OnButtonRset()即可实现,点击系统见面上的复位按钮即可实现此功能。
点击显示按钮即可得到二值化后的黑白图像,如图3.3所示。
图3.1 系统初始界面
图3.2 手写数字的绘制
图3.3 二值化结果
具体实现代码为:
1.手写数字的绘制
void CNumShiBieDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect,rect1,rect2;
CString str;
this->GetClientRect(rect);
m_ctlDrawNum.GetClientRect(rect1); //这个函数获得的坐标是相对于屏幕左上角而言,此矩形保存的值都是相对于屏幕左上角而言
//m_DrawNum.GetClientRect(rect2);//此函数的坐标是相对于本窗体客户区而言,左上角坐标为(0 , 0)
::ClientToScreen(this->m_hWnd,&point);
::ScreenToClient(m_ctlDrawNum.m_hWnd,&point); //注意这里的转换,先将窗口坐标转换成屏幕坐标,再将此坐标转换成画图控件的坐标系统,
str.Format("%d %d",point.x,point.y); //这样得到的点就是以画图控件为坐标系统了,既是左上角(0,0)开始,到(w,h)
if(rect1.PtInRect(point))
{
bBegin = true; //此参数确定手写数字开始
this->AddPointStack(point); //将数字开始点保存到堆栈中,相当于保存的是数字像素点在原始图像中的坐标值
//AfxMessageBox(str);
}
CDialog::OnLButtonDown(nFlags, point);
}
void CNumShiBieDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CRect rect;
CString str;
m_ctlDrawNum.GetClientRect(rect);
::ClientToScreen(m_hWnd,&point);
::ScreenToClient(m_ctlDrawNum.m_hWnd,&point);
str.Format("%d %d",point.x,point.y);
CPen pen(PS_SOLID,3,RGB(0,0,255));
pDC = m_ctlDrawNum.GetDC();
CPen *pOldPen;
pOldPen = pDC->SelectObject(&pen);
if(rect.PtInRect(point)&&bBegin)
{
if(this->AddPointStack(point))
{
CPoint befPoint = *(stuPoint.p+stuPoint.cur-2);
CPoint curPoint = *(stuPoint.p+stuPoint.cur-1);
pDC->MoveTo(befPoint.x,befPoint.y);
pDC->LineTo(curPoint.x,curPoint.y);
}
}
CDialog::OnMouseMove(nFlags, point);
}
void CNumShiBieDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default CRect rect;
m_ctlDrawNum.GetClientRect(rect);
::ClientToScreen(this->m_hWnd,&point);
::ScreenToClient(m_ctlDrawNum.m_hWnd,&point);
if(rect.PtInRect(point))
{
bBegin = false;
if(this->AddPointStack(point))
{
CPoint befPoint = *(stuPoint.p+stuPoint.cur-2);
CPoint curPoint = *(stuPoint.p+stuPoint.cur-1);
pDC->MoveTo(befPoint.x,befPoint.y);
pDC->LineTo(curPoint.x,curPoint.y);
}
}
CDialog::OnLButtonUp(nFlags, point);
}
2.手写数字的存储
bool CNumShiBieDlg::AddPointStack(const CPoint &point)
{
CString str;
bool b = false;
if(stuPoint.cur!=stuPoint.size)
{
*(stuPoint.p+stuPoint.cur) = point;
stuPoint.cur += 1; //cur指向的是最后一个结点的后一个空结点
b = true;
}
else
{
b=false;
AfxMessageBox("栈满溢出");
}
return b;
}
3.手写数字的清除
void CNumShiBieDlg::OnButtonRset()
{
// TODO: Add your control notification handler code here
this->Invalidate();
stuPoint.cur = 0;
if(stuPoint.p!=NULL)
delete stuPoint.p;
stuPoint.p = new CPoint[stuPoint.size];
}
4.二值化后的显示
void CNumShiBieDlg::OnButtonShownum()
{
// TODO: Add your control notification handler code here
CRect rect;
m_ctlDrawNum.GetClientRect(rect);
int w = 0 ,h = 0;
w = rect.Width();
h = rect.Height();
if(w%4!=0)
w = w+4-w%4; //按位补齐,宽一定要是4的倍数
//windows下规定,位图的行的像素个数必须是4的倍数
int xleft=w , ytop= h, xrigth=0 ,ybottom=0;
for(int i1 = 0; i1 < stuPoint.cur ;i1++)
{
CPoint point;
point = *(stuPoint.p+i1);
if(xleft > point.x)
xleft = point.x;
if(ytop > point.y)
ytop = point.y;
if(xrigth < point.x)
xrigth = point.x;
if(ybottom < point.y)
ybottom = point.y;
} //这个循环之后,xleft记录了手写数字区域最左边的像素点的x坐标,ytop 记录了最上边的像素点的y的坐标
//xrigth记录了最右边的点的x坐标,ybottom记录了最下边的点的y的坐标,这样就可以得到数字区域的宽跟长,以像素为单位
unsigned char * pdata=new unsigned char[w*h*3];
::memset(pdata,0,w*h*3);
for(int i=0;i<stuPoint.cur;i++)
{
CPoint point = *(stuPoint.p+i);
int x = point.x;
int y = point.y;
pdata[(y*w+x)*3] = 255;
pdata[(y*w+x)*3+1] = 255;
pdata[(y*w+x)*3+2] = 255; //注意坐标变量y才是表示图像的第几行像素} //鼠标走过的点的坐标值即是数字像素在数据矩阵中的坐标值,这样我们把鼠标走过的所有坐标值对应到图像数据的坐标值中的灰度值设置成255,而背景色置为白//这样相当于,将手写数字区域当成一张数字图片,将其从第一行到最后一行的数据顺序读到内存中,所以我们显示时,一定要将此图片数据区域按行转置unsigned char * ppdata = new unsigned char[w*h*3];
::memcpy(ppdata,pdata,w*h*3);
for(i = 0 ; i < h ; i++)
for(int j = 0 ; j < w ; j++)
{
ppdata[((h-i-1)*w+j)*3] = pdata[(i*w+j)*3];
ppdata[((h-i-1)*w+j)*3+1] = pdata[(i*w+j)*3+1];
ppdata[((h-i-1)*w+j)*3+2] = pdata[(i*w+j)*3+2];
}
//注意,windows中一般的位图是倒向的位图,也就是说我们实际看到的图像中,第一行像素值在文件中存储在位图数据区域的最后一行的,而最后一行像素在位图//文件数据区域矩阵中是存储在第一行的,所以平时我们读取了一个位图之后,读取到的像素值,与我们实际看到的图像的像素是按行倒置的
//并且当我们用StretchDibits函数显示位图时,数据区域指针一定要给成按行倒置的位图数据矩阵
xleft = xleft-1;
ytop = ytop-1;
xrigth = xrigth + 1;
ybottom = ybottom + 1; //将四周扩展了一排像素
nw= xrigth-xleft+1;
nh= ybottom-ytop+1;
if(nw%4!=0)
nw = nw + 4-nw%4;
unsigned char* pnum = new unsigned char[nw*nh];
pnumzheng = new unsigned char[nw*nh];
::memset(pnum,0,nw*nh);
for(i = 0 ;i < stuPoint.cur ;i++)
{
CPoint point;
point = *(stuPoint.p+i);
int x , y;
x = point.x - xleft;
y = point.y - ytop; //(xleft,ytop)相当于生成的手写数字区域矩形最左上角的坐标,这里完成了坐标系统的转换
pnum[y*nw+x] = 255;
//pnum[(y*nw+x)*3+1] = 255;
//pnum[(y*nw+x)*3+2] = 255;
}
for(i = 0 ;i < nh ;i++)
for(int j = 0 ; j < nw ;j++)
{
pnumzheng[(nh-i-1)*nw+j] = pnum[i*nw+j]; //8bit级图像指针
//pnumzheng[((nh-i-1)*nw+j)*3+1] = pnum[(i*nw+j)*3+1];
//pnumzheng[((nh-i-1)*nw+j)*3+2] = pnum[(i*nw+j)*3+2];
}
BYTE * p = BitTo24Bit(pnumzheng , nw , nh);
binfo.biSize=sizeof(BITMAPINFOHEADER);
binfo.biWidth=nw;
binfo.biHeight=nh;
binfo.biPlanes=1;
binfo.biBitCount=24;
binfo.biCompression=BI_RGB;
binfo.biSizeImage=nw*nh*3;
binfo.biXPelsPerMeter=0;
binfo.biYPelsPerMeter=0;
binfo.biClrUsed=0;
binfo.biClrImportant=0;
HDC hdc = ::GetDC(m_ctlShowNum.m_hWnd);
::SetStretchBltMode(hdc,HALFTONE);
::StretchDIBits(hdc,
0,0,
nw,nh,
0,0,
nw,
nh,
p,
(LPBITMAPINFO)&binfo,
DIB_RGB_COLORS,
SRCCOPY
);
}
3.2数字的特征提取
数字特征的提取是为了更好的实现数字的识别,在此是通过提取所绘制数字的像素特征即如前面所介绍的用目标像素个数除以这个小区域内总得像素个数的结果作为此区域的特征值,共可得到64个特征值。
在此通过在函数
Get64Feature(BYTE *pd, int w, int h)中调用函数GetOneFeature(int brow,
int erow, int bcol, int ecol , BYTE * pdata ,int w
)来实现数字特征的提取功能。
具体实现代码为:
1.得到64个区域的特征值
double* CNumShiBieDlg::Get64Feature(BYTE *pd, int w, int h)
{
double *pFeature = new double[64];
if((w%8==0) && (h%8==0))
{
int hc = h/8;
int wc = w/8;
int jishu = 0;
int j = 0; //列的方向上共8个单元格
while(j < 8)//j = 0说明是第一行的单元格像素,依次类推
{
int cc = 0 ; //记录一行方格内的单元格个数,共8个方格所以循环8次
while(cc < 8)
{
pFeature[jishu++] = this->GetOneFeature(j*hc , (j+1)*hc , cc*wc ,(cc+1)*wc , pd ,w);
cc++;//此时已经统计完一个单元格的像素
}
j++ ; //此时已经统计完一行单元格内的像素
}
}//if((w%8==0) && (h%8==0))
else if((h%8!=0)&&(w%8==0))
{
int hc1 = h%8; //行不是8的倍数,最上面那一行单元格单独考虑
int hc = (h-hc1)/8;
int wc = w/8;
int jishu = 0;
int j = 0; //列的方向上共8个单元格
while(j < 8)//j = 0说明是第一行的单元格像素,依次类推
{
int cc = 0 ; //记录一行方格内的单元格个数,共8个方格所以循环8次
while(cc < 8)
{
if(j==0)
pFeature[jishu++] = this->GetOneFeature(0 , hc1 , cc*wc , (cc+1)*wc,pd ,w);
else
pFeature[jishu++] = this->GetOneFeature(hc1+(j-1)*hc , hc1+j*hc ,cc*wc , (cc+1)*wc , pd ,w);
cc++;
}
j++ ; //此时已经统计完一行单元格内的像素
}
}//else if((h%8!=0)&&(w%8==0))
else if((w%8!=0)&&(h%8==0)) //行是8的倍数,列不是8的倍数
{
int hc = h/8;
int wc1 = w%8;
int wc = (w-wc1)/8;
int jishu = 0;
int j = 0; //列的方向上共8个单元格
while(j < 8)//j = 0说明是第一行的单元格像素,依次类推
{
int cc = 0 ; //记录一行方格内的单元格个数,共8个方格所以循环8次
while(cc < 8)
{
if(cc==0)
pFeature[jishu++] = this->GetOneFeature(j*hc , (j+1)*hc , 0 , wc1 , pd ,w);
else
pFeature[jishu++] = this->GetOneFeature(j*hc , (j+1)*hc , wc1+(cc-1)*wc ,wc1+cc*wc ,pd ,w);
cc++;
}
j++ ; //此时已经统计完一行单元格内的像素
}
}/*else if((w%8!=0)&&(h%8==0))*/
else if((w%8!=0)&&(h%8!=0))
{
int hc1 = h%8;
int hc = (h-hc1)/8;
int wc1 = w%8;
int wc = (w-wc1)/8;
int jishu = 0;
int j = 0; //列的方向上共8个单元格
while(j < 8)//j = 0说明是第一行的单元格像素,依次类推
{
int cc = 0 ; //记录一行方格内的单元格个数,共8个方格所以循环8次
while(cc < 8)
{
if(j==0&&cc==0)
pFeature[jishu++] = this->GetOneFeature(0 , hc1 ,0 , wc1 ,pd ,w);
else if(j==0&&cc!=0)
pFeature[jishu++] = this->GetOneFeature(0 ,hc1 ,wc1+(cc-1)*wc ,wc1 + cc*wc , pd ,w);
else if(j!=0&&cc==0)
pFeature[jishu++] = this->GetOneFeature(hc1+(j-1)*hc1 ,hc1+j*hc1 ,0 , wc1 ,pd ,w);
else
pFeature[jishu++] = this->GetOneFeature(hc1+(j-1)*hc1 , hc1+j*hc1 , wc1+(cc-1)*wc , wc1+cc*wc ,pd ,w);
cc++;
}
j++ ; //此时已经统计完一行单元格内的像素
}
}/*else if((w%8!=0)&&(h%8!=0))*/
return pFeature;
}
2.得到每个小区域的特征值
double CNumShiBieDlg::GetOneFeature(int brow, int erow, int bcol, int ecol , BYTE * pdata ,int w)
{
int pixAim = 0;
int pixSum = 0;
for(int i = brow ; i< erow ; i++)
for(int j = bcol ; j < ecol ;j++)
if(pdata[i*w+j]==255)
pixAim++;
else
pixSum++;
double df;
df = (double)pixAim/(double)pixSum;
return df;
}
3.3 模板特征库的建立
点击系统界面上的训练按钮即可实现特征库的建立,训练的次数越多特征库越丰富,识别结果就越准确。
在此系统的特征库是存储在Access数据库中,如图3.4所示。
在此利用MFC ADO技术连接数据库,并且建立记录集对象并通过函数OnButtonInitialize()来实现特征库的训练。