查看: 3663|回复: 6

【教程】M5StickV(Unit-V)深度学习之追踪小球的Giraffe长颈鹿小车

[复制链接]

该用户从未签到

发表于 2020-2-29 23:04 | 显示全部楼层 |阅读模式
本帖最后由 沧海笑1122 于 2020-3-1 14:19 编辑

M5StickV(unit)深度学习之追踪小球的
Giraffe长颈鹿小车

【故事】
   在写完M5StickV(unit)深度学习之微信跳一跳之后,再讲这个故事就比较简单了。
   这也是一个深度学习之目标检测的小玩具。利用M5StickV(unit)的深度学习,训练了一个小球目标,使用M5StackRoverC麦轮小车作为载机。在视觉传感器识别到目标后,返回目标的准确坐标(x,y),然后驱动小车追踪。那天搭建好小车后,Jimmy说好像一个长颈鹿,所以我就起了个外号M5Giraffe。这样的小车搭建主要是为了照顾视角更大范围捕获小球目标。
roverc1.jpg
   目标检测、yolo3的背景资料详见《M5StickV(unit)深度学习之微信跳一跳》。下面我们就可以直接上干货了。
还是老规矩,一段视频来感受一下

【硬件】
名称
备注
1
M5StickVUnit-V
视觉传感器模块,M5Stack明栈科技出品
2
RoverC麦轮小车
M5Stack
3
摄像头支架以及杜邦线若干
乐高积木

【软件】

名称
备注
1
M5StickVUnit-V
Maixpy ide 0.24(sipeed)
2
labelIMG
深度学习标记软件
3
M5StickC
Arduino ide 1.8.11
M5的基础库
关于RoverC麦轮小车的控制函数
4
arduionjson
V6.0 arduionjson.org

【代码】
M5StickVUnit-V)端
[mw_shl_code=python,true]#=========使用机器学习追踪小球目标的小车
#=========2020-02-26;
#=========模型及固件:aa5837eee6273218_vtrainer.kfpkg

import sensor, image, time
from pid import PID
from utime import sleep_us
from machine import UART
from Maix import GPIO
from fpioa_manager import *
#import image
#import lcd
import sensor
import KPU as kpu
from pmu import axp192

#from modules import ws2812

#fm.register(8) #IO_8对应RGB,亮灯用于显示搜索到目标
#class_ws2812 = ws2812(8,130) #IO_8对应RGB

#===PMU初始化
#pmu = axp192()
#pmu.enablePMICSleepMode(True)
#===========uart init
fm.register(34,fm.fpioa.UART1_TX)
fm.register(35,fm.fpioa.UART1_RX)
uart_out = UART(UART.UART1, 115200, 8, None, 1, timeout=1000, read_buf_len=4096)
#LCD初始化
#lcd.init()
#lcd.rotation(2)
#装载模型
task=kpu.load(0x00300000)
#只有一个目标小球,1即为小球
labels=["1"] #You can check the numbers here to real names.

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.2, 0.2, 3, anchor)
a = kpu.init_yolo2(task, 0.05, 0.05, 3, anchor)
#降低了目标搜索精度阈值,此值可以调试

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.")

ball_x=0 #小球坐标
ball_y=0

#x_pid = PID(p=0.5, i=1, imax=100)
x_pid = PID(p=0.2, i=0.1, imax=100)
y_pid = PID(p=0.3, i=0.1, imax=50)
#h_pid = PID(p=0.05, i=0.1, imax=50)

Px=0.25
Py=0.25

