关于手写识别原理,我们就介绍到这里。如果想自己实现手写识别,那得花很多时间学习和研究,但是如果只是应用的话,那么就只需要知道怎么用就OK了,相对来说,简单的多。
ALIENTEK提供了一个数字字母识别库,这样我们不需要关心手写识别是如何实现的,只需要知道这个库怎么用,就能实现手写识别。ALIENTEK提供的手写识别库由4个文件组成:
ATKNCR_M_V2.0.lib、ATKNCR_N_V2.0.lib、atk_ncr.c和atk_ncr.h。
ATKNCR_M_V2.0.lib和ATKNCR_N_V2.0.lib是两个识别用的库文件(两个版本),使用的时候,选择其中之一即可。ATKNCR_M_V2.0.lib用于使用内存管理的情况,用户必须自己实现alientek_ncr_malloc和alientek_ncr_free两个函数。而ATKNCR_N_V2.0.lib用于不使用内存管理的情况,通过全局变量来定义缓存区,缓存区需要提供至少3K左右的RAM。大家根据自己的需要,选择不同的版本即可。ALIENTEK手写识别库资源需求:FLASH:52K左右,RAM:6K左右。
atk_ncr.c代码如下:
#include "atk_ncr.h"
#include "malloc.h"
//内存设置函数
void alientek_ncr_memset(char *p,char c,unsigned long len)
{
mymemset((u8*)p,(u8)c,(u32)len);
}
//内存申请函数
void *alientek_ncr_malloc(unsigned int size)
{
return mymalloc(SRAMIN,size);
}
//内存清空函数
void alientek_ncr_free(void *ptr)
{
myfree(SRAMIN,ptr);
}
这里,主要实现了alientek_ncr_malloc、alientek_ncr_free和alientek_ncr_memset等三个函数。
atk_ncr.h则是识别库文件同外部函数的接口函数声明
#ifndef __ATK_NCR_H
#define __ATK_NCR_H
//当使用ATKNCR_M_Vx.x.lib的时候,不需要理会ATK_NCR_TRACEBUF1_SIZE和
//ATK_NCR_TRACEBUF2_SIZE
//当使用ATKNCR_N_Vx.x.lib的时候,如果出现识别死机,请适当增加
//ATK_NCR_TRACEBUF1_SIZE和ATK_NCR_TRACEBUF2_SIZE的值
#define ATK_NCR_TRACEBUF1_SIZE 500*4
//定义第一个tracebuf大小(单位为字节),如果出现死机,请把该数组适当改大
#define ATK_NCR_TRACEBUF2_SIZE 250*4
//定义第二个tracebuf大小(单位为字节),如果出现死机,请把该数组适当改大
//输入轨迹坐标类型
__packed typedef struct _atk_ncr_point
{
short x; //x轴坐标
short y; //y轴坐标
}atk_ncr_point;
//外部调用函数
//初始化识别器
//返回值:0,初始化成功
// 1,初始化失败
unsigned char alientek_ncr_init(void);
void alientek_ncr_stop(void); //停止识别器
//识别器识别
//track:输入点阵集合
//potnum:输入点阵的点数,就是track的大小
//charnum:期望输出的结果数,就是你希望输出多少个匹配结果
//mode:识别模式
//1,仅识别数字
//2,进识别大写字母
//3,仅识别小写字母
//4,混合识别(全部识别)
//result:结果缓存区(至少为:charnum+1个字节)
void alientek_ncr(atk_ncr_point * track,int potnum,int charnum,unsigned char mode,char*result);
void alientek_ncr_memset(char *p,char c,unsigned long len); //内存设置函数
//动态申请内存,当使用ATKNCR_M_Vx.x.lib时,必须实现.
void *alientek_ncr_malloc(unsigned int size);
//动态释放内存,当使用ATKNCR_M_Vx.x.lib时,必须实现.
void alientek_ncr_free(void *ptr);
#endif
此段代码中,我们定义了一些外部接口函数以及一个轨迹结构体等。
alientek_ncr_init,该函数用与初始化识别器,该函数在.lib文件实现,在识别开始之前,我们应该调用该函数。
alientek_ncr_stop,该函数用于停止识别器,在识别完成之后(不需要再识别),我们调用该函数,如果一直处于识别状态,则没必要调用。该函数也是在.lib文件实现。
alientek_ncr,该函数就是识别函数了。它有5个参数,第一个参数track,为输入轨迹点的坐标集(最好200以内);第二个参数potnum,为坐标集点坐标的个数;第三个参数charnum,为期望输出的结果数,即希望输出多少个匹配结果,识别器按匹配程度排序输出(最佳匹配排第一);第四个参数mode,该函数用于设置模式,识别器总共支持4中模式:
1,仅识别数字
2,进识别大写字母
3,仅识别小写字母
4,混合识别(全部识别)
最后一个参数是result,用来输出结果,注意这个结果是ASCII码格式的。
alientek_ncr_memset、alientek_ncr_free和alientek_ncr_free这3个函数在atk_ncr.c里面实现,这里就不多说了。
最后,我们看看通过ALIENTEK提供的手写数字字母识别库实现数字字母识别的步骤:
1) 调用alientek_ncr_init函数,初始化识别程序
该函数用来初始化识别器,在手写识别进行之前,必须调用该函数。
2) 获取输入的点阵数据
此步,我们通过触摸屏获取输入轨迹点阵坐标,然后存放到一个缓存区里面,注意至少要输入2个不同坐标的点阵数据,才能正常识别。注意输入点数不要太多,太多的话,需要更多的内存,我们推荐的输入点数范围:100~200点。
3) 调用alientek_ncr函数,得到识别结果.
通过调用alientek_ncr函数,我们可以得到输入点阵的识别结果,结果将保存在result参数里面,采用ASCII码格式存储
4) 调用alientek_ncr_stop函数,终止识别.
如果不需要继续识别,则调用alientek_ncr_stop函数,终止识别器。如果还需要继续识别,重复步骤2和步骤3即可。
以上4个步骤,就是使用ALIENTEK手写识别库的方法,十分简单。
51.2 硬件设计
本章实验功能简介:开机的时候先初始化手写识别器,然后检测字库,之后进入等待输入状态。此时,我们在手写区写数字/字符,在每次写入结束后,自动进入识别状态,进行识别,然后将识别结果输出在LCD模块上面(同时打印到串口)。通过按KEY0可以进行模式切换(4种模式都可以测试),通过按KEY2,可以进入触摸屏校准(如果发现触摸屏不准,请执行此操作)。DS0用于指示程序运行状态。
本实验用到的资源如下:
1) 指示灯DS0
2) KEY0和KEY2两个按键
3) 串口
4) TFTLCD模块(含触摸屏)
5) SPI FLASH
这些用到的硬件,我们在之前都已经介绍过,这里就不再介绍了。
51.3 软件设计
打开上一章的工程,首先在HARDWARE文件夹所在的文件夹下新建一个ATKNCR的文件夹。将ALIETENK提供的手写识别库文件(ATKNCR_M_V2.0.lib、ATKNCR_N_V2.0.lib、atk_ncr.c和atk_ncr.h这四个个文件,在光盘à 4,程序源码à ATKNCR(数字字母手写识别库) 文件夹里面)拷贝到该文件夹下,然后在工程里面新建一个ATKNCR的组,将atk_ncr.c和ATKNCR_M_V2.0.lib加入到该组下面(这里我们使用内存管理版本的识别库)。最后,将ATKNCR文件夹加入头文件包含路径。
关于ATKNCR_M_V2.0.lib和atk_ncr.c前面已有介绍,我们这里就不再多说,我们在test.c里面修改main函数如下:
//最大记录的轨迹点数
atk_ncr_point READ_BUF[200];
int main(void)
{
u8 i=0;
u8 tcnt=0;
u8 res[10];
u8 key;
u16 pcnt=0;
u8 mode=4; //默认是混合模式
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口1初始化
LCD_Init(); //初始化液晶
LED_Init(); //LED初始化
KEY_Init(); //按键初始化
TP_Init(); //触摸屏初始化
usmart_dev.init(72); //usmart初始化
mem_init(SRAMIN); //初始化内部内存池
alientek_ncr_init(); //初始化手写识别
POINT_COLOR=RED;
while(font_init()) //检查字库
{
LCD_ShowString(60,50,200,16,16,"Font Error!"); delay_ms(200);
LCD_Fill(60,50,240,66,WHITE);//清除显示
}
RESTART:
Show_Str(60,10,200,16,"战舰 STM32开发板",16,0);
Show_Str(60,30,200,16,"手写识别实验",16,0);
Show_Str(60,50,200,16,"正点原子@ALIENTEK",16,0);
Show_Str(60,70,200,16,"KEY0:MODE KEY2:Adjust",16,0);
Show_Str(60,90,200,16,"识别结果:",16,0);
LCD_DrawRectangle(19,114,220,315);
POINT_COLOR=BLUE;
Show_Str(96,207,200,16,"手写区",16,0);
tcnt=100;
while(1)
{
key=KEY_Scan(0);
if(key==KEY_LEFT)
{
TP_Adjust(); //屏幕校准
LCD_Clear(WHITE);
goto RESTART; //重新加载界面
}
if(key==KEY_RIGHT)
{
LCD_Fill(20,115,219,314,WHITE);//清除当前显示
mode++;
if(mode>4)mode=1;
switch(mode)
{
case 1: Show_Str(80,207,200,16,"仅识别数字",16,0); break;
case 2: Show_Str(64,207,200,16,"仅识别大写字母",16,0); break;
case 3: Show_Str(64,207,200,16,"仅识别小写字母",16,0); break;
case 4: Show_Str(88,207,200,16,"全部识别",16,0); break;
}
tcnt=100;
}
tp_dev.scan(0);//扫描
if(tp_dev.sta&TP_PRES_DOWN)//有按键被按下
{
delay_ms(1);//必要的延时,否则老认为有按键按下.
tcnt=0;//松开时的计数器清空
if((tp_dev.x<220&&tp_dev.x>=20)&&(tp_dev.y<315&&tp_dev.y>=115))
{
TP_Draw_Big_Point(tp_dev.x,tp_dev.y,BLUE);//画图
if(pcnt<200)//总点数少于200
{
if(pcnt)
{
if((READ_BUF[pcnt-1].y!=tp_dev.y)&&(READ_BUF[pcnt-1].x!=
tp_dev.x))//x,y不相等
{
READ_BUF[pcnt].x=tp_dev.x;
READ_BUF[pcnt].y=tp_dev.y;
pcnt++;
}
}else
{
READ_BUF[pcnt].x=tp_dev.x;
READ_BUF[pcnt].y=tp_dev.y;
pcnt++;
}
}
}
}else //按键松开了
{
i++;tcnt++;delay_ms(10); //延时识别
if(tcnt==40)
{
if(pcnt)//有有效的输入
{
printf("总点数:%d\r\n",pcnt);
alientek_ncr(READ_BUF,pcnt,6,mode,(char*)res);
printf("识别结果:%s\r\n",res);
pcnt=0;
POINT_COLOR=BLUE;//设置画笔蓝色
LCD_ShowString(60+72,90,200,16,16,res);
}
LCD_Fill(20,115,219,314,WHITE);
}
}
if(i==30) { i=0; LED0=!LED0;}
}
}
该函数同触摸屏实验的main函数有点类似,不过加入了一些处理,以实现51.1.2节提到的功能。其中,READ_BUF用来存储输入轨迹点阵,大小为200,即最大输入不能超过200点,注意:这里我们采集的都是不用的点阵(即相邻的坐标不相等)。这样可以大大减少重复点阵的大小,而重复点阵对识别是没有帮助的。
至此,本实验的软件设计部分结束。
51.4 下载验证
在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,得到,如图51.4.1所示:
图51.4.1 手写识别界面
此时,我们在手写区写数字/字母,即可得到识别结果,如图51.4.2所示:
图51.4.2 手写识别结果
按下KEY0可以切换识别模式,同时在识别区提示当前模式。按下KEY2可以进行屏幕校准。每次识别结束,会在串口打印本次识别的输入点数和识别结果,大家可以通过串口助手查看。