M5Train 视觉识别轨道小火车头-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 7928|回复: 4

M5Train 视觉识别轨道小火车头

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

M5Train 视觉识别轨道小火车头】

【故事】
   front_s.jpg
     玩具轨道电动小火车历史悠久,是孩子们常见喜爱的玩具。米兔小火车轨道兼容宜家的轨道,孩子们百玩不厌。但是,毕竟电动小火车头只有一个车速,小火车头拖动着车厢匀速前进,时间长了,难免枯燥。如何增进趣味呢?
    M5stack(明栈科技)出品了基于K210的视觉识别core-M5stickV。我们利用http://v-training.m5stack.com/这个友好的训练服务,训练自己的模型并且上传至服务器,将得到返回的训练模型。这样就为小火车装上了一双能识别交通标志的聪明眼睛。
  本项目一共训练了七个交通标志。我尝试将其用于米兔轨道小火车,用M5stickC作为执行元件,构成了一个基于视觉识别的,好玩的轨道小火车。
icon1.jpg

   我们先一起看看视频吧,在视频中,小火车头拖着一节车厢,从始发站出发,经过了限速、注意火车、解除限速、亮灯、鸣笛以及停车等七个动作。小火车头变身可以识别交通标志的AI小火车,根据交通标志完成了变速、闪灯、各种音效鸣笛以及起步停车等动作。为儿童玩具增加了更多的趣味。
【硬件】
编号
内容
型号或性能配置
备注
1
电动火车头
配置标称1200MAH1.2V可充电电池以及管理模块,一套单电机及齿轮传动系统
来自闲鱼,这是一个兼容宜家、米兔的可充电电动火车头
2
视觉识别装置
M5stickV/K210芯片,具有视觉识别、模型自主训练等功能
鸣笛及火车音效、亮灯等通过M5stickV完成
3
执行装置
M5stickC/esp32,接收来自m5stcikV的识别结果并且转换为相应动作,发送至电机驱动板
驱动电机的相关动作,有C完成
4
电机驱动板
L298N双路电机驱动板
本项目火车头仅有一个电机,因此仅用一路;
5
小火车头动力部分改装
锂电池充电控制板、5V/750MA充电器以及14500电池构成。
外置的锂电池充电装置,代替了原有小火车内部的1.2V电池以及充电管理模块,14500电池(850MAH/3.7V)具有良好的动力性能,而且可以与L298N模块匹配
6
配件
自保持开关、铜芯导线若干
vc.jpg


【软件】
编号
内容
出品及版本
备注
1
M5sitckV固件
M5出品/V1022-beta
2
M5StickC固件
M5出品/V1.4.3
固件基于UIFLOW
3
UIFLOW开发环境
M5出品/V1.4.3
4
Thonny IDE
Thonny.org /v3.2.6
优秀的micropython开发环境
5
Vscode+m5stack插件
Microsoft出品
优秀的通用代码开发环境




【制作过程】
  • 第一步:购买并且改装小火车头
  • 火车头1_s.jpg
  在闲鱼上购买了一只黑红相间的漂亮扎实的小火车头,当时是看中了它具有充电以及车灯功能。到货拆解后,发现其设计比较紧凑合理,做工精良,使用内三角螺丝紧固。小火车头的动力系统包括一节标称1200MAH1.2V可充电电池+单电机以及齿轮系统+电池充电管理模块。
  但是发现存在三个问题:一是动力不足,1200mah的容量几乎可以肯定是虚标;二是电池1.2V的输出电压无法与L298N电机驱动板匹配,也就是难以完成调速、停车、正反转等功能。三是前车灯非常黯淡,可以肯定串接了一个高阻。使用M5sitckC测试后,输出亮度难以满意。
  所以我们需要对小火车头进行改装。
(1)改装其动力系统,原有的1.2V电池以及充电管理模块忍痛去除,代之以850MAH/3.7V可充电电池14500,这颗电池的尺寸和原电池一致,所以很容易装配到原位置,我用热熔胶进行了较为严实的固定,将电池的充电系统外置,不再安装在火车内部,把腾出来的位置用于安装L298N电机驱动板。
(2)在车厢顶部开孔(M3)用于安装M5sitckV
(3)将内部空间里面的原有拨动开关拆除,将内部的塑料结构用电磨进行拆除,空间用于电机驱动板以及布置铜芯导线。
(4)增设一节车厢,用于承载M5sitckC。这样,把小火车的动力部分、电机驱动部分以及视觉识别元件(M5sitckV)放在火车头,而电源开关以及执行元件(M5sitckC)装在一节单独的车厢,两者通过磁性连接件以及排线连接。
old_s.jpg

  • 第二步:代码设计
  代码设计部分其实比较简单,我们一起来看看吧。