while(True):

    img = sensor.snapshot()
    code = kpu.run_yolo2(task, img)
    #注:i.rect()[0]   i.rect()[1]    i.rect()[2]    i.rect()[3]   分别是兴趣框的x,y,w,h
    if code:
        for i in code:
            img.draw_rectangle(i.rect())
            lcd.display(img)
            #===图形标记,unit-v不需要
            #lcd.draw_string(i.x(), i.y(), labels[i.classid()], lcd.BLACK, lcd.WHITE)
            #lcd.draw_string(i.x(), i.y(), str(ball_x), lcd.BLACK, lcd.WHITE)
            #lcd.draw_string(i.x(), i.y()+12, '%f1.3'%i.value(), lcd.BLACK, lcd.WHITE)
            #lcd.draw_string(i.x(), i.y()+12, str(ball_y), lcd.BLACK, lcd.WHITE)

            ball_x = i.rect()[0] + i.rect()[2]//2 #计算小球兴趣框中心点横坐标
            ball_y = i.rect()[1] + i.rect()[3]//2 #计算小球兴趣框中心点纵坐标
            img.draw_cross(ball_x, ball_y) # ball_x, ball_y
            #x_error =190 -ball_x+img.width()/2 #计算小球中心点与画面中点横坐标的偏差值
            x_error = 190-ball_x #计算小球中心点与画面中点横坐标的偏差值
            y_error = ball_y-120 #计算小球中心点与画面下端纵坐标的偏差值,120基本是小球在视野的纵坐标中点
            print('**x,y***')
            print(ball_x,ball_y)

            #======在openmv以及m5的小球追踪例程中,均采用目标面积与阈值比对的方式来判断小球的远近,从而确定y_error
            #本文尝试用兴趣框中心点的纵坐标与画面中心点纵坐标比对,也就是在摄像头角度一定的情况下,目标越远则偏于
            #Y轴上端,反之亦然。这样做可以不必对目标检测兴趣框有过高要求,只需要中心点相对准确即可。
            x_output=x_pid.get_pid(x_error,1)
            y_output=y_pid.get_pid(y_error,1)
            #x_output=Px*x_error
            #y_output=Py*y_error
            #==== send json str for x_output and h_output  四舍五入 round()
            s_json="{\"x_output\":\""+str(round(x_output))+"\",\"h_output\":\""+str(round(y_output))+"\",\"z_output\":\""+str(0)+"\"}"

            uart_out.write(s_json+"\r\n")
            print(s_json)

    else:
        #lcd.display(img)
        #====== 小车原地旋转,寻找目标
        s_json="{\"x_output\":\""+str(0)+"\",\"h_output\":\""+str(0)+"\",\"z_output\":\""+str(15)+"\"}"
        #uart_out.write(s_json+"\r\n")


a = kpu.deinit(task)
[/mw_shl_code]

M5StickC
[mw_shl_code=arduino,true]//=================ADD PID
//=======2020-02-15
//=======2020-02-27 为roverc+unitv(目标识别小球)
//=======2020-02-28 完善z轴动作

#include <M5StickC.h>
#include <math.h>
  
//===========ArduinoJson部分
#include <ArduinoJson.h>

HardwareSerial VSerial(1);
//TFT_eSprite tft = TFT_eSprite(&M5.Lcd);

uint8_t I2CWrite1Byte(uint8_t Addr, uint8_t Data)
{
    Wire.beginTransmission(0x38);
    Wire.write(Addr);
    Wire.write(Data);
    return Wire.endTransmission();
}

uint8_t I2CWritebuff(uint8_t Addr, uint8_t *Data, uint16_t Length)
{
    Wire.beginTransmission(0x38);
    Wire.write(Addr);
    for (int i = 0; i < Length; i++)
    {
        Wire.write(Data);
    }
    return Wire.endTransmission();
}

