0.教学资源

以下是本节课的微课,配合文档学习效果更佳喔。 7.mp4 (81.37MB)动手3-追踪目标小车.pptx

1.课前导入

今天我们要制作一个跟踪目标的小车,就比如下面的踢足球小车。 FIRA 2013 国际机器人足球大赛小组赛进球片段(inovamicro)_好看视频.mp4 (3.78MB)视频中的小车能识别出橙色小球,然后驱动马达追击小球。本节课,我们就会学习到如何驱动马达,以及如何配合颜色识别或者人脸识别实现追击目标的效果。
如果你不记得云台是如何追踪颜色以及人脸的话,赶紧翻一翻“动手时间1”和“动手时间2”吧。

2.知识准备

电机(马达)

电机也就是俗称的马达,它是小车中最重要的驱动部件,带动轮子运动。控制电机的快慢需要一种特殊的控制方法,叫做PWM,那么什么是PWM呢?
image.png
图1.马达和轮子

PWM脉冲宽度调制

马达转动的快慢与电压有关,如果电压越小,转动的则越慢,反之亦然。但是在OpenMV中,我们不能直接的调节电压,而需要通过PWM来调节。
PWM简单的说,是通过放电的时间长短来控制电压的。比如说,在一段时间中,一半的时间通5V的电压,一半的时间不通电。那么,该段时间的有效电压就是2.5V(如下图的第一个例子)。
image.png
图2.PWM原理
如果还没看懂,可以看看这个视频。
点击查看【bilibili】

PWM相关代码

在开始编程之前,我们需要将小车和OpenMV做好连接,驱动电机需要准备一块电机驱动板,我用的是“迷你L298N”。
image.png
图3.电机驱动板迷你L298N
我们将两个电机,一共四根线连接至电机驱动板的MOTOR-A和MOTOR-B,在另一端INT4连接OpenMV中的SCL口,INT3连接CS口,INT2连接RST口,INT1连接SDA口,并且将电源连入电机驱动板。这样,OpenMV和电机就连接完毕了。
image.png
图4.电路连接图
因为PWM需要根据时间来通电,所以它需要计时器帮助控制。我们定义两个计时器(tim2和tim12),分别操控右轮和左轮,频率为1000hz。

  1. from pyb import Pin, Timer
  2. tim2 = Timer(2, freq=1000) #创建两个计时器,频率为1000hz,用它们控制引脚周期性的变化
  3. tim12 = Timer(12, freq=1000)

然后,我们就可以开始把引脚定义出来,我们定义了四个PWM引脚(pwma,pwmb,pwmc,pwmd)来控制:左轮向前,左轮向后,右轮向前,右轮向后。

  1. pwma = Pin('PB10') #将绿线插在电机驱动板int4和SCL 控制左轮前进
  2. pwmb = Pin('PB11') #蓝线 int3 CS 控制左轮后退
  3. pwmc = Pin('PB14') #白线 int2 RST 控制右轮前进
  4. pwmd = Pin('PB15') #紫线 int1 SDA 控制右轮后退

接着,我们设立四个通道,在通道里,计时器将会控制PWM接口。并且通过pulse_width_percent(0)将电压百分值设置为0。

  1. #2号定时器控制1和2通道,引脚为pwma和pwmb
  2. ch1 = tim2.channel(3, Timer.PWM, pin=pwma) #左轮向前
  3. ch2 = tim2.channel(4, Timer.PWM, pin=pwmb) #左轮向后
  4. #12号定时器控制3和4通道,引脚为pwma和pwmb
  5. ch3 = tim12.channel(1, Timer.PWM, pin=pwmc) #右轮向前
  6. ch4 = tim12.channel(2, Timer.PWM, pin=pwmd) #右轮向后
  7. ch1.pulse_width_percent(0) #将通道的电位设置为0
  8. ch2.pulse_width_percent(0)
  9. ch3.pulse_width_percent(0)
  10. ch4.pulse_width_percent(0)

最后,我们定义一个run函数,来控制小车的前进和后退。run函数有两个参数,分别是左轮速度和右轮速度,如果速度小于0,则分别控制ch2和ch4的电压增高;如果大于0则将ch1和ch3电压增高。增高的数值就是输入的参数。

  1. def run(left_speed, right_speed):
  2. if left_speed < 0: #如果左轮速度小于0
  3. ch2.pulse_width_percent(int(abs(left_speed))) #控制左轮向后退
  4. ch1.pulse_width_percent(0)
  5. else:
  6. ch1.pulse_width_percent(int(abs(left_speed))) #否则控制左轮向前进
  7. ch2.pulse_width_percent(0)
  8. if right_speed < 0:#如果右轮速度小于0
  9. ch4.pulse_width_percent(int(abs(right_speed))) #控制右轮向后退
  10. ch3.pulse_width_percent(0)
  11. else:
  12. ch3.pulse_width_percent(int(abs(right_speed))) #否则控制右轮向前进
  13. ch4.pulse_width_percent(0)

大功告成,我们将这个文件命名为:car.py,放在OpenMV内存中。
新建一个文件test.py,测试一下刚才写的代码。

  1. import car
  2. while True:
  3. car.run(100,100)

