人工智能实战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 维的最终特征值。

\[\left[d_{1}, d_{2}, \cdots, d_{512}\right] \times \left[ \begin{array}{ccc}{l} & {\cdots} & {l} \\ {\vdots} & {\ddots} & {\vdots} \\ {l} & {\cdots} & {l}\end{array}\right]=\left[f_{1}, f_{2}, \cdots, f_{64}\right] \]

最后将这 64 维特征分别于模板中的特征进行求距离运算。得到最小的距离为该输入的最佳识别结果输出。

\[output=\underset{i \in[1,62]}{\arg \min }\left\{\left(f_{1}-f_{1}^{i}\right)^{2}+\left(f_{2}-f_{2}^{i}\right)^{2}+\cdots+\left(f_{64}-f_{64}^{i}\right)^{2}\right\} \]

实现步骤:
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 开发板上:
VK9Y2F.jpg
在手写区手写数字/字母,即可得到识别结果:
VK9ar9.jpg
每次识别结束,会在串口打印概率最高的识别结果,可通过串口调试助手查看:
VJBNKe.png

总结


  • 可作为小型应用,以识别结果为反馈,通过触摸屏搜集高质量的手写图片
posted @ 2019-05-29 02:18  WangShihong  阅读(225)  评论(0编辑  收藏  举报