uint8_t Setspeed(int16_t Vtx, int16_t Vty, int16_t Wt)
{
    int16_t speed_buff[4] = {0};
    int8_t speed_sendbuff[4] = {0};

    Wt = (Wt > 100) ? 100 : Wt;
    Wt = (Wt < -100) ? -100 : Wt;

    Vtx = (Vtx > 100) ? 100 : Vtx;
    Vtx = (Vtx < -100) ? -100 : Vtx;
    Vty = (Vty > 100) ? 100 : Vty;
    Vty = (Vty < -100) ? -100 : Vty;

    Vtx = (Wt != 0) ? Vtx * (100 - abs(Wt)) / 100 : Vtx;
    Vty = (Wt != 0) ? Vty * (100 - abs(Wt)) / 100 : Vty;

    speed_buff[0] = Vty - Vtx - Wt;
    speed_buff[1] = Vty + Vtx + Wt;
    speed_buff[3] = Vty - Vtx + Wt;
    speed_buff[2] = Vty + Vtx - Wt;

    for (int i = 0; i < 4; i++)
    {
        speed_buff = (speed_buff > 100) ? 100 : speed_buff;
        speed_buff = (speed_buff < -100) ? -100 : speed_buff;
        speed_sendbuff = speed_buff;
    }
    return I2CWritebuff(0x00, (uint8_t *)speed_sendbuff, 4);
}

void setup()
{
    M5.begin();
    M5.Lcd.setRotation(0);
    M5.Lcd.fillScreen(0);
    M5.Lcd.setRotation(3);
    M5.Lcd.setTextColor(BLUE);
    M5.Lcd.setCursor(40, 30, 4);
    M5.Lcd.printf("Rover Ball");

    VSerial.begin(115200, SERIAL_8N1, 33, 32);
    Wire.begin(0, 26);

    Setspeed(0, 0, 0);
    delay(2000);
   
}

int16_t ix,ih,iz;
//unsigned long T;
#define BASE_SPEED  20
bool last_dir = false;
void loop()
{
//   M5.update();
    const size_t capacity = JSON_OBJECT_SIZE(3) + 60;   //定义来自arduinojson.org的助手生成
    DynamicJsonDocument jsonBuffer(capacity);
  
     if(VSerial.available())
    {
     delay(10);
   DeserializationError error = deserializeJson(jsonBuffer, VSerial);// json数据源来自serial。生成一个jsonBuffer
   if (error) {
      Serial.print(F("deserializeJson() failed: "));
      Serial.println(error.c_str());
      return;
  }

  ix=jsonBuffer["x_output"]; //解析jsonBuffer,得到x_output
  ih=jsonBuffer["h_output"];//解析jsonBuffer,得到h_output
  iz=jsonBuffer["z_output"];//解析jsonBuffer,得到h_output

    Setspeed(ix,ih,iz);   //以ix速度沿x轴移动,以ih沿y轴移动,iz为旋转

}
}
[/mw_shl_code]


【关于学习训练】
1、基本步骤和微信跳一跳当中的模型训练、标记是一致的,不同之处就是本次项目的目标只有一个小球,所以"classes":1。在小球训练时,用了和追踪时相同的环境(台面、光线、角度),并且尽可能在移动中拍摄样本。我用了大约150张样本,比跳一跳稍微多一些,如果可能,建议玩家多拍一些,切忌一个角度拍摄多张,这样训练的意义就会削弱。
ball_left.JPG ball_far.JPG ball_nearly.JPG ball_right.JPG ball_runing.JPG
2、关于小车电机PID的参数选择。
  关于PID参数的意义,网上有很多很精辟的文章,大家可以自行搜索一下。
   我的做法是,第一步unit-v装在roverc上之后,在其视野里,对能够识别的四个边界进行测试和记录;
