人工智能实战2019 - 第4次作业(团队中期项目)- 就起这个名字吧
项目 | 内容 |
---|---|
课程 | 人工智能实战2019 |
作业要求 | 第4次作业 |
课程目标 | 学习人工智能基础知识 |
本次作业对我的帮助 | STM32进阶,LDA 算法 |
参考文献 | 正点原子,LDA |
手写识别简介
不再赘述手写识别的相关背景。。。
目前用于手写识别的设备有许多种,比如电磁感应手写板、压感式手写板、触摸屏、触摸屏、超声波笔等。ALIENTEK MiniSTM32 开发板自带 2.8 寸电阻型触摸屏,可以用来作为手写识别的输入设备。
手写数字识别系统如下图所示:
虚线部分为训练学习过程,对数据样本进行传统的方向特征提取,提取后特征维数为 512 维。对于单片机来说,如此合成的模板库的存储量过大,需要降维处理。常用方法为线性判别分析(Linear Discriminant Analysis, LDA)。
LDA 原理是,将带上标签的数据,通过投影的方法,投影到维度更低的空间中,使得投影后的点,会形成按类别区分,一簇一簇的情况;相同类别的点,将会在投影后的空间中更接近。
对于识别过程,首先得到触屏输入的有序轨迹,然后进行预处理,包括重采样、归一化处理。重采样主要是因为不同的输入设备、不同的输入方式所产生的有序序列不同,为了达到更好的识别结果,我们需要对训练样本和识别输入进行重采样处理。归一化是因为不同的书写风格、采样分辨率的差异会导致字体大小不同,需要对输入轨迹进行归一化。
预处理后的输入为 \(64 \times 64\),切分成 \(8 \times 8\) 的小方格,每个方格 \(8 \times 8\) 个像素。总共 64 个格子,于是一个样本最终能得到 \(64 \times 8=512\) 维特征。
通过 LDA 降维计算,得到 \(512 \times 64\) 维的矩阵,通过矩阵运算得到 64 维的最终特征值。
最后将这 64 维特征分别于模板中的特征进行求距离运算。得到最小的距离为该输入的最佳识别结果输出。
实现步骤:
1.调用初始化函数
- 初始化识别器
2.获取输入的点阵数据
- 至少输入2个不同坐标的点阵数据
- 输入的点不要太多,太多的点阵数据需要更多的内存,推荐的输入点数范围为100-200
3.调用识别函数,得到识别结果
- 有5个参数,分别为输入轨迹的坐标集、坐标集点坐标的个数、期望输出的结果(按匹配程度排序输出)、模式设置
- 结果采用 ASCII 码格式存储
4.调用终止函数
- 终止识别器
- 如果继续识别,重复步骤 2 和步骤 3
硬件连接
硬件比较简单,开发板+液晶模块即可。
我们在手写区写数字或字符,每次写入结束后,自动进入识别状态,然后将识别结果输出到LCD模块,同时打印到串口。通过按KEY1,可以进行模式切换;通过按KEY0,可以进入触摸屏校准。DSO用于表示程序运行状态。
软件实现
正点原子提供手写识别的lib供大家使用,但不提供源码。。。
各种具体辅助函数的实现方法不去赘述,主函数如下:
//最大记录的轨迹点数
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(); //触摸屏初始化
mem_init(); //初始化内部内存池
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:
POINT_COLOR = RED;
Show_Str(40, 10, 200, 16, "MiniSTM32 开发板", 16, 0);
Show_Str(40, 30, 200, 16, "手写识别实验", 16, 0);
Show_Str(40, 50, 200, 16, "就起这个名字吧@AI实战2019", 16, 0);
Show_Str(40, 70, 200, 16, "KEY1:MODE KEY0:Adjust", 16, 0);
Show_Str(40, 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 == KEY0_PRES)
{
TP_Adjust(); //屏幕校准
LCD_Clear(WHITE);
goto RESTART; //重新加载界面
}
if (key == KEY1_PRES)
{
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[0] < 220 && tp_dev.x[0] >= 20) && (tp_dev.y[0] < 315 && tp_dev.y[0] >= 115))
{
TP_Draw_Big_Point(tp_dev.x[0], tp_dev.y[0], BLUE);//画图
if (pcnt < 200)//总点数少于 200
{
if (pcnt)
{
if ((READ_BUF[pcnt - 1].y != tp_dev.y[0]) && (READ_BUF
[pcnt - 1].x != tp_dev.x[0]))//x,y 不相等
{
READ_BUF[pcnt].x = tp_dev.x[0];
READ_BUF[pcnt].y = tp_dev.y[0];
pcnt++;
}
}
else
{
READ_BUF[pcnt].x = tp_dev.x[0];
READ_BUF[pcnt].y = tp_dev.y[0];
pcnt++;
}
}
}
}
else //按键松开了
{
tcnt++;
delay_ms(10);
i++;
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; }
}
}
验证
在代码编译成功后,我们烧录程序到 ALIENTEK MiniSTM32 开发板上:
在手写区手写数字/字母,即可得到识别结果:
每次识别结束,会在串口打印概率最高的识别结果,可通过串口调试助手查看:
总结
- 可作为小型应用,以识别结果为反馈,通过触摸屏搜集高质量的手写图片