如果连线正确以及代码正确的话,现在小车应该会向前走了。

小车如何转向?

眼尖的同学可能发现了,我们本节课使用的小车和传统的车不一样,它没有转向轴。
我们平时坐的汽车,它的轮子可以朝左、朝右进行转动,这就是转动轴的作用。
image.png
图5.转动轴转向
但是本节课的小车没有转动轴,那它如何转弯呢?我们需要通过车轮的差速来进行转弯。
当我们想向左转时,我们要使右边的轮子的速度比左边快;想右转时,要使左边的轮子比右边快。这样的转向方式也是履带坦克的转向方法。
image.png
图6.差速转向

3.主程序实现

和之前学到的“颜色追踪云台”以及“人脸识别云台”类似,我们需要用到PID算法,然后根据“色块”或者“人脸”的位置来调整轮子的转速。
注意:在开始程序前,先把pid.py和car.py放进内存。如果想脱机运行,将下面的代码替换进内存中的main.py即可,详情查看动手时间2的脱机运行。

跟踪颜色小车

  1. import sensor, image
  2. import car
  3. from pid import PID
  4. sensor.reset()
  5. sensor.set_pixformat(sensor.RGB565)
  6. sensor.set_framesize(sensor.QQVGA)
  7. sensor.skip_frames(10)
  8. sensor.set_auto_whitebal(False)
  9. green_threshold = (38, 58, -67, -24, 19, 127) #追踪绿色的物体
  10. size_threshold = 2000
  11. x_pid = PID(p=0.5, i=1, imax=100) #创建PID
  12. h_pid = PID(p=0.05, i=0.1, imax=50)
  13. def find_max(blobs):
  14. max_size=0
  15. for blob in blobs:
  16. if blob[2]*blob[3] > max_size:
  17. max_blob=blob
  18. max_size = blob[2]*blob[3]
  19. return max_blob
  20. while(True):
  21. img = sensor.snapshot()
  22. blobs = img.find_blobs([green_threshold])
  23. if blobs:
  24. max_blob = find_max(blobs)
  25. x_error = max_blob.cx()-img.width()/2 #x方向上的偏移值,往左小于0,往右大于0
  26. #将当前识别到的色块和面积阈值相减,得到距离偏移值 远距离是正值,近距离是负直
  27. h_error = size_threshold-max_blob[2]*max_blob[3]
  28. print("x error: ", x_error)
  29. print("h_error",h_error)
  30. img.draw_rectangle(max_blob.rect())
  31. img.draw_cross(max_blob.cx(), max_blob.cy())
  32. x_output=x_pid.get_pid(x_error,1)
  33. h_output=h_pid.get_pid(h_error,1)
  34. #将h_output和x_output结合,共同影响轮胎转速
  35. car.run(h_output+x_output,h_output-x_output)
  36. else:
  37. car.run(0,0)#如果没检测到色块,就停下小车

跟踪人脸小车

  1. import sensor, image
  2. import car
  3. from pid import PID
  4. sensor.reset() # 初始化摄像头.
  5. sensor.set_framesize(sensor.HQVGA)
  6. sensor.set_pixformat(sensor.GRAYSCALE) #注意人脸识别只能用灰度图
  7. sensor.skip_frames(10) # 让新设定生效
  8. sensor.set_auto_whitebal(False) # 关闭白平衡
  9. size_threshold = 7000
  10. x_pid = PID(p=0.5, i=1, imax=100)
  11. h_pid = PID(p=0.05, i=0.1, imax=50)
  12. face_cascade = image.HaarCascade("frontalface", stages=15) #加入一个Haar分类器
  13. def find_max(blobs):
  14. max_size=0
  15. for blob in blobs:
  16. if blob[2]*blob[3] > max_size:
  17. max_blob=blob
  18. max_size = blob[2]*blob[3]
  19. return max_blob
  20. while(True):
  21. img = sensor.snapshot()
  22. faces = img.find_features(face_cascade, threshold=0.5, scale=1.25)
  23. if faces:
  24. face = find_max(faces) #寻找最大的人脸
  25. cx = int(face[0]+face[2]/2) #face[0]是该色块左上角的x坐标,face[2]是该色块的宽
  26. cy = int(face[1]+face[3]/2) #face[1]是该色块左上角的y坐标,face[3]是该色块的长
  27. x_error = cx -img.width()/2 #x方向上的偏移值 往左小于0 往右大于0
  28. h_error = size_threshold-face[2]*face[3] #将当前识别到的色块和面积阈值相减,得到距离偏移值 远距离是正值,近距离是负直
  29. print("x error: ", x_error)
  30. print("h_error",h_error)
  31. img.draw_rectangle(face)
  32. img.draw_cross(cx, cy)
  33. x_output=x_pid.get_pid(x_error,1)
  34. h_output=h_pid.get_pid(h_error,1)
  35. #将h_output和x_output结合,共同影响轮胎转速
  36. car.run(h_output+x_output,h_output-x_output)
  37. #以下是我测试后,合适在桌子上追踪人脸的数值
  38. #car.run(0.4*h_output+0.26*x_output,0.4*h_output-0.26*x_output)
  39. else:
  40. car.run(0,0) #如果没检测到人脸,就停下小车