【教程】M5Stack UnitV2应用之手写识别简易计算器-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4241|回复: 3

【教程】M5Stack UnitV2应用之手写识别简易计算器

[复制链接]
发表于 2021-8-14 20:44 | 显示全部楼层 |阅读模式
本帖最后由 沧海笑1122 于 2021-8-14 20:50 编辑

【项目故事】
这是一个M5Stack UnitV2的趣味应用,使用V-Training平台训练自己的手写数字笔迹,然后用M5Stack UnitV2利用目标检测识别出你的手写算式,将结果用Json串发送到M5Stack Basic上进行展示(屏显并发出语音提示)。关于M5StackUnitV2这块AI智能摄像头的基本情况,我在前一篇文章中详述,在此不再赘述,大家可以参考我的上一篇《工业应用场景下的M5Stack UnitV2应用之不良品分选》https://www.arduino.cn/thread-104690-1-1.html
一起先看看手写识别简易计算器的效果。
所谓简易计算器,是指本文仅实现了个位数的加减法计算,目的是展示UnitV2的训练、使用方式,抛砖引玉,吸引更多玩家以及更好的玩法。
有趣度:★★★★
难度:★★
这是我一如既往的风格:好玩不难。话不多说,我们开始制作吧。

【软硬件准备】
软件:
  
  
名称
版本
备注
1
UIFLOW
1.8.1
设计Basic端的界面
2
Thonny IDE
3.3.13
调试Basic的micropython程序
3
V-Training平台

训练手写数字及运算符
4
M5Stack UnitV2内置应用
目标检测,固件版本0604

5
串口调试工具

用于分步调试
硬件:
  
  
名称
型号
备注
1
M5Stack UnitV2
V2.0
AI智能摄像头
2
M5Stack Basic

上位机,用于解析、展示手写式以及计算结果
3
USB-TTL
CH340
调试串口时使用,正常不需要
4
Uart线
三根(Tx\Rx\Gnd)

再就是一支笔、一张白纸、一个俯拍支架,再就是您的手(手写嘛)。
【软件原理】
l  M5Stack UnitV2侧:使用内置应用的目标检测功能,将手写的数字以及运算符(如+、-、=)进行识别,并且以json格式通过uart发送至上位机处理。
l  M5Stack Basic侧:
basic流程图_a.jpg
UI设计_a.jpg
这是M5Stack Basic侧的UI以及主循环设计。
【制作步骤】
Step1:训练手写数字及运算符模型
(1)    准备若干纸卡片,用墨水笔手写数字,尽可能大小一致、字体保持一致。我准备了约30张卡片,每张有2个算式。为了简化期间,只训练了+、-、=三种运算符以及若干数字。
(2)    使用自己编写的连拍程序,用UnitV2连续拍摄样本,做好取样准备。

(3)    将拍摄的样本,上传http://v-training.m5stack.com/,进行上传、建立lable以及标注工作,这些工作在M5Stack官网以及我上篇文章中都有详述,此处不再赘述。
训练1_a.jpg
Step2:上传模型后,打开目标检测功能,测试识别以及json串的传送情况

将UnitV2固定在俯拍支架上,光源充足的情况下,手写算式,测试目标检测的效果。效果如下:
识别1_a.jpg
注:1是刚才训练好并且上传的模型,UnitV2就是靠这个模型通过目标检测来识别你的手写录入。2是识别并且标注兴趣框的情况,3是识别后的json字符,通过uart发送至上位机进行展示。
Step3:分步调试,用usb-ttl模块加上串口调试器,与M5Stack Basic通信,将收到的json串进行解析和展示。

将UnitV2识别出的Json字符串,复制在串口调试器里,就可以脱离UnitV2进行分步调试。好处是可以简化调试步骤,减少不定因素,分别对M5StackBasic以及UnitV2进行调试。
串口调试助手_a.jpg thonny_a.jpg
注:1、减号识别并且替换成“-”;2、等号识别并且替换成“=”,3、4分别是数字的识别。

Step4:整体联调:将UnitV2(grove)与M5Stack Basic(pin21\pin22\gnd)连接,实现整体联调,在手写一个算式后,V2识别出来并且发送给M5Stack Basic,后者解析、组装算式、计算结果,并且将带有运算结果的算式展示在屏幕上,同时发出“滴”声,提醒玩家识别、计算过程结束。

如果你需要再算一组算式,只需要按“reset”键,即可清屏,为下一次识别计算做准备。

【代码】

