【正点原子STM32H7R3开发套件试用体验】手写数字、字母的识别
本文介绍了利用正点原子提供的手写识别库,在STM32H7R3开发板上实现 0-9 数字和 A-Z,a-z 字母的手写识别。
简介
手写识别,是指对在手写设备上书写时产生的有序轨迹信息进行识别的过程,是人际交互最自然、最方便的手段之一。
随着智能手机和平板电脑等移动设备的普及,手写识别的应用也被越来越多的设备采用。手写识别能够使用户按照最自然、最方便的输入方式进行文字输入,易学易用,可取代键盘或者鼠标。
用于手写输入的设备有许多种,比如电磁感应手写板、压感式手写板、触摸屏、触控板、超声波笔等。
原理
手写识别分为两个过程:训练学习过程、识别过程。
手写数字和字母的系统示意图如下

采样
首先需要使用设备采集大量数据样本,样本类别数目为0-9,a-z,A-Z 共 62 类,每个类别 5-10 个样本不等(样本越多识别率就越高)。对这些样本进行传统的八方向特征提取,提取后特征维数为 512 维,采用 LDA 线性判决分析的方法进行降维,这里将维度降到 64 维度,然后针对各个样本类别进行平均计算的到该类别的样本模板。
识别
对于识别过程,首先需要得到触屏输入的有序轨迹,然后进行一些预处理。
预处理主要包括重采样,归一处理。
重采样主要是因为不同的输入设备不同的输入处理方式产生的有序轨迹序列有所不同。为了达到更好的识别结果我们需要对训练样本和识别输入的样本进行重采样处理,这里使用隔点重采样的方法对输入序列进行重采样;
归一化是因为不同的书写风格采用分辨率的差异会导致字体太小不同,因此需要对输入轨迹进行归一化。 这里把样本进行线性缩放的方法归一化为 64x64 像素。
训练
由于训练过程进行了 LDA 降维计算,所以识别过程同样需要对应的 LDA 降维过程得到最终的 64 维特征。
计算过程是在训练模板的过程中可以运算得到一个 512x64 维的矩阵,通过矩阵乘运算可以得到 64 维的最终特征值。最后将 64 维特征分别与模板中的特征进行求距离运算。得到最小的距离为该输入的最佳识别结果输出。
硬件准备
- LED
LED0 - PD14
LED1 - PC0
- 独立按键
KEY0 - PE9
KEY1 - PE8
KEY2 - PE7
WK_UP - PC13
- USART1
USART1_TX - PB14
USART1_RX - PB15
- 正点原子 2.8/3.5/4.3/7寸LCD模块
- MPU
- NOR Flash
- SD NAND
- SD卡(若提示无字库,则需要先运行字库录入工程)
项目实现
- 开机,初始化手写识别器,检测字库,进入等待输入状态;
- 手写区写数字字符,在每次写入结束后,自动进入识别状态,进行识别,然后将识别结果输出在 LCD 模块上,同时打印到串口;
- 通过按 KEY0 可以进行模式切换( 4 种模式都可以测试),通过按 KEY_UP ,可以进入触摸屏校准(仅电阻屏需要校准,如果发现触摸屏不准,可执行此操作)
- LED0 闪烁用于提示程序正在运行。
代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./MALLOC/malloc.h"
#include "./FATFS/exfuns/exfuns.h"
#include "./TEXT/text.h"
#include "./ATKNCR/atk_ncr.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/HYPERRAM/hyperram.h"
#include "./BSP/SDNAND/spi_sdnand.h"
#include "./BSP/SDMMC/sdmmc_sdcard.h"
#include "./BSP/TOUCH/touch.h"
static atk_ncr_point ncr_input_buf[200];
static void lcd_draw_bline(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t size, uint16_t color)
{
uint16_t t;
int xerr = 0;
int yerr = 0;
int delta_x;
int delta_y;
int distance;
int incx;
int incy;
int row;
int col;
if ((x1 < size) || (x2 < size) || (y1 < size) || (y2 < size))
{
return;
}
delta_x = x2 - x1;
delta_y = y2 - y1;
row = x1;
col = y1;
if (delta_x > 0)
{
incx = 1;
}
else if (delta_x == 0)
{
incx = 0;
}
else
{
incx = -1;
delta_x = -delta_x;
}
if (delta_y > 0)
{
incy = 1;
}
else if (delta_y == 0)
{
incy = 0;
}
else
{
incy = -1;
delta_y = -delta_y;
}
if (delta_x > delta_y)
{
distance = delta_x;
}
else
{
distance = delta_y;
}
for (t=0; t<=(distance + 1); t++)
{
lcd_fill_circle(row, col, size, color);
xerr += delta_x;
yerr += delta_y;
if (xerr > distance)
{
xerr -= distance;
row += incx;
}
if (yerr > distance)
{
yerr -= distance;
col += incy;
}
}
}
int main(void)
{
uint32_t t = 0;
uint8_t tcnt;
uint8_t key;
uint8_t res;
uint8_t mode = 4;
uint16_t lastpos[2];
uint16_t pcnt = 0;
char sbuf[10];
sys_mpu_config();
sys_cache_enable();
HAL_Init();
sys_stm32_clock_init(300, 6, 2);
delay_init(600);
usart_init(115200);
led_init();
key_init();
hyperram_init();
lcd_init();
tp_dev.init();
my_mem_init(SRAMIN);
my_mem_init(SRAMEX);
my_mem_init(SRAM12);
my_mem_init(SRAMDTCM);
my_mem_init(SRAMITCM);
exfuns_init();
f_mount(fs[0], "0:", 1);
f_mount(fs[1], "1:", 1);
f_mount(fs[2], "2:", 1);
alientek_ncr_init();
while (fonts_init() != 0)
{
lcd_show_string(30, 10, 200, 16, 16, "Font error! ", RED);
delay_ms(200);
lcd_show_string(30, 10, 200, 16, 16, "Please Check! ", RED);
delay_ms(200);
}
RESTART:
text_show_string(30, 10, 200, 16, "正点原子STM32开发板", 16, 0, RED);
text_show_string(30, 30, 200, 16, "手写识别实验", 16, 0, RED);
text_show_string(30, 50, 200, 16, "ATOM@ALIENTEK", 16, 0, RED);
text_show_string(30, 70, 200, 16, "KEY0: Mode KEY1: Adjust ", 16, 0, RED);
text_show_string(30, 90, 200, 16, "识别结果:", 16, 0, RED);
lcd_draw_rectangle(19, 114, lcddev.width - 20, lcddev.height - 5, RED);
text_show_string(96, 207, 200, 16, "手写区", 16, 0, BLUE);
tcnt = 100;
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES)
{
if ((tp_dev.touchtype & 0x80) == 0)
{
tp_adjust();
goto RESTART;
}
}
else if (key == KEY0_PRES)
{
lcd_fill(20, 115, 219, 315, WHITE);
tcnt = 100;
if (++mode > 4)
{
mode = 1;
}
if (mode == 1)
{
text_show_string(80, 207, 200, 16, "仅识别数字", 16, 0, BLUE);
}
else if (mode == 2)
{
text_show_string(64, 207, 200, 16, "仅识别大写字母", 16, 0, BLUE);
}
else if (mode == 3)
{
text_show_string(64, 207, 200, 16, "仅识别小写字母", 16, 0, BLUE);
}
else if (mode == 4)
{
text_show_string(88, 207, 200, 16, "全部识别", 16, 0, BLUE);
}
}
tp_dev.scan(0);
if (tp_dev.sta & TP_PRES_DOWN)
{
tcnt = 0;
if (((tp_dev.x[0] < (lcddev.width - 20 - 2)) && (tp_dev.x[0] >= (20 + 2))) &&
((tp_dev.y[0] < (lcddev.height - 5 - 2)) && (tp_dev.y[0] >= (115 + 2))))
{
if (lastpos[0] == 0xFFFF)
{
lastpos[0] = tp_dev.x[0];
lastpos[1] = tp_dev.y[0];
}
lcd_draw_bline(lastpos[0], lastpos[1], tp_dev.x[0], tp_dev.y[0], 2, BLUE);
lastpos[0] = tp_dev.x[0];
lastpos[1] = tp_dev.y[0];
if (pcnt < 200)
{
if (pcnt != 0)
{
if ((ncr_input_buf[pcnt - 1].y != tp_dev.y[0]) &&
(ncr_input_buf[pcnt - 1].x != tp_dev.x[0]))
{
ncr_input_buf[pcnt].x = tp_dev.x[0];
ncr_input_buf[pcnt].y = tp_dev.y[0];
pcnt++;
}
}
else
{
ncr_input_buf[pcnt].x = tp_dev.x[0];
ncr_input_buf[pcnt].y = tp_dev.y[0];
pcnt++;
}
}
}
}
else
{
lastpos[0] = 0xFFFF;
tcnt++;
if (tcnt == 40)
{
if (pcnt != 0)
{
printf("总点数:%d\r\n", pcnt);
alientek_ncr(ncr_input_buf, pcnt, 6, mode, sbuf);
printf("识别结果:%s\r\n", sbuf);
pcnt = 0;
lcd_show_string(60 + 72, 90, 200, 16, 16, sbuf, BLUE);
}
lcd_fill(20, 115, lcddev.width - 20 - 1, lcddev.height - 5 - 1, WHITE);
}
}
if (++t == 20)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
效果
手写数字识别

手写字母识别

完整项目工程见附件。
*附件:Demo4_HandWriteNum.zip
总结
本文介绍了手写数字字母识别的基本原理以及将其部署于单片机的流程,使用正点原子STM32H7R3开发套件实现了手写数字和字母的识别,通过触摸屏获取模型,进而分析轨迹,根据训练模型的结果,得到分析结果并显示。
数字和字母识别在生活中有诸多应用,如车牌号的识别、快递号识别等,相信随着算法和模型的不断发展演化与进步,数字识别必将发挥越来越重要的作用,本文为数字识别以及人工智能的开发提供了相关参考。