【教程】M5StickV深度学习之微信跳一跳-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 13884|回复: 22

【教程】M5StickV深度学习之微信跳一跳

[复制链接]
发表于 2020-2-29 21:18 | 显示全部楼层 |阅读模式
本帖最后由 沧海笑1122 于 2020-3-1 21:43 编辑

M5StickV深度学习之微信跳一跳
【故事】
    M5StickV以及后续的Unit-V都是M5Stack.com(明栈科技)基于勘智K210的视觉传感器模块,K210可以在超低功耗下进行高速卷积神经网络计算。使用场景如基于卷积神经网络的目标检测和图像分类任务,人脸检测和人脸识别,多分类物体检测与识别等。
  我们接触这款好玩又性价比极高的模块后,陆续尝试了模拟小超市、视觉识别轨道小火车等。这些玩法都是基于图像分类的任务,也就是通过机器学习,对摄像头视野中出现的图像进行分类,比如小火车行驶中看到的交通标志,识别属于什么类别,并且给出识别概率。玩家根据M5StickV(Unit-V)识别到的图像类别进行逻辑判断,从而驱动执行元件做出相应动作,比如小火车看到鸣笛标志(如识别概率>0.98)会鸣笛,识别到开灯标志就会打开led等等。
  这样的玩法也被玩家善意戏称为“考勤机玩法”,随着我们对这款好玩的视觉传感器不断了解,这样简单的玩法已经不能满足我们的好奇心。
我们想知道,说好的目标检测玩法可以试试吗?
  请教M5StickV(Unit-V)的设计大佬笑笑(韩笑)后,答案是,当然可以!
   于是我们选择了微信跳一跳这款经典的小游戏,作为一个目标检测试验的载体。
微信图片_20200229001211.jpg
    下面跟随我们,一起从简单的概念、到软硬件准备,一起经历样本采集和训练,然后挑战一下深度学习之下的微信跳一跳有什么激动人心的地方吧。
    首先按照惯例,我们通过一段视频来感受一下M5StickV深度学习之微信跳一跳。
【几个基本概念】
    在开始实践之前,我们要一起学习两个基本的小概念,也希望玩家能认真地阅读一下这些背景资料,从而磨刀不误砍柴工。
我们设定您已经指导微信跳一跳是怎样一个小游戏,可以看看这个帖子,在此不赘述。

1、目标检测
   这个概念我搬运了《目标检测|YOLO原理与实现》(作者:小小将)的一段话:
   目标检测是一件比较实际的且具有挑战性的计算机视觉任务,可以看成图像分类与定位的结合,给定一张图片,目标检测系统要能够识别出图片的目标并给出其位置,由于图片中目标数是不定的,且要给出目标的精确位置,目标检测相比分类任务更复杂。
目标检测的一个实际应用场景就是无人驾驶,如果能够在无人车上装载一个有效的目标检测系统,那么无人车将和人一样有了眼睛,可以快速地检测出前面的行人与车辆,从而作出实时决策。
    好吧,我们把这段话套用到“微信跳一跳”的场景下,即:我们通过M5StickV对“跳一跳”画面中的人偶以及跳台进行分类(识别出哪个是人偶,哪个是跳台),然后测定人偶和即将前往的跳台之间的具体位置(获取中心点坐标)。由于视野里面的跳台数量是不定的(有已经跳过的,有下一个目标跳台),需要我们获取目标的精确位置,显然,目标检测比单一的分类任务更复杂。
