本帖最后由 沧海笑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的训练、使用方式,抛砖引玉,吸引更多玩家以及更好的玩法。 有趣度:★★★★ 难度:★★ 这是我一如既往的风格:好玩不难。话不多说,我们开始制作吧。
【软硬件准备】 软件: 硬件: 再就是一支笔、一张白纸、一个俯拍支架,再就是您的手(手写嘛)。 【软件原理】 l M5Stack UnitV2侧:使用内置应用的目标检测功能,将手写的数字以及运算符(如+、-、=)进行识别,并且以json格式通过uart发送至上位机处理。 l M5Stack Basic侧: 这是M5Stack Basic侧的UI以及主循环设计。 【制作步骤】 Step1:训练手写数字及运算符模型 (1) 准备若干纸卡片,用墨水笔手写数字,尽可能大小一致、字体保持一致。我准备了约30张卡片,每张有2个算式。为了简化期间,只训练了+、-、=三种运算符以及若干数字。 (2) 使用自己编写的连拍程序,用UnitV2连续拍摄样本,做好取样准备。
Step2:上传模型后,打开目标检测功能,测试识别以及json串的传送情况
将UnitV2固定在俯拍支架上,光源充足的情况下,手写算式,测试目标检测的效果。效果如下: 注:1是刚才训练好并且上传的模型,UnitV2就是靠这个模型通过目标检测来识别你的手写录入。2是识别并且标注兴趣框的情况,3是识别后的json字符,通过uart发送至上位机进行展示。 Step3:分步调试,用usb-ttl模块加上串口调试器,与M5Stack Basic通信,将收到的json串进行解析和展示。
将UnitV2识别出的Json字符串,复制在串口调试器里,就可以脱离UnitV2进行分步调试。好处是可以简化调试步骤,减少不定因素,分别对M5StackBasic以及UnitV2进行调试。 注:1、减号识别并且替换成“-”;2、等号识别并且替换成“=”,3、4分别是数字的识别。
Step4:整体联调:将UnitV2(grove)与M5Stack Basic(pin21\pin22\gnd)连接,实现整体联调,在手写一个算式后,V2识别出来并且发送给M5Stack Basic,后者解析、组装算式、计算结果,并且将带有运算结果的算式展示在屏幕上,同时发出“滴”声,提醒玩家识别、计算过程结束。
如果你需要再算一组算式,只需要按“reset”键,即可清屏,为下一次识别计算做准备。
【代码】
M5Stack Basic侧: - # UNITV2 之手写计算器
- # 2021-08-06 测试uart1接受V2传送来的识别串,并且生成算式、计算结果并且在屏幕展示
- # 2021-08-08 v2送来的Num=4的json串已经可以解析,且放入一个四元素二维数组。
- # 2021-08-11 一是按钮reset。二是识别后,显示计算式以及答案,并且确保只要识别到就仅处理一次。三是识别后带语音提示
- # 沧海
- from m5stack import *
- from m5ui import *
- from uiflow import *
- from easyIO import *
- import json
- setScreenColor(0x222222)
- print("Begin...")
- # 变量init
- rev_str = None
- rev_obj = None
- rev_obj_num = None
- vprob = None
- vtype = None
- vx = None
- vy = None
- vw = None
- vh = None
- string_result= None
- #生成一个2维、四元素数组temp1 ,放置中间结果
- temp1 = [['' for i in range(2)] for j in range(4)]
- #temp2 是表达式以及答案
- temp2 =None
- #vexe 执行控制字,确保目标进入执行框后,只执行一次(计数以及吹不良品)
- vexe=0
- #UI INIT
- Calculator = M5Title(title="Calculator 2021-08", x=3, fgcolor=0xFFFFFF, bgcolor=0x0034ff)
- label1 = M5TextBox(52, 71, "Text", lcd.FONT_DejaVu56, 0xff8d08, rotate=0)
- rectangle0 = M5Rect(30, 187, 80, 40, 0x0efdf9, 0xFFFFFF)
- label2 = M5TextBox(37, 194, "Reset", lcd.FONT_Comic, 0x100000, rotate=0)
- #Uart INIT
- uart1 = machine.UART(1, tx=22, rx=21)
- uart1.init(115200, bits=8, parity=None, stop=1)
- def buttonA_wasPressed():
- global vexe
- label1.setText('') #清屏
- vexe=0 #复位执行控制字
- pass
- btnA.wasPressed(buttonA_wasPressed)
-
- #Main loop
- while True:
- try:
- if uart1.any():
- wait_ms(100)
- rev_str = json.loads((uart1.read()).decode())
- rev_obj_num=rev_str["num"]
- rev_obj=rev_str["obj"]
- #print(rev_obj_num)
- #print(vexe)
- if rev_obj_num==4 and vexe==0: #识别出完整表达式(四元素)且vexe=0
- x=0
- for zidian in rev_obj:
- #print(type(zidian))
- vtype=zidian.get("type")
- vprob=zidian.get("prob")
- vx=zidian.get("x")
- vy=zidian.get("y")
- vw=zidian.get("w")
- vh=zidian.get("h")
- print(vx,vy,vw,vh,vtype,vprob)
- temp1[x][0]=vx
- if vtype is 'plus':
- temp1[x][1]='+'
- print(temp1[x][1])
- elif vtype is 'minus':
- temp1[x][1]='-'
- print(temp1[x][1])
- elif vtype is 'equal':
- temp1[x][1]='='
- print(temp1[x][1])
- else:
- temp1[x][1]=vtype.strip('n')
- print(temp1[x][1])
- x=x+1
- temp1.sort() #按照坐标排序
- temp2=temp1[0][1]+temp1[1][1]+temp1[2][1]+temp1[3][1] #组装算式
- temp2=temp2.strip('=') #去除'='符号
- #print(temp2)
- #print(eval(temp2)) #运行计算式
- #eval(temp2)为执行表达式,并组装成结果字符串
- string_result= temp2+'='+str(eval(temp2))
- label1.setText(string_result) #在Basic上显示
- speaker.sing(489, 1/4) #提示音
- wait_ms(2)
- vexe=1 #每次识别+显示只进行一次,随后手动清零
- print('ok')
- except:
- 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设计文件附后。
|