M5Stack Basic侧:
  1. # UNITV2 之手写计算器
  2. # 2021-08-06 测试uart1接受V2传送来的识别串,并且生成算式、计算结果并且在屏幕展示
  3. # 2021-08-08 v2送来的Num=4的json串已经可以解析,且放入一个四元素二维数组。
  4. # 2021-08-11 一是按钮reset。二是识别后,显示计算式以及答案,并且确保只要识别到就仅处理一次。三是识别后带语音提示
  5. # 沧海


  6. from m5stack import *
  7. from m5ui import *
  8. from uiflow import *
  9. from easyIO import *
  10. import json

  11. setScreenColor(0x222222)
  12. print("Begin...")

  13. # 变量init
  14. rev_str = None
  15. rev_obj = None
  16. rev_obj_num = None
  17. vprob = None
  18. vtype = None
  19. vx = None
  20. vy = None
  21. vw = None
  22. vh = None
  23. string_result= None
  24. #生成一个2维、四元素数组temp1 ,放置中间结果
  25. temp1 = [['' for i in range(2)] for j in range(4)]
  26. #temp2 是表达式以及答案
  27. temp2 =None
  28. #vexe 执行控制字,确保目标进入执行框后,只执行一次(计数以及吹不良品)
  29. vexe=0
  30. #UI INIT
  31. Calculator = M5Title(title="Calculator 2021-08", x=3, fgcolor=0xFFFFFF, bgcolor=0x0034ff)
  32. label1 = M5TextBox(52, 71, "Text", lcd.FONT_DejaVu56, 0xff8d08, rotate=0)
  33. rectangle0 = M5Rect(30, 187, 80, 40, 0x0efdf9, 0xFFFFFF)
  34. label2 = M5TextBox(37, 194, "Reset", lcd.FONT_Comic, 0x100000, rotate=0)

  35. #Uart INIT
  36. uart1 = machine.UART(1, tx=22, rx=21)
  37. uart1.init(115200, bits=8, parity=None, stop=1)

  38. def buttonA_wasPressed():
  39.   global vexe
  40.   label1.setText('') #清屏
  41.   vexe=0 #复位执行控制字
  42.   pass
  43. btnA.wasPressed(buttonA_wasPressed)
  44.    
  45. #Main loop
  46. while True:
  47.     try:
  48.         if uart1.any():
  49.             wait_ms(100)
  50.             rev_str = json.loads((uart1.read()).decode())
  51.             rev_obj_num=rev_str["num"]
  52.             rev_obj=rev_str["obj"]
  53.             #print(rev_obj_num)
  54.             #print(vexe)
  55.             if rev_obj_num==4 and vexe==0: #识别出完整表达式(四元素)且vexe=0
  56.                 x=0
  57.                 for zidian in rev_obj:
  58.                     #print(type(zidian))
  59.                     vtype=zidian.get("type")
  60.                     vprob=zidian.get("prob")
  61.                     vx=zidian.get("x")
  62.                     vy=zidian.get("y")
  63.                     vw=zidian.get("w")
  64.                     vh=zidian.get("h")
  65.                     print(vx,vy,vw,vh,vtype,vprob)
  66.                     temp1[x][0]=vx
  67.                     if vtype is 'plus':
  68.                         temp1[x][1]='+'
  69.                         print(temp1[x][1])
  70.                     elif vtype is 'minus':
  71.                         temp1[x][1]='-'
  72.                         print(temp1[x][1])
  73.                     elif vtype is 'equal':
  74.                         temp1[x][1]='='
  75.                         print(temp1[x][1])
  76.                     else:
  77.                         temp1[x][1]=vtype.strip('n')
  78.                         print(temp1[x][1])
  79.                     x=x+1
  80.                 temp1.sort() #按照坐标排序
  81.                 temp2=temp1[0][1]+temp1[1][1]+temp1[2][1]+temp1[3][1] #组装算式
  82.                 temp2=temp2.strip('=')  #去除'='符号
  83.                 #print(temp2)
  84.                 #print(eval(temp2)) #运行计算式
  85.                 #eval(temp2)为执行表达式,并组装成结果字符串
  86.                 string_result= temp2+'='+str(eval(temp2))
  87.                 label1.setText(string_result) #在Basic上显示
  88.                 speaker.sing(489, 1/4) #提示音
  89.                 wait_ms(2)
  90.                 vexe=1 #每次识别+显示只进行一次,随后手动清零
  91.                 print('ok')
  92.     except:
  93.         print("except")
复制代码
【软件知识点简述】
这个小玩具里面有三个知识点需要关注学习。
1、识别的中间结果怎么保存?
我们建立了一个一个二维、四元素数组temp1 ,放置中间结果:
temp1 = [['' for i in range(2)] for j inrange(4)]
temp1[x][0] 放置x坐标,temp1[x][1] 放置识别出来的类型(数字或者运算符)
2、我们对这个二维数组进行排序,使用以下语句:temp1.sort() 。实际上是按照temp1[x][0]
(即x坐标)排序,这样就使得识别出来的数据按照从左往右的顺序排列好,和我们的手写习惯是一致的。
3、根据字符表达式计算运算结果, str(eval(temp2))这个语句就是完成此功能。
整个M5Stack Basic侧的代码,加上注释也不到100行。UI设计及主逻辑搭建是通过UIFLOW完成的,调试工作通过thonny ide这个轻量级调试工具完成。
【小结】
  • 简易手写计算器是一个有趣但简单的小玩具,由于手写数字比较简单,所以我训练了大概不到50组数据,就可以达到不错的识别效果。这也是V2的硬件支撑以及软件优化的结果吧,作为玩家,我感觉用户体验还不错。
  • 在B站有玩家留言,是否可以做得更复杂些(比如三角函数、对数运算),答案是:当然可以。
这是一个深度学习的应用,只要你的样本足够多、足够合理,识别出更加复杂的数学表达式当然没有问题,本文只是抛砖引玉,我连乘除功能都没有训练,请玩家一起进阶更加有趣的玩法吧。
  •   感谢笑笑师兄的指导以及M5Stack一众师兄的指导、鼓励,谢谢大家。

沧海抱拳。

代码和UIFLOW设计文件附后。
upload.zip (3.31 KB, 下载次数: 1)

6 8_a.jpg
发表于 2021-8-14 22:27 | 显示全部楼层
沧海笑1122老师写的文章条理清晰,逻辑严密值得推荐,一起学习一起进步。
 楼主| 发表于 2021-8-15 11:30 | 显示全部楼层
topdog 发表于 2021-8-14 22:27
沧海笑1122老师写的文章条理清晰,逻辑严密值得推荐,一起学习一起进步。

感谢师兄支持
发表于 2021-8-16 08:22 | 显示全部楼层
                                            
点赞,这个模块没有用过


                                                                           
                    
                                            
                        
                    
               

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-1 01:46 , Processed in 0.148046 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表