8特征值,第二行对应着特征的9~16个,以此类推。
2.2.4 特征库的建立
在手写数字识别系统中,我们首先要建立一个特征库,我们手写一个数字,并且取得这个数字的特征值,然后再想程序输入这个数字,在程序中将此输入数字与所有特征值相对应,作为模板库的一条记录,初始化模板库之后,就可以对手写数字进行识别,在识别的过程中我们不断的丰富模板库,如果手写数字识别成功则不需要将此数字存储到模板库中,如果识别失败就需要将此数字存储到模板库中,这样我们的模板库将越来越丰富。
2.2.5 图像数字的识别
在手写数字图像特征提取结束后,即可进行数字的识别。这也是手写数字
识别系统设计的最后一个环节,在本次设计中采用模板匹配法进行手写体数字识别。
模板匹配法是图像识别中最具有代表性的方法之一。它是将从待识别的图像提取的若干特征量与模板对应的特征量进行比较,计算图像和模板特征量之间的距离,用最小距离法判定所属类。模板匹配通常事先建立标准模板库。这里,模板库中的标准模板是数字样本的特征向量。
具体过程是:对于一个待测试的样本X,计算X和训练集中的某样本Xj(0 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, 8 CPoint point)来实现的。手写数字的绘制过程中通过建立堆栈即可实现手写数字的保存,所用函数为AddPointStack(const CPoint &point) 。绘制数字后还可以对所绘制数字进行清除,通过函数OnButtonRset()即可实现,点击系统见面上的复位按钮即可实现此功能。点击显示按钮即可得到二值化后的黑白图像,如图3.3所示。 图3.1 系统初始界面 图3.2 手写数字的绘制 9 图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(\ //这样得到的点就是以画图控件为坐标系统了,既是左上角(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; 10 m_ctlDrawNum.GetClientRect(rect); ::ClientToScreen(m_hWnd,&point); ::ScreenToClient(m_ctlDrawNum.m_hWnd,&point); str.Format(\ 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; 11 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) 12