2、yolo3
  知乎作者小小将解释,Yolo算法,其全称是You Only Look Once: Unified, Real-Time Object Detection,算法名字取得非常好,基本上把Yolo算法的特点概括全了:
    You Only Look Once说的是只需要一次CNN运算,Unified指的是这是一个统一的框架,提供end-to-end的预测,而Real-Time体现是Yolo算法速度快。
     M5StickV(Unit-V)设计大佬笑笑的说法更接地气:K210主要就是提供了一个简单的方法让不懂AI的用户用起来,里面跑的是谷歌的MobilenetV1这个神经网络结构,加上YoloV3的检测结构。yolo核心就是you only look once,它是通过一张图片直接得到目标的位置和class。一次操作最多可以可以预测几百个目标,检测时间不会随着目标的数量增长。
   了解了这两个概念,作为玩家的我们就知道,M5StickVUnit-V)可以为我们提供目标检测服务,让我们在微信跳一跳中,识别到人偶和跳台并且获取它们的准确位置。
【硬件】
名称
备注
1
M5StickVUnit-V
视觉传感器模块,M5Stack明栈科技出品
2
触屏继电器模块
方案一:模拟人的手指对手机屏幕进行触碰,是跳一跳的执行元件
3
M5StickC+8 SERVOS舵机扩展板+9g舵机
方案二:接收M5StickV的检测信息,驱动舵机执行模拟手指跳一跳
4
摄像头支架以及杜邦线若干
  注:我们用两个硬件方案实现了跳一跳功能,主要差别是执行环节,在视觉识别、目标检测方面没有差别。第一种方案更可靠,执行环节更少。而第二种环节非要找出优点,也有,那就是舵机每一次触屏的点都不一样,更容易帮助你获取更高的成绩,但咱们这个试验并非为了获取高分,所以这个优点,不提也罢。更规范的说法是,方案二尝试了M5StickC+8 SERVOS舵机扩展板+9g舵机的使用,拓展了你的知识点,这样说算不算优点呢。哈哈。
   方案一的连线只有一根grove电缆,接插在v和继电器触屏模块两端。
   方案二的连线也是grove,在c与+8 SERVOS舵机扩展板+9g舵机除了3pin电缆外,也并无连线,这些都是得益于M5Stack出品的便捷配件。
20200229205153.jpg

【软件】
名称
备注
1
M5StickVUnit-V
Maixpy ide 0.24(sipeed)
2
labelIMG
深度学习标记软件
2
触屏继电器模块
方案一:Maixpy ide 0.24(sipeed)
3
M5StickC+8 SERVOS舵机扩展板+9g舵机
方案二:arduino ide 1.8.11+m5stickc的基本库以及IIC舵机库

【关于样本采集以及训练】
    流程图
vtraining.PNG
  • 第一步:拍摄样本>100张,建议拍摄最好和你实际使用跳一跳的角度环境一致,(手机、光线条件、拍摄角度等)尽量接近。
根据笑笑的建议,我修改了一个简单的自动拍摄程序,每隔3s拍摄一张,保存在/train目录下,需要在M5StickV_Firmware_1022_beta.kfpkg固件下运行。后附。
  • 第二步:进行目标标记。
labelIMG1.PNG labelIMG2.PNG labelIMG3b.jpg labelIMG5.PNG labelIMG4.PNG labelIMG6.PNG labelIMG7.PNG
  • 第三步:打包后,上传http://v-training.m5stack.com/
  • 第四步:在服务器训练后,接收反馈回来的模型、固件以及boot.py文件

【代码】
一、自动拍摄程序
[mw_shl_code=python,true]import audio
import gc
import image
import lcd
import sensor
import sys
import time
import uos
import os
#import KPU as kpu
from fpioa_manager import *
from machine import I2C
from Maix import I2S, GPIO

#
# initialize
#
lcd.init()
lcd.rotation(2)

fm.register(board_info.SPK_SD, fm.fpioa.GPIO0)
spk_sd=GPIO(GPIO.GPIO0, GPIO.OUT)
spk_sd.value(1) #Enable the SPK output

fm.register(board_info.SPK_DIN,fm.fpioa.I2S0_OUT_D1)
fm.register(board_info.SPK_BCLK,fm.fpioa.I2S0_SCLK)
fm.register(board_info.SPK_LRCLK,fm.fpioa.I2S0_WS)

