基于特征点标识法在OCR上的应用开发
吕威 学号:071210119
<此论文 版权所有:吕威,拒绝转载,只作为技术交流>
摘要:本文主要针对特定字体的汉字识别与研究,阐述了基于特征点标示法在具体汉字识别中的开发和设计。
关键词:汉字识别。
Abstract: This paper mainly focuses on chinese recognition and research of specific font, describing the scientific development and designing of labelling method baseed on characteristic point.
Keywords: chinese recognition
OCR识别技术是根据一定的图像特征进行的,特征选择和提取的基本任务为从众多特征中找到最有效的特征,汉字的识别有很多方法提取各种特征,如统计决策法,句法结构法等,提取不同的特征使建立的特征字典的模板的不同。本文所讨论的建立特征字典的方法是类似于统计决策法中的汉字特征点的一种更简单的思路:特征点标示法来建立特征字典,论文后面我们将给出他们的区别。
1.一个简单的应用
算法思想来源于对识别象棋棋盘的的思索。关于特征点标识法的一个小简单的应用:QQ新中国象棋自动下棋程序,象棋自动下棋程序的关键思路在于识别棋盘,下面我们将用基于特征点标识的识别方法进行讨论。
1.1我为特征点标识下的定义是:我们可以对汉字提取出一个或多个黑点(称汉字中填充汉字的为黑点),而此组黑点在其他的有待识别的汉字中为非黑点或不全为黑点,那么我们就称这几个特征点为此汉字的标识特征点(或关键点),那么用这几个特征点即可标识此汉字。
1.2象棋棋盘特征点提取的过程:
(1)先截屏PrintScreen,保存屏幕到bmp;
(2)用Photoshop截出每个棋子(52px*52px);
(3)写程序各提取出红字的标识特征点和黑子的标识特征点(红炮比较特殊,颜色与其它不同可单独判断)(这里每个字只提一个标识特征点即可)
1.3象棋棋盘识别的过程(以下坐标为以屏幕坐上角为原点,单位:px):
(1)程序对含棋盘的屏幕截屏;
(2)用Photoshop找到棋盘开始点(268,84),每行和每列宽:57,黑子颜色为RGB(26,26,26),普通红字颜色为RGB(172,32,0),红炮为RGB(173,2,0),
(3)用上述数据建立棋盘,输出给象棋算法运算,然后控制鼠标走棋即可。
以下为取图判断的代码
void CXQWLIGHTDlg::QuTuPanDuan()
{
int x, y;
int akeypoint[14][2]= //找到的标识特征点(已转换到以棋子的中心为原点的坐标)
{
{-14,8 }, //黑将RGB(26,26,26)
{-16,-3 }, //黑士RGB(26,26,26)
{15,9 }, //黑象RGB(26,26,26)
{0,-12 }, //黑马RGB(26,26,26)
{-17,7 }, //黑车RGB(26,26,26)
{-17,4 }, //黑炮RGB(26,26,26)
{-16,2 }, //黑卒RGB(26,26,26)
{-14,-9 }, //红帅
{-15,-8 }, //红仕
{-16,-5 }, //红相
{-14,8 }, //红马
{14,5 }, //红车
{-14,-9 }, //红炮 12
{-16,3 }, //红兵
};
CDC dc; //截屏,保存到memDC建立的数据缓冲区
dc.CreateDC("DISPLAY",NULL,NULL,NULL);
CBitmap bm;
int Width=GetSystemMetrics(SM_CXSCREEN);
int Height=GetSystemMetrics(SM_CYSCREEN);
bm.CreateCompatibleBitmap(&dc,Width,Height);
CDC tdc;
tdc.CreateCompatibleDC(&dc);
CBitmap*pOld=tdc.SelectObject(&bm);
tdc.BitBlt(0,0,Width,Height,&dc,0,0,SRCCOPY);
tdc.SelectObject(pOld);
BITMAP btm;
bm.GetBitmap(&btm);
DWORD size=btm.bmWidthBytes*btm.bmHeight;
LPSTR lpData=(LPSTR)GlobalAlloc(GPTR,size);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
SelectObject(memDC,bm); ///截屏到此
for(y=84; y<603; y+=57)//开始识别,棋盘开始点(268,84),每行列宽:57
{
for(x=268; x<729; x+=57)
{
//对每一个xy点识别以此点为中心点是否含有棋子,并识别此棋子
int i,flag=0; // flag标示棋子是否已找到,1表示不用再找了
for(i=0;i<7;i++) //识别红子
if(flag==0&& memDC.GetPixel(x+akeypoint[i][0],y+akeypoint[i][1])==RGB(26,26,26)
)
{ //Init_i, Init_j用作棋盘的建立
InitChessBoard[Init_i][Init_j++]=i+16; //存储棋子
if(Init_j>=9) {Init_i++;Init_j=0;} //请看棋盘的结构
flag=1;
}
for(i=7;i<14;i++) //识别黑子
if( flag==0 &&
( memDC.GetPixel(x+akeypoint[i][0],y+akeypoint[i][1])==RGB(172,32,0) ||
( i==12 &&
memDC.GetPixel(x+akeypoint[i][0],y+akeypoint[i][1])==RGB(173,2,0) )//红炮
)
)
{
InitChessBoard[Init_i][Init_j++]=i+1;
if(Init_j>=9)
{Init_i++;Init_j=0;}
flag=1;
}
if(flag==0){InitChessBoard[Init_i][Init_j++]=0;if(Init_j>=9) {Init_i++;Init_j=0;}}
}
}
for(x=0;x<10;x++)
for(y=0;y<9;y++)
cucpcStartup[(x+3)*16+y+3]=InitChessBoard[x][y]; //棋盘的转换
//转换后执行算法就可以了
}
注:附带程序中将给出红字走棋的程序,及开发的软件包,关于象棋走棋算法是修改一个开源代码的软件的代码:象棋小巫师,相见参考文献[1];
小结:基于特征点标示法进行识别速度较快(<0.5s),当然对棋盘的识别质量一定要是百分之百的,那我们可不可以把这种方法应用到OCR软件汉字识别中去呢?
2.特征点标识法OCR软件实现
下面开发的即为基于特征点标示法的OCR软件(我们仅对2500个常用汉字进行处理)。
系统要实现的基本要求:
(1) 输入图片中可含有多行文字;
(2) 对特定文字的识别准确率大于90%(修改后);
(3) 对图片噪声具有一定的适应性;
2.1特征点标识法提取汉字的原理与识别过程(在此OCR软件中)。
特征点标示法提取的原理:首先建立汉字大小为32*32的点阵,那么我们可以说我们可以找出10个点来准标示一个汉字,我们可以通过简单计算来验证,从1024个点中选10个点,即为
=334265867498622145619456种选法,
当然考虑到我们从骨架点内提取不可能唯一标示一个汉字,我们所说的为10个点来准标示一个汉字(尽量唯一标识此汉字),是因为有的汉字之间具有包含关系,如’巴’和’已’等,我们只是用选出的10个点来进行粗提取。
特征点标示法识别过程:
1.图片二值化,
2.预处理(去离散杂点噪声,倾斜度调整)
3.行切割和列切割,,
4.对应字的大小归一化(32*32),
5.由建立的字典(标识点)粗提取出候选字,
6.把候选字从字库中提取此字的矩阵,细识别。
2.2示例系统开发
本系统以特征点标示法为原理,关于特征字典的提取采用两个程序来做:自动提取关键点和手动提取关键点,通过自动提取关键点来模拟人去提取标识特征点,但提取的并不一定是最优(由于在实际大小归一后可能造成无法识别),所以用手动提取关键点由人来提取修正即可:
2.2.1自动提取关键点:
用VC++6.0生成MFC的Dialog项目
详细代码和注释请详见附件:自动提取关键点;
2.2.2 手动提取关键点:
用VC++6.0生成MFC的Dialog项目,界面如图1:
图1
功能及使用方法:
① 开启后请勿移动窗口,
② 区域1用来选择当前用来处理的汉字,
③ 区域2 用来显示此汉字的32*32的矩阵,双击对应点可添加点
④ 区域3用来显示此点在2500个汉字出现的次数(概率),细化后的骨干点,添加后需误识的字数,双击项即可添加此点,双击顶部按钮可对此列排序,
⑤ 区域4用来显示你选择的十个点,
⑥ 区域5用来显示对你选择的十个点的分析
添加代码即可,详细代码和注释请详见附件:手动提取关键点;
2.2.3开始识别
使用上述方法即可提取到每个字的10个标识特征点,用VC++6.0生成MFC的Dialog项目:关键点识别,开始识别。
2.2.3.1 系统流程图
本程序的流程图如图2:
加载图片 去离散杂点噪声 倾斜度调整
细提取出汉字 特征点标识法粗提取 切割出汉字
图2
2.2.3.2 函数声明
(1) void SlopeAdjust(BYTE *&image , int lWidth , int lHeight); //倾斜度调整函数
(2) void RemoveScatterNoise(BYTE *&image , int lWidth , int lHeight);// 去离散杂点噪声
(3) bool DeleteScaterJudge(BYTE *&image , BYTE * lplab, int lWidth, int lHeight, int x, int y, CPoint lab[], int lianXuShu); // 去离散杂点噪声
(4) void PreShiBie();//预处理函数
(5) void ReadGuanJianDian();//从 关键点.txt 中读取关键点
(6) void ZoomInterpolation(BYTE* pixel, BYTE*& tempPixel,int width, int height);//放大处理函数
(7) void GetZiBYTE(string c,BYTE * &pixel6) ;// 从字库提取自体矩阵函数,
(8) void CMyDlg::OnShiBie();//识别函数
2.2.3.3 识别函数OnShiBie()的代码如下:
//
// 识别函数
//
void CMyDlg::OnShiBie()
{
string strjieguo; //保存识别结果
PreShiBie(); //预处理
ReadGuanJianDian(); //读关键点
int i,j;
vector< vector<bool> > arrbmp; //建立arrbmp二维矩阵
vector< bool > arrx(ImageWidth); //图片矩阵数组
for( i = 0 ; i != ImageHeight ; ++i ) //转换到arrbmp
{
for( j = 0 ; j != ImageWidth ; ++j )
if( OpenImage[ i * ImageWidth + j ] ) //二值化 黑1-0,白0-255
arrx[j]=0;
else
arrx[j]=1;
arrbmp.push_back (arrx);
}
int liney[2000]={0}; //储存每行黑点数
for( i = 0 ; i != ImageHeight ; ++i ) //统计前计算每行黑点数,行切割
for( j = 0 ; j != ImageWidth ; ++j )
if( arrbmp[ i ][ j ] ) liney[i]++;
vector< vector<bool> > linearrbmp[50]; //最多五十行,每行存储到二维数组中
int numline,lk,ly1,ly2,lflag=0;
//统计行数numline,并把每行存储到二维数组中linearrbmp中
for( numline=0 , lk=0 ; lk < ImageHeight ; lk++ ){
if( lflag==0 && liney[lk]!=0 )
if( liney[lk+1]>0 && liney[lk-1]==0 )
{ ly1=lk; lflag=1; }
if( lflag==1 && liney[lk]==0 && liney[lk+1]==0 ){
ly2=lk; lflag=0;
// // y1,y2
for(i=ly1-1;i<ly2+1;i++)
linearrbmp[numline].push_back (arrbmp[i]);
numline++;
}
}
for( lk=0 ; lk < numline ; ++lk ) //每行进行列切割,开始循环
{
int lhei=linearrbmp[lk].size (); // lk行的高
int lwid=linearrbmp[lk][0].size (); // lk行的宽
int zftx[5000]={0}; //为列切割统计
for(j=0;j<lwid;j++)
for(i=0;i<lhei;i++)
if(linearrbmp[lk][i][j]){
zftx[j]++; //统计前计算每列点数,列切割
}
vector< vector<bool> > ziarrbmp[50]; //每个字矩阵放在 ziarrbmp[n]里
vector< bool > ziarrx;
int k,x1,x2,y1,y2,flag=0;
int numZi=0,overwid=0,sumwid=0;
for(numZi=0,k=0 ; k < lwid ; k++ ) //开始列切割
//列切割时为了防止把一个字切成多个字(如‘门’),
//使用平均宽度overwid处理
{
if( flag==0 && zftx[k]!=0 )
if( zftx[k+1]>0 && zftx[k-1]==0 )
{ x1=k; flag=1; }
if( flag==1 && zftx[k]==0 && zftx[k+1]==0 )
{
x2=k;
if( numZi>2 )
if( ( overwid-(x2-x1) )>10 )
if( !(
zftx[x1+overwid-2] &&
zftx[x1+overwid-1] &&
zftx[x1+overwid ] &&
zftx[x1+overwid+1] &&
zftx[x1+overwid+2]
)
)
{
k=x1+overwid;
x2=k;
}
sumwid+=(x2-x1);
overwid=sumwid/(numZi+1);//计算平均宽度
flag=0;
// // 定汉字的y1,y2
int zfty[500]={0}; //统计
for(i=0;i<lhei;i++)
for(j=x1;j<x2;j++)
if(linearrbmp[lk][i][j])
zfty[i]++;
for( i = 0 ; i < ImageHeight ; i++ )
if( zfty[i] != 0 ) { y1=i; break; }
for( i = ImageHeight-1 ; i >= 0 ; i-- )
if( zfty[i] != 0 ) { y2=i; break; }
// 储存汉字矩阵
for(i=y1;i<=y2;i++)
{
for(j=x1;j<=x2;j++)
ziarrx.push_back (linearrbmp[lk][i][j]);
ziarrbmp[numZi].push_back (ziarrx);
ziarrx.clear ();
}
numZi++; //字数++
}
} //已把此行的字加入到了矩阵,开始识别
BYTE * pixel[50] , * tPixel[50];
int m , hei , wid ;
ofstream mf("meiyilie.txt",ios::out);//把每列输出测试用
for(k=0;k<numZi;k++)
for(i=0;i<ziarrbmp[k].size();i++)
{
for(j=0;j<ziarrbmp[k][i].size();j++)
if(ziarrbmp[k][i][j]) mf<<"w";
else mf<<" ";
mf<<endl;
}
mf.close(); //对numZi个字开始识别
for(k=0;k<numZi;k++)
{
pixel[k]=new BYTE[ ziarrbmp[k].size() * ziarrbmp[k][0].size() ];
m=0;
hei=ziarrbmp[k].size ();
wid=ziarrbmp[k][0].size ();
for(i=0;i<hei;i++)
{
for(j=0;j<wid;j++)
{
if(ziarrbmp[k][i][j]) //建立同大小的pixel[];
pixel[k][i*wid+j]=255;
else
pixel[k][i*wid+j]=0;
}
}
ZoomInterpolation( pixel[k] , tPixel[k] , wid , hei ); //放大到 tPixel[k] 32*32
BYTE * pixel2 ;
pixel2=new BYTE[32*32];
ofstream file7("待识别的字.txt",ios::app);
for(i=0;i<32;i++)
{
//由tPixel[k] 32*32 -到----pixel2 二值0,1
for(j=0;j<32;j++)
if( tPixel[k][i*32+j] )
{ pixel2[i*32+j]=1; file7<<"w"; }
else
{ pixel2[i*32+j]=0; file7<<" "; }
file7<<"/n";
}
int x,y,pipie; //
vector<string> arri;
ofstream file2("合适的字.txt",ios::app); //
for(i=0;i<MAX;i++) //对2500个字进行匹配查找
{
pipie=0;
for(j=0;j<10;j++) //计算匹配率
{
x=chlist[i].key [j][0];
y=chlist[i].key [j][1];
if( pixel2[ y*32+x ] )
pipie++;
}
if( pipie>=9 ) //匹配率达到9
{
arri.push_back (chlist[i].hanzi); //把候选字加入到候选列表(粗匹配)
file2<<k<<" "<< chlist[i].hanzi <<" "<<pipie<<" ";
}
}
file2<<"/n";
file2.close ();
vector<double> arrpipeilv; //匹配率
int n=0,flagn=0;
//每个候选列表的字,对矩阵进行(细匹配),找到匹配汉字
for( n = 0 ; n < arri.size() ; n++ )
{
BYTE *pixel6=new BYTE[32*32];
string c=(string)arri[n];
GetZiBYTE( c , pixel6 );
for( i=0; i<32; i++ )
for( j=0; j<32; j++ )
if(pixel6[i*32+j]>50) pixel6[i*32+j]=1;
else pixel6[i*32+j]=0;
int summ=0;
for( i=0; i<32; i+=2 )
{
for( j=0; j<32; j+=2 )
if( pixel2[ i*32+j ] == pixel6[ i*32+j ] )
++summ;
}
arrpipeilv.push_back ( (double)summ/256 );
if(arrpipeilv[n]>0.90) { flagn=1; break; }
}
int maxi=0;
if(!flagn)
{
double max=0.0;
for( i = 0 ; i < arri.size() ; i++ )
if( max < arrpipeilv[i] ) { max=arrpipeilv[i]; maxi=i; }
}
else maxi=n;
if(arri.size()) strjieguo+=arri[maxi]+'+';
else strjieguo+="*+";
}
strjieguo+="";
}
::SetDlgItemText (*this,IDC_EDIT1,strjieguo.c_str() );
}
2.2.3.4界面如图3:
图3
详细代码和注释请详见附件:手动提取关键点(暂时只可识别图片的格式为8位的灰度图像或通过键盘Pint Screen截得的图片);
注:部分预处理函数为修改 参考文献[3] 书中的代码
2.3此示例系统开发的识别效果测试:
下面通过对下面一短图片进行测试,并说明修正关键点的方法
下面是 /关键点识别/测试图片/测试图片4.bmp,如图4:
图4
(1) 由自动提前关键点提取点;保存到 关键点.txt
(2) 用 关键点识别 实施识别,结果为:乙+二+十+丁+*+七+卜+夫+*+九+几+了+*+乃+
(3) 对未能识别的汉字 :厂、人、入、力,使用手动提取关键点重新人工修正
修正前:
厂 31 0 31 1 30 1 0 31 0 30 3 25 6 4 5 16 22 2 6 11
人 14 0 0 31 1 31 31 29 8 26 25 27 13 18 16 10 18 15 22 23
入 10 0 9 0 10 1 28 31 0 31 13 3 31 28 27 31 12 16 7 24
力 31 9 16 0 31 10 24 31 0 31 4 30 29 19 11 24 20 28 22 29
修正后:
厂29 1 22 1 6 1 4 7 4 13 5 17 3 20 2 27 1 29 16 2
人15 2 14 4 15 8 13 14 10 21 6 25 20 19 24 25 27 28 28 27
入9 0 14 4 15 8 12 13 10 18 4 26 21 22 27 27 28 29 1 29
力16 10 23 9 6 9 29 10 29 17 26 27 6 27 13 21 16 1 23 28
(4) 识别结果为:乙+二+十+丁+厂+七+卜+人+入+九+几+了+力+乃+
2.4此示例系统开发的不足即可改进方面:
关于此系统还可以进行大量修改,本项目仅作为对此方法的简单的实现,以证明此理论的可行性,大部分代码可以采用更快的结构和算法,通过下面几个方面的改进,估计可使识别效率部分性能达到商业ocr标准。
系统可进行修改的方面有:
(1) 关键点识别在提取候选字计算匹配点数时,可以对特征点建立搜索树等,可提高识别速度;
(2) 自动提取的关键点应进行大量的人工检测此组点可靠性及误识数,并使用开发的手动提取关键点软件进行大量的修改,以达到关键点的可靠性最高,误识数最低;
(3) 关键点识别在识别以前应对汉字的粗细进行判断,并通过梯度(或其他)算法调整粗细;
(4) 一些地方对一些数据进行了重复统计,降低了速度;
由于笔者时间有限并未修改,请见谅。
3特征点标识法与其他方法的异同及意义
3.1特征点标识法与其他方法的异同
经查阅资料,确实存在一种叫汉字特征点法的建立字典的方法,但是此种方法提取字典比较繁琐(其中可能涉及到对线段进行Hough变换并需要区分笔画特征点),下面对这两种方法进行比较:
汉字特征点法:先细化出汉字骨架,因为汉字信息的绝大部分集中在汉字骨架上,而骨架信息又大量集中在若干特征点上(笔画特征点),所以取特征点像端点、折点、交点,所取得点的个数随库汉字的量增加而增加,但受于细化技术不成熟,应用到识别上时这种方法会受到部分限制。
特征点标识法:先细化出汉字骨架,再在骨架上提取尽量能唯一标识此汉字的特征点,识别时先通过特征点标识法粗筛选出一部分汉字,相对汉字特征点法,在OCR识别中,粗筛选出的汉字更少,再细识别提取出汉字,从某些角度特征点标识法容易实现。
3.2特征点标识法的意义
综上可看出特征点标识法的优点就是思路简单,对于特定问题若能实现,无论从识别速度或准确可以达到较高水平,缺点:只能针对特定问题开发特定的程序,以此特定问题数据得到标识特征点,所开发的OCR软件具有较大的针对性。
除了识别棋盘及识别汉字,通过特征点标识法我们还可以对其他一些位置等性质一定的特定问题实现开发识别。
参考文献
[1] http://www.elephantbase.net/computer/stepbystep2.htm 象棋小巫师代码
[2]精通Visual C++数字图像模式识别技术及工程实践 张宏林
[3] Visual C++_MATLAB图像处理与识别实用案例精选 胡小锋