报名的时候选择的儿童陪伴,想做一些声光效果,因为麦昆剩余的引脚实在有限,没有什么发挥余地,所以想与手机结合做些扩充,根据环境的变化将值传给手机,手机开启更多的应用,如唱歌、读诗、安抚、绘画等等。后来觉得这个应用没有什么挑战难度,脑洞开的不够大。手头正好有图像传感器,想尝试做些图像识别的项目。
下面是DFROBOT商城对图像识别传感器的介绍,看着是不是很让人心动?但是千万别冲动,入坑容易出来难啊!
“Pixy是一个开源的图像识别传感器,支持多物体,多色彩的颜色识别,最高支持7种颜色。你可以告诉它你想要的颜色,教它找东西。Pixy支持多种通信方式,如SPI,I2C等,可以直插在Arduino控制板上面。把它安装到你的机器人上,为你的小机器人增加一副眼睛。它搭载的图像传感器配合强大的硬件,可以配合PC跟踪、分析多色的数据。 Pixy是由Charmed实验室联合卡内基梅隆大学共同推出的一款图像传感器。其强大的处理器上搭载着一个图像传感器,它会选择性的处理有用的信息,因为它采用以颜色为中心的办法——使该产品只是将特定颜色的物体的视觉数据发送给相互配合的微型控制器,而不是输出所有视觉数据以进行图像处理。所以你的Arduino板或其它微控制器很容易跟它交流,并滕出许多CPU来执行其他事件。”
项目设想:
让麦昆有双慧眼,可以跟着麦昆学交规,当麦昆看到交通灯或一些交通符号时能够做出正确的选择。但是随着项目的实施,发现坑无处不在,因此,最后这个脑洞赛就硬生生的被做成了科学探究,解决一个又一个出现的问题。
这个项目遇到的最大难处是Pixy CMUcam5 图像识别传感器在国内几乎没有相关资料,社区也没有人发过帖子。国外能找到的基本都是在arduino上实现,传感器配了SPI线可以直接接到Arduino上,通信也相对简单。但是microbit则几乎找不到资料,开始的时候就是看帮助文档,文档非常多,只能反复看多揣摩,越往后做遇到的问题越多,很多时候甚至不知道问题出在哪里,真是做到让我怀疑人生。
步骤1 安装PixyMon及固件
注:Pixy1代和2代需要下载不同的PixyMon及固件版本,两者之间不通用。
步骤2 安装Arduino库并通过串口监视器读取返回值
这一步是为了使用Arduino的串口监视器来直观获取Pixy传感器的返回值,为后面microbit的使用做准备。视频我用的是PIXY2,PIXY是完全一样的。PIXY2后来才发现在microbit上无法通过I2C获取返回值(不知是何原因),所以最终用的是PIXY1代。
步骤3 线路连接
下图为PIXY的背面图
上图是Pixy的引脚图(1代和2代是一样的)。
I / O端口的引脚按以下顺序排列,左上角是引脚1,右下角是引脚10
引脚2接麦昆I2C的+,引脚5接麦昆I2C的SCL,引脚6接麦昆I2C的GND,引脚9接麦昆I2C的SDA.
下面6个引脚是云台舵机的引脚,左边接水平方向的舵机,右边接垂直方向的舵机。我没有接PIXY的舵机引脚,因为麦昆还有P1P2引脚可用,就直接把舵机接到了P1P2上。
下图是接好的样子。电池盒是把4节电池盒改装成3节安上去,有点笨重。云台需要修剪,官网里有详细的方法。这里不赘述,后续如果有时间我再发教程贴吧。
因为时间紧,又是一个人,所以没有拍过程图,接好了拍照如下:
最后一天在做小车跟随行走的时候舵机居然坏了(当然用了很久才判断出来这个问题,这个项目的自变量太多了)。然后拆云台,重新装舵机,各种折腾,白白浪费2个小时(手太笨了!)
步骤4 教Pixy学习(按钮方式)
按钮方式比较简单,还有一种学习方式是在PixyMon里,mouse click方式,我在教程贴里会写。
步骤5 在Microbit中读取PIXY的返回值
编程平台
Makecode
之所以选择makecode,一个是因为mind+没有I2C语句块,而且mind+的bug目前还没有解决,有时候做好的东西保存之后重新加载却无法打开,如果要编大的程序,这是很让人崩溃的,期待能够早日解决这个bug。
编程语言
Javascript
makecode中我也是大部分选择代码的方式,因为写代码比拖动积木块高效,而且makecode的代码补全、语法错误检查还是非常好用的。最重要的一点是程序使用了类以及带参数的函数,积木块是无法实现这些功能的。
代码:定义变量
//变量定义
let PIXY_START_WORD_CC = 0
let oldX = 0
let oldY = 0
let oldSignature = 0
let Y_CENTER = 0
let newSize = 0
let X_CENTER = 0
let maxSize = 0
let obj_Y = 0
let obj_X = 0
let signature = 0
let checkSum = 0
let lastBlockTime = 0
let PIXY_START_WORD = 0
let var2 = 0
let I2C_address = 0
let var1 = 0
let rightSpeed = 0
let leftSpeed = 0
let height = 0
let width = 0
let tiltError = 0
let panError = 0
let RCS_CENTER_POS = 0
let RCS_MAX_POS = 0
let RCS_MIN_POS = 0
let size = 0
代码:定义函数用于跟随物体行走
// 定义函数用于跟随物体行走
function FollowBlock(width: number, height: number) {
let followError = 0
let forwardSpeed = 0
let differential = 0
followError = RCS_CENTER_POS - panLoop.m_pos
//basic.showString("E:" + Math.trunc(followError))
size += width * height
//basic.showString("S:" + Math.trunc(size))
// size -= size / 8
forwardSpeed = Math.constrain(400 - (size / 256), -100, 400) / 10
//basic.showString("F:" + Math.trunc(forwardSpeed))
differential = (followError + (followError * forwardSpeed)) / 256
//basic.showString("D:" + Math.trunc(differential))
leftSpeed = Math.constrain(forwardSpeed + differential, -400, 400) / 10
//basic.showString("L:" + Math.trunc(leftSpeed))
rightSpeed = Math.constrain(forwardSpeed - differential, -400, 400) / 10
//basic.showString("R:" + Math.trunc(rightSpeed))
maqueen.MotorRun(maqueen.aMotors.M1, maqueen.Dir.CW, leftSpeed)
maqueen.MotorRun(maqueen.aMotors.M2, maqueen.Dir.CW, rightSpeed)
}
代码:初始化云台位置,初始化变量
// 初始化摄像头云台位置
pins.servoWritePin(AnalogPin.P1, 90)
pins.servoWritePin(AnalogPin.P2, 90)
// 变量初始化
maxSize = 0
newSize = 0
oldSignature = 0
oldY = 0
oldX = 0
width = 0
height = 0
oldSignature = 0
X_CENTER = 160
Y_CENTER = 100
RCS_MIN_POS = 0
RCS_MAX_POS = 180
size = 400
RCS_CENTER_POS = (RCS_MAX_POS - RCS_MIN_POS) / 2
PIXY_START_WORD = -21931
PIXY_START_WORD_CC = -21930
I2C_address = 84
lastBlockTime = 0
maqueen.motorStopAll()
说明:
我们看到程序中定义了两个变量PIXY_START_WORD(Normal Object单色物体)和PIXY_START_WORD_CC(Color Code,多色对象),这两个变量代表同步字。数字是不是觉得很奇怪?下面看一下官方给出的Arduino程序,这两个变量的值分别是0xaa55,0xaa56。
#define PIXY_ARRAYSIZE 100
#define PIXY_START_WORD 0xaa55
#define PIXY_START_WORD_CC 0xaa56
#define PIXY_START_WORDX 0x55aa
#define PIXY_SERVO_SYNC 0xff
#define PIXY_CAM_BRIGHTNESS_SYNC 0xfe
#define PIXY_LED_SYNC 0xfd
#define PIXY_OUTBUF_SIZE 64
打开科学计算器,进行一下换算就可以发现它们之间的关系了:
代码:定义类及实例化
实例化时参数需要根据运行的结果不断调整。如果摄像头抖动很厉害,可以将propotionalGain的参数调低;如果摄像头大幅度摆动,可以将velocity中的被除数放大,达到微调的效果。
class ServoLoop {
m_pos: number
m_prevError: number
m_proportionalGain: number
m_deritativeGain: number
constructor(propotionalGain: number, derivativeGain: number) {
this.m_pos = RCS_CENTER_POS
this.m_proportionalGain = propotionalGain
this.m_deritativeGain = derivativeGain
this.m_prevError = 0x80000000
}
update(error: number) {
let velocity = 0
if (this.m_prevError != 0x80000000) {
velocity = (error * this.m_proportionalGain + (error - this.m_prevError) * this.m_deritativeGain) / 1024
this.m_pos += velocity
if (this.m_pos > RCS_MAX_POS) {
this.m_pos = RCS_MAX_POS
}
else if (this.m_pos < RCS_MIN_POS) {
this.m_pos = RCS_MIN_POS
}
}
this.m_prevError = error
}
}
let panLoop = new ServoLoop(5, 200)
let tiltLoop = new ServoLoop(15, 200)
代码:程序主体
basic.forever(function () {
var1 = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
if (var1 != 0) {
if (var1 == PIXY_START_WORD) {
var2 = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
if (var2 == PIXY_START_WORD) {
checkSum = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
signature = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
obj_X = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
obj_Y = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
width = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
height = pins.i2cReadNumber(I2C_address, NumberFormat.Int16LE, true)
panError = X_CENTER - obj_X
tiltError = obj_Y - Y_CENTER
panLoop.update(panError)
tiltLoop.update(tiltError)
pins.servoWritePin(AnalogPin.P1, panLoop.m_pos)
pins.servoWritePin(AnalogPin.P2, tiltLoop.m_pos)
FollowBlock(width, height)
oldX = obj_X
oldY = obj_Y
oldSignature = signature
}
}
}
})
关于PID算法
这是一种反馈控制(Feedback Control)。不是简单的用物体的X、Y坐标来控制,而是用比例、积分、微分来调节,使摄像头及小车移动更灵活自然。详情可参见文末的参考资料。
其中的参数需要自己根据测试的反馈来不断的调整,就像炼丹一样,不知道什么时候可以达到最佳效果,只能慢慢逼近~~
图片来源:CSDN
图片来源:https://cdn-learn.adafruit.com/downloads/pdf/pixy-pet-robot-color-vision-follower-using-pixycam.pdf
感想:
最后这个项目虽然离预期还有距离,但是我自己已经非常非常满意了,只有自己知道太不容易了,但是完成后的喜悦也是巨大的。这也许就是学习的乐趣吧!
当然也要感谢社区提供了这么好的活动,让自己有了挑战自我的机会。
小小吐槽:
这里把过程步骤简化了很多,Makelog确实不太方便。我习惯边做边记录,做的时候都会截图,或者直接粘贴到论坛草稿箱里,或者保存到电脑上。但是在makelog里不能直接复制粘贴图片,而上传图片时经常提示那些截图的图片过小,导致无法上传图片。
写帖子还是在论坛里方便哈哈~~
参考资料
https://blog.beyond-coding.org.t ... icrobitpixycam.html
https://makecode.microbit.org/reference/pins/i2c-read-number
https://makecode.microbit.org/javascript/classes
https://github.com/liamkinne/microbit-pixy
https://blog.csdn.net/qq_25352981/article/details/81007075
https://docs.pixycam.com/wiki/do ... oading_new_firmware
https://docs.pixycam.com/wiki/doku.php?id=wiki:v1:porting_guide
https://docs.pixycam.com/wiki/doku.php?id=wiki:v1:Teach_Pixy_an_Object_2
https://docs.pixycam.com/wiki/doku.php?id=wiki:v1:start#how-to-connect-pixy-to
https://cdn-learn.adafruit.com/downloads/pdf/pixy-pet-robot-color-vision-follower-using-pixycam.pdf
luyi2020.02.05
好赞啊,学习了!
江宇瀚2019.08.03
牛
江宇瀚2019.07.26
ROBO2019.06.21
我也在学习研究这个传感器,符合人工智能的范畴,希望大家在一起交流,建议成立一个QQ群(我的QQ:380282219),有兴趣的老师联系一下。谢谢!
rzyzzxw2019.06.19
赞一个+1,手上有pixy2,玩不转。
szjuliet2019.06.20
我刚开始也是用pixy2,从I2C读不到返回值,无奈只好改用一代。不过用Arduino就没问题,简单很多。
许培享2019.05.17
大赞
gray66662019.05.16
技术大师,赞一个