(1)M5sitckV部分
一是训练模型
  使用M5sitckV的训练程序,将七个交通标志各拍摄100张以上的照片(合计700+照片),发送至http://v-training.m5stack.com/,如果你的训练是符合要求的,计算结果将收敛,这样大约在半小时左右,你会收到一份带有下载链接的邮件。下载后,你将得到一份自主训练的模型库,一个boot.py的demo代码,不要小看这个demo,我们会很容易移植到主程序中。注意:训练时尽可能将模型放入取景框且尝试不同的光线条件。
二是设计代码
  这部分主要是将识别的结果发送至M5sitckC,所以uart的发送编程是主要内容,另外,由于M5sitckV带有喇叭,所以我们这个项目的火车音效就靠M5sitckV实现,前文说到的火车头车灯不亮,而M5sitckV本身就带有一个非常亮的全彩LED,这个亮灯的任务也交给M5sitckV吧。


[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 Maix import I2S, GPIO
from machine import I2C
from board import board_info
from pmu import axp192
pmu = axp192()
pmu.enablePMICSleepMode(True)
fm.register(board_info.SPK_SD, fm.fpioa.GPIO0)
spk_sd=GPIO(GPIO.GPIO0, GPIO.OUT)
spk_sd.value(1)
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)
from machine import UART
fm.register(board_info.CONNEXT_B,fm.fpioa.UART1_TX)
fm.register(board_info.CONNEXT_A,fm.fpioa.UART1_RX)
uart_A = UART(UART.UART1, 115200, 8, None, 1, timeout=1000, read_buf_len=4096)
fm.register(board_info.LED_W, fm.fpioa.GPIO3)
led_w = GPIO(GPIO.GPIO3, GPIO.OUT)
led_w.value(1)
passlable=''
global v_state
v_state='no'
lcd.init()
lcd.rotation(2)
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)
task = kpu.load("/sd/3f758421b4b1db32_mbnet10_quant.kmodel")
labels=["1","2","3","4","5","6","7"]
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
sensor.run(1)
lcd.clear()
def play_sound(filename):
        try:
                player = audio.Audio(path = filename)
                player.volume(30)
                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
while(True):
        img = sensor.snapshot()
        fmap = kpu.forward(task, img)
        plist=fmap[:]
        pmax=max(plist)
        max_index=plist.index(pmax)
        a = lcd.display(img)
        if pmax>0.99:
                lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
                passlable=str(labels[max_index])
                if passlable==v_state:
                        pass
                else:
                        v_state=passlable
                        uart_A.write(passlable)
                        if v_state=='1':
                                led_w.value(0)
                                time.sleep_ms(2000)
                                led_w.value(1)
                        elif v_state=='2':
                                play_sound("/sd/whistle3.wav")
                                time.sleep_ms(200)
                        elif v_state=='3':
                                play_sound("/sd/train.wav")
                                time.sleep_ms(200)
                        else:
                                pass
        else:
                pass
a = kpu.deinit(task)
uart_A.deinit()
[/mw_shl_code]

(1)M5sitckC部分
M5sitckC的代码比较简单,从uart接收到识别特征字以后,通过一系列判断语句,将动作行为发送至L298N即可。
(a)在设计M5sitckC的代码时,我用UIFLOW作为UI设计以及框架搭建,然后将生成的代码用thonny进行调试,玩家看到这里可能会问两个问题:一是为什么不是用uiflow继续调试呢?Uiflow毕竟有一定局限性,而thonny在调试esp32时可以得到非常全面的调试和错误信息;二是为什么调试M5sitckC时,不用vscode+m5插件这种方式呢?因为M5sitckC的屏幕很小,在vscode+m5插件调试时,得不到具体的出错信息。所以根据我的体会,在调试M5sitckC时,比较适合我的办法就是UIFLOW+thonny.
(b)在M5sitckC的屏幕上,我设计了三个参数,一是电池的电压(C的续航能力较弱,实时显示电池电压非常重要,否则调试中会走很多弯路),二是显示从v获取的识别特征码,三是现实C的动作行为(如stop/go/......)便于与V联调时,对识别情况的把握。
stop1s.jpg

