本帖最后由 沧海笑1122 于 2021-7-7 18:55 编辑
【教程】工业应用场景下的M5Stack UnitV2应用之不良品筛选
【项目故事】 这是一个严肃的话题,从流水线产品质量控制、到矿石分选,利用AI技术,将不良品或者不同种类的矿石进行分选,都是非常常见的工业AI应用场景。 这是一个原煤分选的案例,识别装置使用了视觉+X光等双源方式,来判别是精煤还是矸石,用识别后的结果(目标物的坐标)驱动储气罐+电磁阀,对需要分选的对象(精煤或者矸石)进行吹选,落在不同的仓内,从而达到分选目的。 本文尝试使用M5Stack出品的最新Linux AI智能摄像头UnitV2,搭建一个模拟工业应用场景下的不良品筛选功能。我们看看成品的运行状况。 ▶▶▶
我们看到,上一个工序形成的一个个零件,均匀布撒在传送带上。良品上面有一个螺丝,而“不良品”我们设定是漏装这颗螺丝的零件。经过AI摄像头后,会通过目标检测功能,识别出零件的类别(良品还是不良品)以及兴趣框的左上角坐标、兴趣框长宽等四个数据,通过uart串口,将json格式的识别结果发送到一台M5Stack Basic上,后者会进行简单的数据统计(良品、不良品以及总零件数),良品通过时,会以一个1/4拍的低音A来告知,不良品被发现时,会发出一个1/4拍的高音A来警示用户。对于不良品,M5StackBasic将驱动两只电磁阀,喷出压缩空气,将不良品吹至不良品仓,从而完成分选。 这个项目的推荐评级: 有趣度:四星★★★★ 难度:三星★★★ 同样是一个易于上手复现的玩具,却可以真实再现工业场景。 下面我们就一起动手试试吧。
【硬件准备】
在这里穿插一下背景知识,介绍一下基于Linux的新品M5Stack UnitV2。
我原先文章中的AI摄像头是基于K210的,这次UnitV2是M5Stack最新推出的一款高效率的AI识别模块, 采用Sigmstar SSD202D(集成双核Cortex-A7 1.2Ghz处理器)控制核心,集成128MB-DDR3内存,512MBNAND Flash, 1080P摄像头。内嵌Linux操作系统,集成软硬件资源与开发工具。你可以理解为一个由M5Stack高度定制的带摄像头的Linux小电脑,将识别结果用串口送出,格式是标准的json,便于玩家在PC、树莓派或者esp32单片机上将识别结果进一步解析、运用、展示及控制。 【软件准备】
| | | | 1 | | | 为了便于Thonny调试,我选择了离线版,在线版功能更强、版本更高 | 2 | | | | 3 | |
| | 4 | | | |
【主要原理流程图】 M5Stack Basic部分: 【制作过程】 1、 目标检测模型训练(新一代的v-training) (1) 批量采样 UnitV2本身带有1080P摄像头,支持完整的pytho3(不再是micropython),所以我用python写了一个小程序,实现了对样本的采集,每隔3秒拍摄一张照片,照片320*240,保存在UnitV2里,然后通过http方式访问、下载。本次采样我一共拍摄了190张样本。 (2) 模型训练 第一步:注册训练用户 第二步:上传样本,本次上传190张,为提升训练效果,样本是多多益善,尽可能采用实际应用中的环境、采光条件来取样。 第三步:建立标签,比如本文建立了两个样本标签,i_yes:良品,i_no:不良品。 ① 设定为不良品(缺螺丝);②设定为良品
第四步:标记。这个步骤比较枯燥,需要手工将图片中的目标标记出来,同时匹配相应的标签。对残缺不全的目标可以放弃。注意,这个过程中切勿刷新页面,否则前功尽弃。 第五步:训练、等待结果返回。如果你漏标了样本,系统也会提醒你完善。 (1) 成果应用 结果返回后,是一个压缩文件,比如我的模型是:v2model_3c6b26928a46a81e.tar。这个文件并不需要像k210那样拷贝到TF卡上,UnitV2提供了AP模式和Ethernet模式连接,我是用后者连接到UnitV2设备,访问域名unitv2.py或IP:10.254.239.1访问预览网页。切换功能至Object Recognition,点击add按键,在本地路径中上传模型即可。 此刻就可以看到,在目标检测中,增加了一个模型(就是刚才训练的模型),在UnitV2镜头下放上样品,就可以看到在右侧的结果框,出现识别结果(json格式)。此刻我们可以把UnitV2当成一个AI摄像头,将目标检测结果从串口送出,我们用PC、树莓派或者单片机进行后期的结果处理、展示和执行。本文我们使用esp32为核心的M5StackBasic来作为执行元件。 2、 传送带 为了这个工业场景,我在网购平台找到了这款木质结构的微型电机传送带,尺寸是27*6.6*8.6cm(长宽高),电机靠两节1.5V干电池驱动,可以控制正反转。我测试了一下转速,基本满足我的试验要求。很可惜,运输过程中,整个传送带收到了很严重的破损,商家很给力(必须点个赞),重新发货一台。由于是成品,所以可以直接使用。 3、 吹选系统(压缩空气装置+电磁阀+管路以及喷嘴) (1) 喷嘴制作
电磁阀系统,由压缩空气装置+电磁阀+管路以及喷嘴组成,喷嘴要求稳定、便于连接、口径合适。我找到了一些收音机天线(铜质镀锌),天线实质就是一节节优质的铜管,用电磨截断、打磨后,我得到了两根内径4mm的铜管,作为我们两只电磁阀的喷嘴。
(2) 压缩空气装置+电磁阀+管路以及喷嘴组装
压缩空气装置就是两只带有自锁功能的压力喷水壶,我把壶嘴做了改装,与硅胶软管连接,电磁阀是执行元件,一端接压力壶,一端接刚才制作的喷嘴。这样就形成了一套完整的压缩空气系统。
注:①是电磁阀驱动板;②是电磁阀(两只) (3) 电控部分以及吹选系统联调:
我在制作“高速水滴碰撞摄影装置”时,制作了一块电磁阀扩展板,扩流采用了ULN2803A 8通道达林顿阵列。本次使用M5StackBasic的第16、17引脚,通过ULN2803A扩流后,驱动2只12V电磁阀。ULN2803A和M5StackBasic的GND相连接。电磁阀系统单独调试,用3.3V点击ULN2803A的相应输入端,验证电磁阀动作可靠、压缩空气排出正常、整个系统无漏气。 ① 电磁阀引线;②ULN2803达林顿;③12V直流输入 4、 系统总装测试
我使用了一个俯拍支架,将UnitV2和M5StackBasic固定在俯拍支架上,准备了两个成品盒,一个放置良品(在传送带末端),另一个放置不良品(在传送带末端垂直于传送带方向)。样品一共准备了十只,乐高积木+连接件(模拟螺丝)构成良品,不良品就是不带连接件的积木块。 注:①传送带以及样品;②UnitV2;③M5StackBasic;④电磁阀驱动板;⑤电磁阀;⑥压力壶;⑦零件盒(待分选);⑧传送带开关;⑨有源hub以及俯拍支架底座。
使用UIFLOW设计了UI和基本流程后,主要的调试工作在Thonny IDE完成,这是一个轻量级的python和micropython调试软件。调试M5StackBasic非常方便,调试过程中,可以观察各个变量以及程序节点的状态。
【代码】
一、M5StackBasic侧代码 - # UNITV2 之传送带质量监测
- # 2021-07-04
- # 0620 使用Basic 的grove tx=21, rx=22 与V2建立uart通信
- # 0620 完成了识别、计数、LED闪动等功能联调
- # 0621 编制电磁阀吹零件功能,联调电磁阀+管路以及压力壶
- # 0704 在传送带垂直方向安装2只电磁阀,将不良品吹入不良品仓,调试完成
- # 沧海
- 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
- vprob = None
- vtype = None
- vx = None
- vy = None
- vw = None
- vh = None
- #vexe 执行控制字,确保目标进入执行框后,只执行一次(计数以及吹不良品)
- vexe=0
- #c_ok累计良品
- c_ok=0
- #c_ng累计次品
- c_ng=0
- #c_total累计总零件数量
- c_total=0
- #v_timer
- v_timer=0
- #vobj_x 目标中心的x值
- vobj_x =0
- #vobj_y 目标中心的y值
- vobj_y =0
- okorng= None
- #UI INIT
- title0 = M5Title(title="QC 202106", x=3, fgcolor=0xFFFFFF, bgcolor=0x0000FF)
- label0 = M5TextBox(13, 47, "OK=", lcd.FONT_DejaVu40, 0x30f905, rotate=0)
- label1 = M5TextBox(9, 94, "NG=", lcd.FONT_DejaVu40, 0xf70606, rotate=0)
- label2 = M5TextBox(13, 149, "TOTAL=", lcd.FONT_DejaVu40, 0xf3f4f6, rotate=0)
- i_ok = M5TextBox(204, 47, "0", lcd.FONT_DejaVu40, 0x04fe03, rotate=0)
- i_ng = M5TextBox(204, 99, "0", lcd.FONT_DejaVu40, 0xf10303, rotate=0)
- i_total = M5TextBox(204, 149, "0", lcd.FONT_DejaVu40, 0xFFFFFF, rotate=0)
- label3 = M5TextBox(52, 217, "Reset", lcd.FONT_Default, 0xf8db04, rotate=0)
- #Uart INIT
- uart1 = machine.UART(1, tx=21, rx=22)
- uart1.init(115200, bits=8, parity=None, stop=1)
- # 电磁阀1:Pin 16 电磁阀2:Pin 17
- # 执行函数,将驱动2只电磁阀吹动不良品进入不良品仓
- # 本程序中,电磁阀排列在传送带垂直方向。
- # 良品使用低音作为指示
- def f_exe1(okorng,vobj_y,vobj_x):
- if vobj_y>=150 and vobj_y<310 and okorng=="i_no":
- speaker.sing(889, 1/4) #不良品声音
- digitalWrite(16, 1)
- digitalWrite(17, 1)
- wait_ms(60)
- digitalWrite(16, 0)
- digitalWrite(17, 0)
- if okorng=="i_yes":
- speaker.sing(220, 1/4) #良品声音
- def buttonA_wasPressed():
- global c_ok,c_ng,c_total
- #计数以及变量清零
- i_ok.setText('0')
- i_ng.setText('0')
- i_total.setText('0')
- c_ok=0
- c_ng=0
- c_total=0
- pass
- btnA.wasPressed(buttonA_wasPressed)
- #目标检测函数,检测到良品(不良品),分别进行计数,并且驱动电磁阀吹动不良品
- def f_detection(vx,vy,vw,vh,vprob,vtype):
- #计算目标坐标
- global vexe,c_ok,c_ng,c_total
- vobj_x=(vx+vw/2)
- vobj_y=(vy+vh/2)
- print(vobj_x)
- print(vobj_y)
- print(vexe)
-
- #目标进入执行框(如x=260~400),则执行一次(计数以及吹不良品)
- if vprob>0.5 and vobj_x>260 and vobj_x<400:
- print("in exe area")
- if vtype=="i_yes" and vexe==0:
- c_ok=c_ok+1
- i_ok.setText(str(c_ok))
- f_exe1(vtype,vobj_x,vobj_y) #绿灯
- c_total=c_total+1
- i_total.setText(str(c_total))
- vexe=1 #确保在整个执行框只动作一次
- else:
- if vtype=="i_no" and vexe==0:
- c_ng=c_ng+1
- i_ng.setText(str(c_ng))
- c_total=c_total+1
- i_total.setText(str(c_total))
- f_exe1(vtype,vobj_x,vobj_y) #电磁阀吹动50ms
- vexe=1 #确保在整个执行框只动作一次
- else:
- print("out exe area")
- vexe=0
-
-
- #Main loop
- while True:
- try:
- if uart1.any():
- rev_str = json.loads((uart1.read()).decode())
- rev_obj=rev_str["obj"]
- #print(rev_obj)
- 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")
- f_detection(vx,vy,vw,vh,vprob,vtype)
- #print(vx,vy,vw,vh,vtype,vprob)
- wait_ms(2)
- #f_detection(vx,vy,vw,vh,vporb,vtype)
- except:
- print("except")
复制代码
二、UnitV2侧连续拍摄采样代码 - import cv2
- import time
- camera = cv2.VideoCapture(0)
- camera.set(3, 320) # width=320
- camera.set(4, 240) # height=320
- path = "train/0613/b"
- ext=".jpg"
- cnt=0
- def control_white_led(value):
- open('/sys/class/gpio/export', 'w').write('0') # Export the GPIO0
- open('/sys/class/gpio/gpio0/direction', 'w').write('out') # Set the direction of the GPIO
- open('/sys/class/gpio/gpio0/value', 'w').write(str(value)) # Set the calute, '1' or '0'
- try:
- while True:
- ret, frame = camera.read()
- if ret:
- cnt+=1
- fname=path+str(cnt)+ext
- #led blink
- control_white_led(0)
- print(fname+" is saved")
- cv2.imwrite(fname,frame)
- time.sleep(0.2)
- control_white_led(1)
- #time.sleep(2.8)
- else:
- print('OOps, we get some trouble!')
- time.sleep(2.8)
- except KeyboardInterrupt:
- pass
复制代码【小结】 UnitV2升级为Linux 系统的AI摄像头后,实际上是一个带有1080P摄像头的完整Linux计算机系统,只是对系统高度定制后,给用户开放了更加便捷、友好的训练、使用界面,尤其是通过grove接口,将识别出来的结果,以json格式传送给上位机进行二次开发利用,用户可以更加关注训练和应用,而无须关注UnitV2的Linux 系统,最好不要把它定位成一个小电脑(虽然它就是一个小电脑,你可以通过SSH进入系统里),而是当成一个友好的AI摄像头,我理解这样是正确的使用方式。
UnitV2的出厂Linux镜像中集成了JupyterNotebook开发调试工具, Jupyter Notebook是一款网页形式的开发工具,用户可以直接在网页上编写代码和运行代码。这次我写的连续拍摄取样工具,就是在JupyterNotebook上编写、运行的,非常便捷。这也是M5Stack提供给用户的一个方便的调试工具,用好这一工具,往往事半功倍。 本文尝试用一个完整的工业场景,呈现UnitV2在目标检测方面的性能提升,在K210使用中,同一画面多目标往往会出现漏识别、误识别的现象。在UnitV2得到了大幅提升,多目标识别得到了很大改善。会给玩家更多创意实现的可能。比如下图就是我上次写的井字棋,当时使用了遮罩来提升识别率,本次用UnitV2训练后,同一画面多目标的识别是比较顺畅的。 由于集成度高,发热问题也随之而来,所以UnitV2集成了一个内置风扇,来尽可能控制装置温升,在实际运行过程中,也感受到温度的确比较高。看来在兼顾性能、体积以及温度控制等方面还需要做进一步的努力。 今日小暑,万物勃发。 感谢M5Stack开发的新品给玩家以实现创意的可能。感谢笑笑、社区各位师兄的指导帮助。 感谢arduino.cn提供交流平台。
沧海合十。
附上程序附件供学习交流。
|