wav_dev = I2S(I2S.DEVICE_0)

#fm.register(board_info.BUTTON_A, fm.fpioa.GPIO1)
#but_a=GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

#fm.register(board_info.BUTTON_B, fm.fpioa.GPIO2)
#but_b = GPIO(GPIO.GPIO2, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

currentImage=0

def play_sound(filename):
    try:
        player = audio.Audio(path = filename)
        player.volume(20)
        wav_info = player.play_process(wav_dev)
        wav_dev.channel_config(wav_dev.CHANNEL_1, I2S.TRANSMITTER,resolution = I2S.RESOLUTION_16_BIT, align_mode = I2S.STANDARD_MODE)
        wav_dev.set_sample_rate(wav_info[1])
        spk_sd.value(1)
        while True:
            ret = player.play()
            if ret == None:
                break
            elif ret==0:
                break
        player.finish()
        spk_sd.value(0)
    except:
        pass

def initialize_camera():
    err_counter = 0
    while 1:
        try:
            sensor.reset() #Reset sensor may failed, let's try some times
            break
        except:
            err_counter = err_counter + 1
            if err_counter == 20:
                lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Sensor Init Failed", lcd.WHITE, lcd.RED)
            time.sleep(0.1)
            continue

    sensor.set_pixformat(sensor.RGB565)
    sensor.set_framesize(sensor.QVGA) #QVGA=320x240
    sensor.run(1)

try:
    img = image.Image("/sd/startup.jpg")
    lcd.display(img)
except:
    lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED)

time.sleep(2)

initialize_camera()

#currentDirectory = 1