[mw_shl_code=python,true]#2020-01-03
#C侧的火车控制程序v0.11
#
from m5stack import *
from m5ui import *
from uiflow import *
import machine
import time

#UI
setScreenColor(0x111111)
title0 = M5Title(title="Train0108", x=3 , fgcolor=0xFFFFFF, bgcolor=0x0000FF)

label0 = M5TextBox(31, 45, "Ready", lcd.FONT_Default,0xFFFFFF, rotate=0)  #显示火车状态
label1 = M5TextBox(33, 89, "Inbox", lcd.FONT_Default,0xFFFFFF, rotate=0) #显示接收到的指令情况
circle0 = M5Circle(17, 52, 3, 0xFFFFFF, 0xFFFFFF)
circle1 = M5Circle(17, 97, 3, 0xFFFFFF, 0xFFFFFF)
#lcd setup
axp.setLDO2Volt(2.7)
title0.setTitle(str(axp.getBatVoltage()))
#setup
#===uart
uart = None
uart = machine.UART(1, tx=32, rx=33)  
uart.init(115200, bits=8, parity=None, stop=1)

#===GPI0 SETUP
#pin1 = machine.Pin(5, mode=machine.Pin.OUT, pull=machine.Pin.PULL_UP)
pin0 = machine.Pin(0, mode=machine.Pin.OUT, pull=machine.Pin.PULL_UP)
PWM1 = machine.PWM(26, freq=10000, duty=0, timer=0)
PWM1.resume()
wait(1)

#===Train Action
def go(): #normal
  pin0.value(0)
  PWM1.duty(60)  #speed=60
  label0.setText('Go')
  label1.setText('no')

def light(): #light of train
  #light on m5stickV
  label0.setText('Light')
  label1.setText('1')
  pin0.value(0)
  PWM1.duty(50)  #speed=50

def train(): #Train comming
  # Train sound play on m5stickV
  label0.setText('Train')
  label1.setText('2')
  pin0.value(1)
  PWM1.duty(100)   #IN1=1   IN2=1  停车3S后正常速度,相当于等待火车通过
  time.sleep_ms(3000)
  pin0.value(0)
  PWM1.duty(50)   #3S为speed=50
  
  
def whistle(): #whistle
  # whistle play on m5stickV
  label0.setText('Whistle')
  label1.setText('3')
  pin0.value(0)
  PWM1.duty(50)  #speed=50

def limit(): #train limit 5KM/h
  pin0.value(0)
  PWM1.duty(45)   #speed=45
  label0.setText('Limit')
  label1.setText('4')

def nolimit(): #train no limit 5KM/h
  pin0.value(0)
  PWM1.duty(75)   #speed=75
  label0.setText('NoLmt')
  label1.setText('5')
  time.sleep_ms(2000)
  pin0.value(0)
  PWM1.duty(50)   #2S后降速为speed=50

def stop():#train stop
  pin0.value(1)
  PWM1.duty(100)   #IN1=1   IN2=1
  label0.setText('Stop')
  label1.setText('6')
  
def greenlight(): #train Go
  pin0.value(0)
  PWM1.duty(50)   # speed=50
  label0.setText('GLight')
  label1.setText('7')


#=====Loop
while True:
  title0.setTitle(str(axp.getBatVoltage()))
  if uart.any():
    bin_data = uart.readline(1)
    decode_bin_data=bin_data.decode()
    wait_ms(200)
    if decode_bin_data=='1': #如果识别到1开灯
      light()
      time.sleep_ms(200)
    elif decode_bin_data=='2':#如果识别到2===火车通过
      train()
      time.sleep_ms(200)
    elif decode_bin_data=='3':#如果识别到3===鸣笛
      whistle()
      time.sleep_ms(200)
    elif decode_bin_data=='4':#如果识别到4===限速
      limit()
      time.sleep_ms(200)
    elif decode_bin_data=='5':#如果识别到5===解除限速
      nolimit()
      time.sleep_ms(200)
    elif decode_bin_data=='6':#如果识别到6===停车
      stop()
      time.sleep_ms(200)
    elif decode_bin_data=='7':#如果识别到7===绿灯
      greenlight()
      time.sleep_ms(200)
    else: #其他数据正常前进即可
      go()  
  else:
    pass
[/mw_shl_code]



第三步:L298N电机驱动板调试
L298N电机驱动板的控制真值表:
l298n2.jpg
联调的接线很简单,见下图:

l298n_s.jpg

   在联调时,我用了一个小技巧,将一个测试用的电机,其输入线端焊接了两个杜邦线的母头,而L298N的输出输入的铜芯导线端部,都焊接了排针并且进行了热缩处理。这样非常方便地讲L298N与电池、电机以及M5sitckC连接起来。而在正式部署时,只需要将铜芯导线截断至合适长度即可。这样的小技巧可以大大缩短调试时间,并且增加调试的可靠性。L298N的调试目的就是测试各个控制行为以及根据电机实际情况得到PWM的参数(这个参数在装配小火车头电机后,还会进行一些修正)。
第四步:组装M5sitckV+M5sitckC以及小火车系统并且联调
  将M5sitckV+M5sitckC以及小火车系统安装好,其中M5sitckC用扎带固定在车厢上,M5sitckVM3螺丝以及M5提供的专用L型支架固定在火车头的车厢顶部。火车头的电机与调试后的L298N连接起来。
  联调的过程比较简单,注意把火车头翻过来,车轮朝上,这样就不会在调试中,面对不停动弹的火车头手忙脚乱。然后把七个标志一一由V识别,观察C上面的接收情况。
  注意:如果您是第一次调试M5sitckV,您需要增加一个步骤,就是M5sitckVPC的串口助手要先行进行调试,确保M5sitckV识别到的特征码能够准确地传送至uart,我因为在M5sitckV模拟小超市中已经进行过类似调试,积累了很好的经验,所以不需要此步骤。
charge1_s.jpg
第五步:组装轨道以及路测
  接下来就是比较有趣的部分了,也是亲子活动时间,和孩子一起组装一个环形轨道,然后把七个交通标志布置在你希望出现的地方,就可以开始路测了,路测时注意几个问题:
一是M5sitckC/V需要有足够充电,否则因电压不稳的重启会让你的路测不连续。
二是M5sitckV的摄像头角度需要在路测时不断调整。
三是电机的PWM参数需要在路测中不断进行微调,因为每个人小火车头的电机参数都会略有不同,轨道的弯度也需要进行一些调整,有一些过急的弯道可能会造成脱轨,需要进行一些修正。
allmap_s_1.jpg
   这个调试过程总体是很有趣的,看着小火车头拖动着M5sitckC的车厢,在环形轨道上做出种种识别动作,欢声笑语使得前面的辛苦工作,都显得那么物有所值。

【鸣谢】
  • 感谢m5stack.com以及arduino.cn社区,提供这样好的硬件以及交流平台。
  • 感谢社区多位师兄给我的帮助和鼓励,如“滚筒洗衣机”师兄(抱歉您的ID的确如此)对动力系统的指导,笑笑以及jimmy、小华师兄对我的指导和鼓励。
  • 感谢我的孩子能够欣赏我这个小小的项目,并且和我一起测试。
  这个小项目抛砖引玉,希望更多玩家做出更有趣的尝试。
  新春将至,春天的脚步更近了,祝福各位师兄新春大吉,诸事顺意。
  沧海抱拳。

upload_m5train00.zip (363.28 KB, 下载次数: 6) upload_m5train01.zip (512 KB, 下载次数: 6) upload_m5train02.zip (512 KB, 下载次数: 6) upload_m5train03.zip (512 KB, 下载次数: 6)

由于论坛附件size限制,请下载四个附件后,进行文件名修改,然后解压,我分享了code\model\wav。
原文件名更改为
upload_m5train00.zip upload_m5train.zip
upload_m5train01.zip upload_m5train.z01
upload_m5train02.zip upload_m5train.z02
upload_m5train03.zip upload_m5train.z03

last1_s.jpg

upload_m5train.zip

363.28 KB, 下载次数: 7

 楼主| 发表于 2020-1-19 10:22 | 显示全部楼层
【01-19】更正:
这个小火车头里面的电机驱动板,并非L298N,实际是MX1508。特此更正。
发表于 2021-4-9 19:26 | 显示全部楼层
沧海笑1122 发表于 2020-1-19 10:22
【01-19】更正:
这个小火车头里面的电机驱动板,并非L298N,实际是MX1508。特此更正。 ...

大佬,为什么现在那个训练平台不能用了呢,一直是上传失败
 楼主| 发表于 2021-4-10 10:00 | 显示全部楼层
13072600622 发表于 2021-4-9 19:26
大佬,为什么现在那个训练平台不能用了呢,一直是上传失败

嗯,请联系一下m5stack的客服,了解一下训练是否又新的变化。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-30 23:41 , Processed in 0.086108 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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