查看: 2010|回复: 6

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

[复制链接]
  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

    发表于 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)端

    kittenblock中小学创客名师推荐的图形化编程软件

    #=========使用机器学习追踪小球目标的小车
    #=========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)
    

    M5StickC

    kittenblock中小学创客名师推荐的图形化编程软件

    //=================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为旋转
    
    }
    }
    


    【关于学习训练】
    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, 下载次数: 2)
  • TA的每日心情
    擦汗
    2020-4-13 14:33
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2020-2-29 23:36 | 显示全部楼层
    v-train 不是最少 要两个分类才行吗?
  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

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

    v-train升级了,这是针对目标检测的应用,分类不是必要条件,1个目标一样ok的。
  • TA的每日心情
    郁闷
    2020-3-26 12:11
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    发表于 2020-3-8 09:42 | 显示全部楼层
    好例子。。。 否用这个延伸一个可以自动瞄准的枪。哈哈
  • TA的每日心情
    郁闷
    2020-3-26 12:11
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    发表于 2020-3-8 09:43 | 显示全部楼层
    v-train能不能本地安装相应的环境来生成的?
  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

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

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

    本版积分规则

    热门推荐

    5分钟带你快速了解新一代开发板:M5STACK
    5分钟带你快速了解新一代
    一、什么是M5Stack M5Stack是一种模块化、可堆叠扩展的开发板,每个模块
    创客火首发无人机编队套装,开启不一样的人工智能教育
    创客火首发无人机编队套装
    2017年国务院发布《新一代人工智能发展规划》,提出要广泛开展人工智能科普活动,在中
    求大神看看哪里有问题,设备在线,米家也同步正常,就.....
    求大神看看哪里有问题,设
    代码: #define BLINKER_PRINT Serial //用于打开串口 #define BLINKER_WIFI #define
    晒图ESP8266一个框架
    晒图ESP8266一个框架
    好久没发帖了,出来活跃活跃一下。ESP8266免身份登录截图。 最近外研究新的架构M2M或P
    Arduino 求助 串口接收数据不正确,数据丢失问题
    Arduino 求助 串口接收数
    问题整了好久,我自己写的C#程序发送数据,arduino接收数据。 但是发现arduino接收的
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
    快速回复 返回顶部 返回列表