本帖最后由 沧海笑1122 于 2020-3-1 14:19 编辑
M5StickV(unit)深度学习之追踪小球的 Giraffe长颈鹿小车
【故事】 这也是一个深度学习之目标检测的小玩具。利用M5StickV(unit)的深度学习,训练了一个小球目标,使用M5Stack的RoverC麦轮小车作为载机。在视觉传感器识别到目标后,返回目标的准确坐标(x,y),然后驱动小车追踪。那天搭建好小车后,Jimmy说好像一个长颈鹿,所以我就起了个外号M5Giraffe。这样的小车搭建主要是为了照顾视角更大范围捕获小球目标。 目标检测、yolo3的背景资料详见《M5StickV(unit)深度学习之微信跳一跳》。下面我们就可以直接上干货了。 还是老规矩,一段视频来感受一下
【硬件】
【软件】
| | | | | | | | | | | Arduino ide 1.8.11 M5的基础库 关于RoverC麦轮小车的控制函数 | | | |
【代码】 M5StickV(Unit-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张样本,比跳一跳稍微多一些,如果可能,建议玩家多拍一些,切忌一个角度拍摄多张,这样训练的意义就会削弱。 2、关于小车电机PID的参数选择。 关于PID参数的意义,网上有很多很精辟的文章,大家可以自行搜索一下。
我的做法是,第一步对unit-v装在roverc上之后,在其视野里,对能够识别的四个边界进行测试和记录; 第二步:设置一个x轴系数px和一个y轴系数py。在openmv以及m5stack的官方例题当中,小球的远近测量都用的是兴趣框的面积,但是我们在实测中发现,这个兴趣框变化很大,造成V对小球远近的判断抖动很大,所以就改成了y轴参数来表征小球的远近。当摄像头的高度和角度(与地面夹角)一定时,Y轴参数可以代表小球的远近。 第三步:进行参数调整,观察电机输出的动力和振荡情况,从而确定比较合适的系数。在本项目中,我们使用了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的排母为前。
【小结】 这是又一个建立在深度学习上的小玩具,仍然是抛砖引玉,希望各位师兄能有更精彩的创意。 沧海抱拳。
【鸣谢】 感谢arduino.cn和m5stack.com提供优秀的交流和软硬件平台。 本例程中关键的V端代码主要来自于训练后的结果,参考了openmv的小球追踪例程。在此鸣谢。 C侧的RoverC的驱动代码函数来自于@借来的猫师兄,谢谢您的工作。 V侧的目标检测得到了笑笑(经常深夜打扰,不停在服务器端改进完善)全面指导、Jimmy、滚筒洗衣机(减速电机的选型感谢有你)、九磅十五便士等师兄的指导,以及群里各位师兄的支持。一并致谢。
附件包含模型、代码,分了三卷压缩上传
|