第二步:设置一个x轴系数px和一个y轴系数py。在openmv以及m5stack的官方例题当中,小球的远近测量都用的是兴趣框的面积,但是我们在实测中发现,这个兴趣框变化很大,造成V对小球远近的判断抖动很大,所以就改成了y轴参数来表征小球的远近。当摄像头的高度和角度(与地面夹角)一定时,Y轴参数可以代表小球的远近。
参数计算.PNG
第三步:进行参数调整,观察电机输出的动力和振荡情况,从而确定比较合适的系数。在本项目中,我们使用了PID和单纯P的两种方式,玩家可以根据各自的小车载机情况进行选择。目前某宝上的成品小车平台,往往转速高而减速比低,造成在低速状态下的扭矩不足,具体来说,就是控制死区比较大,如我以前玩过的几款平台,在PWM=42,甚至70以下就不能动。这样对小车追踪项目要求反馈迅速、电机动作灵敏来说,就很困难。建议如果有可能,选择1:100或者更大的减速比的减速电机。所幸roverc麦轮小车的死区在20,所以是一部不错的智能小车平台。另外,由于m5官方给出了控制函数,speedset(x,y,z)分别控制了小车的x轴、y轴以及围绕z轴的移动,比起差速小车更加方便快捷。
x>0,向左侧移动
y>0,向前移动
z>0,逆时针旋转
参考方向:以RoverC的电源开关为后,以C的排母为前。

roverc2.jpg
【小结】
      这是一个建立在深度学习上的小玩具,仍然是抛砖引玉,希望各位师兄能有更精彩的创意。
   沧海抱拳。


【鸣谢】
  感谢arduino.cnm5stack.com提供优秀的交流和软硬件平台。
  本例程中关键的V端代码主要来自于训练后的结果,参考了openmv的小球追踪例程。在此鸣谢。
    C侧的RoverC的驱动代码函数来自于@借来的猫师兄,谢谢您的工作。
    V侧的目标检测得到了笑笑(经常深夜打扰,不停在服务器端改进完善)全面指导、Jimmy、滚筒洗衣机(减速电机的选型感谢有你)、九磅十五便士等师兄的指导,以及群里各位师兄的支持。一并致谢。

附件包含模型、代码,分了三卷压缩上传
follow_ball.part1.rar (1 MB, 下载次数: 10)

该用户从未签到

发表于 2020-2-29 23:36 | 显示全部楼层
v-train 不是最少 要两个分类才行吗?

该用户从未签到

 楼主| 发表于 2020-2-29 23:49 | 显示全部楼层
genvex 发表于 2020-2-29 23:36
v-train 不是最少 要两个分类才行吗?

v-train升级了,这是针对目标检测的应用,分类不是必要条件,1个目标一样ok的。

该用户从未签到

发表于 2020-3-8 09:42 | 显示全部楼层
好例子。。。 否用这个延伸一个可以自动瞄准的枪。哈哈

该用户从未签到

发表于 2020-3-8 09:43 | 显示全部楼层
v-train能不能本地安装相应的环境来生成的?

该用户从未签到

 楼主| 发表于 2020-3-8 12:47 | 显示全部楼层
laai 发表于 2020-3-8 09:43
v-train能不能本地安装相应的环境来生成的?

包括v-train在内的深度学习训练的门槛还是比较高,要求的软硬件配置很高,理论上当然可以本地部署和训练,实际上个人玩家这样做并不多,具体看你的项目和需求吧。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

arduino程序设计基础 blinker物联网解决方案

热门推荐

关于红外的求助
关于红外的求助
为什么红外发射出的跟我设定的不一样,如图,我两个板子一个发射,一个接收,我想要发
【Arduino】168种传感器模块系列实验(104)---MAX30102手腕心率
【Arduino】168种传感器模
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是
[Arduino物联网开发实战5]云端历史数据存储与查看
[Arduino物联网开发实战5]
blinker提供了历史数据存储与图表查看数据的功能。 设备端开发 在blinker的设计下,设
程序卡着不动,最后打印Freeheap 25427
程序卡着不动,最后打印Fr
各位有见过这个错误吗,程序跑到这里卡着不动
通过定时器读取串口数据出错
通过定时器读取串口数据出
各位大佬好,本人通过树莓派和Arduino通信,一边接收Arduino传感器数据,一边给Arduin
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
快速回复 返回顶部 返回列表