0.教学资源
以下是本节课的微课,配合文档学习效果更佳喔。
动手3-追踪目标小车.pptx
1.课前导入
今天我们要制作一个跟踪目标的小车,就比如下面的踢足球小车。
视频中的小车能识别出橙色小球,然后驱动马达追击小球。本节课,我们就会学习到如何驱动马达,以及如何配合颜色识别或者人脸识别实现追击目标的效果。
如果你不记得云台是如何追踪颜色以及人脸的话,赶紧翻一翻“动手时间1”和“动手时间2”吧。
2.知识准备
电机(马达)
电机也就是俗称的马达,它是小车中最重要的驱动部件,带动轮子运动。控制电机的快慢需要一种特殊的控制方法,叫做PWM,那么什么是PWM呢?
图1.马达和轮子
PWM脉冲宽度调制
马达转动的快慢与电压有关,如果电压越小,转动的则越慢,反之亦然。但是在OpenMV中,我们不能直接的调节电压,而需要通过PWM来调节。
PWM简单的说,是通过放电的时间长短来控制电压的。比如说,在一段时间中,一半的时间通5V的电压,一半的时间不通电。那么,该段时间的有效电压就是2.5V(如下图的第一个例子)。
图2.PWM原理
如果还没看懂,可以看看这个视频。
点击查看【bilibili】
PWM相关代码
在开始编程之前,我们需要将小车和OpenMV做好连接,驱动电机需要准备一块电机驱动板,我用的是“迷你L298N”。
图3.电机驱动板迷你L298N
我们将两个电机,一共四根线连接至电机驱动板的MOTOR-A和MOTOR-B,在另一端INT4连接OpenMV中的SCL口,INT3连接CS口,INT2连接RST口,INT1连接SDA口,并且将电源连入电机驱动板。这样,OpenMV和电机就连接完毕了。
图4.电路连接图
因为PWM需要根据时间来通电,所以它需要计时器帮助控制。我们定义两个计时器(tim2和tim12),分别操控右轮和左轮,频率为1000hz。
from pyb import Pin, Timer
tim2 = Timer(2, freq=1000) #创建两个计时器,频率为1000hz,用它们控制引脚周期性的变化
tim12 = Timer(12, freq=1000)
然后,我们就可以开始把引脚定义出来,我们定义了四个PWM引脚(pwma,pwmb,pwmc,pwmd)来控制:左轮向前,左轮向后,右轮向前,右轮向后。
pwma = Pin('PB10') #将绿线插在电机驱动板int4和SCL 控制左轮前进
pwmb = Pin('PB11') #蓝线 int3 CS 控制左轮后退
pwmc = Pin('PB14') #白线 int2 RST 控制右轮前进
pwmd = Pin('PB15') #紫线 int1 SDA 控制右轮后退
接着,我们设立四个通道,在通道里,计时器将会控制PWM接口。并且通过pulse_width_percent(0)将电压百分值设置为0。
#2号定时器控制1和2通道,引脚为pwma和pwmb
ch1 = tim2.channel(3, Timer.PWM, pin=pwma) #左轮向前
ch2 = tim2.channel(4, Timer.PWM, pin=pwmb) #左轮向后
#12号定时器控制3和4通道,引脚为pwma和pwmb
ch3 = tim12.channel(1, Timer.PWM, pin=pwmc) #右轮向前
ch4 = tim12.channel(2, Timer.PWM, pin=pwmd) #右轮向后
ch1.pulse_width_percent(0) #将通道的电位设置为0
ch2.pulse_width_percent(0)
ch3.pulse_width_percent(0)
ch4.pulse_width_percent(0)
最后,我们定义一个run函数,来控制小车的前进和后退。run函数有两个参数,分别是左轮速度和右轮速度,如果速度小于0,则分别控制ch2和ch4的电压增高;如果大于0则将ch1和ch3电压增高。增高的数值就是输入的参数。
def run(left_speed, right_speed):
if left_speed < 0: #如果左轮速度小于0
ch2.pulse_width_percent(int(abs(left_speed))) #控制左轮向后退
ch1.pulse_width_percent(0)
else:
ch1.pulse_width_percent(int(abs(left_speed))) #否则控制左轮向前进
ch2.pulse_width_percent(0)
if right_speed < 0:#如果右轮速度小于0
ch4.pulse_width_percent(int(abs(right_speed))) #控制右轮向后退
ch3.pulse_width_percent(0)
else:
ch3.pulse_width_percent(int(abs(right_speed))) #否则控制右轮向前进
ch4.pulse_width_percent(0)
大功告成,我们将这个文件命名为:car.py,放在OpenMV内存中。
新建一个文件test.py,测试一下刚才写的代码。
import car
while True:
car.run(100,100)
小车如何转向?
眼尖的同学可能发现了,我们本节课使用的小车和传统的车不一样,它没有转向轴。
我们平时坐的汽车,它的轮子可以朝左、朝右进行转动,这就是转动轴的作用。
图5.转动轴转向
但是本节课的小车没有转动轴,那它如何转弯呢?我们需要通过车轮的差速来进行转弯。
当我们想向左转时,我们要使右边的轮子的速度比左边快;想右转时,要使左边的轮子比右边快。这样的转向方式也是履带坦克的转向方法。
图6.差速转向
3.主程序实现
和之前学到的“颜色追踪云台”以及“人脸识别云台”类似,我们需要用到PID算法,然后根据“色块”或者“人脸”的位置来调整轮子的转速。
注意:在开始程序前,先把pid.py和car.py放进内存。如果想脱机运行,将下面的代码替换进内存中的main.py即可,详情查看动手时间2的脱机运行。
跟踪颜色小车
import sensor, image
import car
from pid import PID
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(10)
sensor.set_auto_whitebal(False)
green_threshold = (38, 58, -67, -24, 19, 127) #追踪绿色的物体
size_threshold = 2000
x_pid = PID(p=0.5, i=1, imax=100) #创建PID
h_pid = PID(p=0.05, i=0.1, imax=50)
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
while(True):
img = sensor.snapshot()
blobs = img.find_blobs([green_threshold])
if blobs:
max_blob = find_max(blobs)
x_error = max_blob.cx()-img.width()/2 #x方向上的偏移值,往左小于0,往右大于0
#将当前识别到的色块和面积阈值相减,得到距离偏移值 远距离是正值,近距离是负直
h_error = size_threshold-max_blob[2]*max_blob[3]
print("x error: ", x_error)
print("h_error",h_error)
img.draw_rectangle(max_blob.rect())
img.draw_cross(max_blob.cx(), max_blob.cy())
x_output=x_pid.get_pid(x_error,1)
h_output=h_pid.get_pid(h_error,1)
#将h_output和x_output结合,共同影响轮胎转速
car.run(h_output+x_output,h_output-x_output)
else:
car.run(0,0)#如果没检测到色块,就停下小车
跟踪人脸小车
import sensor, image
import car
from pid import PID
sensor.reset() # 初始化摄像头.
sensor.set_framesize(sensor.HQVGA)
sensor.set_pixformat(sensor.GRAYSCALE) #注意人脸识别只能用灰度图
sensor.skip_frames(10) # 让新设定生效
sensor.set_auto_whitebal(False) # 关闭白平衡
size_threshold = 7000
x_pid = PID(p=0.5, i=1, imax=100)
h_pid = PID(p=0.05, i=0.1, imax=50)
face_cascade = image.HaarCascade("frontalface", stages=15) #加入一个Haar分类器
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
while(True):
img = sensor.snapshot()
faces = img.find_features(face_cascade, threshold=0.5, scale=1.25)
if faces:
face = find_max(faces) #寻找最大的人脸
cx = int(face[0]+face[2]/2) #face[0]是该色块左上角的x坐标,face[2]是该色块的宽
cy = int(face[1]+face[3]/2) #face[1]是该色块左上角的y坐标,face[3]是该色块的长
x_error = cx -img.width()/2 #x方向上的偏移值 往左小于0 往右大于0
h_error = size_threshold-face[2]*face[3] #将当前识别到的色块和面积阈值相减,得到距离偏移值 远距离是正值,近距离是负直
print("x error: ", x_error)
print("h_error",h_error)
img.draw_rectangle(face)
img.draw_cross(cx, cy)
x_output=x_pid.get_pid(x_error,1)
h_output=h_pid.get_pid(h_error,1)
#将h_output和x_output结合,共同影响轮胎转速
car.run(h_output+x_output,h_output-x_output)
#以下是我测试后,合适在桌子上追踪人脸的数值
#car.run(0.4*h_output+0.26*x_output,0.4*h_output-0.26*x_output)
else:
car.run(0,0) #如果没检测到人脸,就停下小车