if "sd" not in os.listdir("/"):
    lcd.draw_string(lcd.width()//2-96,lcd.height()//2-4, "Error: Cannot read SD Card", lcd.WHITE, lcd.RED)

try:
    os.mkdir("/sd/train")
except Exception as e:
    pass

isButtonPressedA = 0


try:
    while(True):
        img = sensor.snapshot()
        disp_img=img.copy()
        disp_img.draw_rectangle(0,60,320,1,color=(0,144,255),thickness=10)
        #disp_img.draw_string(50,55,"Train:%03d/35   Class:%02d/10"%(currentImage,currentDirectory),color=(255,255,255),scale=1)
        lcd.display(disp_img)

        #if but_a.value() == 0 and isButtonPressedA == 0:
        img.save("/sd/train/" + str(currentImage) + ".jpg", quality=95)
        play_sound("/sd/kacha.wav")
        time.sleep(3)  #每隔三秒拍摄一张
        currentImage = currentImage + 1
        #    isButtonPressedA = 1

        #if but_a.value() == 1:
        #    isButtonPressedA = 0


except KeyboardInterrupt:
    pass[/mw_shl_code]

二、m5staickv端程序
[mw_shl_code=python,true]import image
import lcd
import sensor
import sys
import time
import KPU as kpu
from fpioa_manager import *
import math
import KPU as kpu
from Maix import GPIO
import utime

#使用grove的g35作为继电器执行pin,注册pin以及初始化
fm.register(35,fm.fpioa.GPIOHS0)
relay = GPIO(GPIO.GPIOHS0,GPIO.OUT)

relay.value(0)
#lcd初始化
lcd.init()
lcd.rotation(2)
#载入已训练模型
task=kpu.load("/sd/yolov3_mbnetv1_0.5_jumpAjump_100epoch_voc_v3.kmodel")

anchor = (0.33340788 * 16, 0.70065861 * 16, 0.18124964 * 16, 0.38986752 * 16, 0.08497349 * 16, 0.1527057 * 16)

a = kpu.init_yolo2(task, 0.05, 0.05, 3, anchor)
#注:第一个是兴趣框的精确度的阈值
#第二个参数,是目标的精确度的阈值,现在是0.05,调高了就会更可靠,但是可能识别率低
#调低了,容易识别到,但是也同时容易误识别。
print("Load Done.")

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((320, 224))
sensor.run(1)

lcd.clear()

print("Init Done.")

counter = 1 #计数器
code_stake = [] #目标检测集合
minh = 1000 #目标跳台坐标
minh_y = 0
toy_x = 0 #人偶坐标
toy_y = 0
pressed_time = 0.0 #模拟手指的触屏时间

distance_factor = 0.25  #跳跃距离系数,此系数经过与实际手机屏幕校正后,据实际情况使用

while(True):
    img = sensor.snapshot()
    code = kpu.run_yolo2(task, img)
        #print("code....",code)
    if code:
        if counter < 5: #累计5次识别,将识别结果累加到code_stake[]当中
            code_stake = code_stake + code
            counter = counter  + 1
        else:
            counter = 0
            minh = 1000
            minh_y = 0
            toy_x = 0
            toy_y = 0
                        #注:i.rect()[0]   i.rect()[1]    i.rect()[2]    i.rect()[3]   分别是兴趣框的x,y,w,h
            for i in code_stake:
                if i.classid() == 0:  #识别到跳台,可能镜头里有多个跳台,我们需跳跃的是离摄像头最远的跳台
                    if minh > i.rect()[0] + i.rect()[2]//2:
                        minh = i.rect()[0] + i.rect()[2]//2  #计算跳台兴趣框中心点横坐标
                        minh_y = i.rect()[1] + i.rect()[3]//2#计算跳台兴趣框中心点纵坐标
                else: #识别到人偶
                    toy_x = i.rect()[0] + i.rect()[2]//2 #计算人偶兴趣框中心点横坐标
                    toy_y = i.rect()[1] + i.rect()[3]//2 #计算人偶兴趣框中心点纵坐标
            pressed_time = math.sqrt((minh - toy_x)**2 + (minh_y-toy_y)**2) / 100 * distance_factor #02-25更正,(minh_y-toy_y)
                        #pressed_time =人偶中心点至跳台中心点距离*跳跃距离系数
        img.draw_arrow(toy_x, toy_y, minh, minh_y)  #画人偶至跳台中心点连线,表明跳跃路径
        img.draw_string((toy_x+minh)//2, (minh_y+toy_y)//2, "%.03f" % (pressed_time), lcd.BLACK) #显示跳跃参数

        for i in code:
            if i.classid() == 0: #涉及跳台为红色标记
                c = lcd.RED
            else:
                c = lcd.GREEN #涉及人偶为绿色标记
            img.draw_circle(i.rect()[0] + i.rect()[2]//2, i.rect()[1] + i.rect()[3]//2, 3)#目标(跳台或人偶)中心点标记
            img.draw_rectangle(i.rect()[0], i.rect()[1], i.rect()[2], i.rect()[3], c) #标注目标(跳台或人偶)兴趣框
            lcd.display(img)   

        if counter == 0: #5次识别完毕
            relay.value(1) #继电器触发,模拟手指触屏动作
            utime.sleep_ms(int(pressed_time * 1000)) #经过pressed_time延时
            relay.value(0) #继电器接点返回,模拟抬起手指
            code_stake = [] #识别集清零
            utime.sleep_ms(2000)


    else:   
        lcd.display(img)
a = kpu.deinit(task)[/mw_shl_code]

  方案二:m5stickcv侧程序,打包附件供玩家下载。
【小结】
   深度学习一定要很高的门槛、很复杂的设备吗?M5stickv(core k210)给我们这些有兴趣但技术水平较低的玩家一个实现兴趣的路径和机会。V-training.m5stack.com把高性能服务器、大量复杂的技术细节留到了服务器端,做了默默的幕后英雄,给玩家提供一个友好的接口。如果你有一个好玩的基于目标检测的创意,采样、标记、上传之后,V-training.m5stack.com提供训练服务(这个服务目前是免费的,但我们不应忘记其实实在在存在的付出和成本)。就可能为你的兴趣和一点点梦想插上一对翅膀。
   梦想并无高低,每个玩家都献出一捧薪柴,篝火更加明亮、温暖。
【参考及鸣谢】
  •   感谢M5Stack.com提供这样有趣的硬件平台,感谢arduino.cn提供软件交流平台。
  •   我们使用了maixpy IDE, 感谢sipeed.com的工作。

   虽然我是本文的编写者,但是从构思到代码完全都是笑笑的辛勤工作,【故事】中的照片也是笑笑提供的,拍得漂亮。感谢笑笑(韩笑)大佬以及泽畔等师兄辛勤的工作。
  下面的一些文章让我对机器学习、深度学习以及目标检测和yolo3有了初步的认识,一并致谢。
1https://www.zhihu.com/question/57770020/answer/249708509
机器学习是一种实现人工智能的方法,深度学习是一种实现机器学习的技术。我们就用最简单的方法——同心圆,可视化地展现出它们三者的关系。
2https://zhuanlan.zhihu.com/p/69278495
《目标检测——YOLO V3简介及代码注释(附github代码——已跑通)》KevinCK(百度 计算机视觉工程师)
3https://zhuanlan.zhihu.com/p/32525231
《目标检测|YOLO原理与实现(小小将)
4https://zhuanlan.zhihu.com/p/90834296《图片标注软件labelImg使用指南(墨明棋妙)》
5、《Win10下安装LabelImg以及使用(绝对是全网最简单的教程)(墨明棋妙)https://zhuanlan.zhihu.com/p/90832346
  但愿2020先苦后甜。春天毕竟到了,惊蛰就在眼前。盼望春雷响起。
沧海合十。

附件:因size限制,分成了三个卷,包括模型、代码
upload_0229.part1.rar (1 MB, 下载次数: 26) upload_0229.part2.rar (1 MB, 下载次数: 26) upload_0229.part3.rar (365.2 KB, 下载次数: 23)

发表于 2020-2-29 21:43 | 显示全部楼层
佛山人民 发来贺电
 楼主| 发表于 2020-2-29 22:55 | 显示全部楼层
genvex 发表于 2020-2-29 21:43
佛山人民 发来贺电

哈哈,感谢师兄支持。
发表于 2020-3-1 00:20 | 显示全部楼层
请教 v-trian.config文件 是怎么生成的,标记文件好像没有自动生成这个文件吧
 楼主| 发表于 2020-3-1 00:30 | 显示全部楼层
genvex 发表于 2020-3-1 00:20
请教 v-trian.config文件 是怎么生成的,标记文件好像没有自动生成这个文件吧

就按照我图片里的格式,手工写一个json格式的文本文件即可。
发表于 2020-3-1 00:46 | 显示全部楼层
先顶起,然后再看能否做一遍
发表于 2020-3-1 01:11 | 显示全部楼层
我直接用你训练好的KMODEL模型行不行得通?
 楼主| 发表于 2020-3-1 09:16 | 显示全部楼层
laai 发表于 2020-3-1 01:11
我直接用你训练好的KMODEL模型行不行得通?

可以啊,所以我上传了模型,我用的是一块平板训练的,可能受屏幕的影响,会略有不同,但可以试试,笑笑用的也是这个模型。
发表于 2020-3-1 12:06 来自手机 | 显示全部楼层
沧海笑1122 发表于 2020-3-1 00:30
就按照我图片里的格式,手工写一个json格式的文本文件即可。

那如果是多种类的训练,怎么写json呀
 楼主| 发表于 2020-3-1 14:18 | 显示全部楼层
genvex 发表于 2020-3-1 12:06
那如果是多种类的训练,怎么写json呀

一共有几个类,classes就写几
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-30 23:59 , Processed in 0.463846 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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