408浏览
查看: 408|回复: 1

[M10项目] 行空板扩展板+视觉PID巡线初体验

[复制链接]
本帖最后由 韩亚楠 于 2024-9-29 14:32 编辑

前言

非常有幸参与到本次的双路电机扩展板试用活动,收到板子后第一感觉做工还是一如既往的精致,各种功能接口也是非常丰富,那既然是双路电机扩展板,那就优先做一个巡线小车项目来测试一下。
行空板扩展板+视觉PID巡线初体验图1


项目目录
  • 硬件组成
  • 双路电机驱动方法
  • 视觉识线
  • PID巡线
  • 完整程序及效果视频
  • 总结

硬件组成

材料清单:


  • 行空板 * 1
  • 行空板双路电机驱动IO扩展板 * 1
  • USB摄像头 * 1、3D打印摄像头支架*1
  • 小车底盘( 直流减速电机* 2、轮子 * 2、五金若干)
  • 减速电机尽量选择减速比大一些的,初步搞视觉巡线,速度一开始不能太快,需要能够慢下来且扭矩大一些的。
  • 电源 * 1 (6-12V)


组装示意:ps:由于是使用之前的小车底盘,而且后续要继续测试高速光电巡线,所以图中的光电传感器并未拆除,本项目中所有光电传感器均处于未连接状态。

行空板扩展板+视觉PID巡线初体验图4行空板扩展板+视觉PID巡线初体验图5
行空板扩展板+视觉PID巡线初体验图3行空板扩展板+视觉PID巡线初体验图2
双路电机驱动

扩展板两路电机管脚控制方法:

M1 方向管脚  P5 (为1前进,为0后退)     速度管脚 P8    (数值范围0-1023)
M2 方向管脚  P6 (为1前进,为0后退)     速度管脚 P16  (数值范围0-1023)

自己写了一个两轮驱动函数,把M1和M2的速度范围映射为-255~255,speed函数两个参数分别对应M1速度和M2速度。
  1. def numberMap(x, in_min, in_max, out_min, out_max):
  2.   return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
  3. def nm(x):
  4.   return int(numberMap(x, 0, 255, 0, 1023))
  5. def speed(x, y):
  6.   if x>0:
  7.       p_p5_out.write_digital(1)
  8.   else:
  9.       p_p5_out.write_digital(0)
  10.   if y>0:
  11.       p_p6_out.write_digital(1)
  12.   else:
  13.       p_p6_out.write_digital(0)
  14.   x = nm(abs(x))
  15.   y = nm(abs(y))
  16.   p_p8_pwm.write_analog(x)
  17.   p_p16_pwm.write_analog(y)
复制代码
视觉识线

视觉巡线的话还是首推opencv,使用方便。

大致思路:

1.首先对摄像头获取到的图片转换为HSV色彩空间,这样有助于找寻红色,然后根据找到的颜色来创建掩码(0(黑色)和255(白色)组成),在进行膨胀和腐蚀操作,改善图像特征并消除噪声,我们能得到如下图效果。行空板扩展板+视觉PID巡线初体验图6行空板扩展板+视觉PID巡线初体验图7
2.取掩码中 第320行和380行的像素点(下图中绿色线代表第320行,黄色线代表380行),统计并计算出画面中红线中心点的位置坐标(下图中红色十字星位置)。
行空板扩展板+视觉PID巡线初体验图8

代码:
  1. #获取线中心位置函数
  2. def process_frame(img):
  3.     global lp1
  4.     global lp2
  5.     global lcs
  6.     #转换为hsv色彩空间
  7.     hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
  8.     # 定义红色的HSV范围
  9.     lower_red = np.array([0, 100, 100])
  10.     upper_red = np.array([10, 255, 255])
  11.     # 创建红色掩码
  12.     mask = cv2.inRange(hsv, lower_red, upper_red)
  13.     # 形态学操作:膨胀和腐蚀
  14.     kernel = np.ones((5, 5), np.uint8)
  15.     mask = cv2.dilate(mask, kernel, iterations=4)
  16.     mask = cv2.erode(mask, kernel, iterations=4)
  17.     colorp1=mask[lp1]
  18.     colorp2=mask[lp2]
  19.     try:
  20.         lineccp1 = np.sum(colorp1==lcs)
  21.         lineccp2 = np.sum(colorp2==lcs)
  22.         lineip1 = np.where(colorp1==lcs)
  23.         lineip2 = np.where(colorp2==lcs)
  24.         if lineccp1 == 0:
  25.             lineccp1 = 1
  26.         if lineccp2 == 0:
  27.             lineccp2 = 1
  28.         leftp1 = lineip1[0][lineccp1-1]
  29.         rightp1 = lineip1[0][0]
  30.         centerp1 = int((leftp1+rightp1)/2)
  31.         leftp2 = lineip2[0][lineccp2-1]
  32.         rightp2 = lineip2[0][0]
  33.         centerp2 = int((leftp2+rightp2)/2)
  34.         center = int((centerp1+centerp2)/2)
  35.     except:
  36.         center = None
  37.     return mask,center
复制代码

PID巡线
拿到准确的线中心坐标后,就可以代入PID公式计算控制信号了,关于PID原理部分这里不再赘述,然后就可以调整Kp和Kd来让小车达到理想的巡线效果。
   ①kp是PID控制器中的比例调节系数,它决定了系统对误差的响应速度。kp越大,系统对误差的响应越敏感,调整速度越快,但是过大的kp会造成小车疯狂摆头,就是过冲、震荡。
  ②kd是PID控制器中的微分调节系数,它决定了系统对误差变化率的响应。kd越大,系统对误差变化的抑制作用越强,有助于减少过冲和震荡。
  ③在实际应用中,通常通过“试凑法”来调整kp、kd的值。就是从小变大逐步调整每个参数,并观察小车巡线的响应状态,直到找到最佳组合。
  ④除了以上因素外,小车减速电机的一致性与摄像头的角度也是影响效果的重要因素,这个因车而异,大家自行调整。

代码
  1. def pid(x):
  2.     global kp,kd,old_err
  3.     err = x-320
  4.     offset = int(err*kp+(err-old_err)*kd)
  5.     old_err = err
  6.     return offset
复制代码

效果展示


完整代码

  1. #  -*- coding: UTF-8 -*-
  2. # MindPlus
  3. # Python
  4. import cv2
  5. import numpy as np
  6. from pinpong.board import Board
  7. from pinpong.board import NeoPixel
  8. from pinpong.board import Board,Pin
  9. from pinpong.extension.unihiker import *
  10. def numberMap(x, in_min, in_max, out_min, out_max):
  11.     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
  12. def nm(x):
  13.     return int(numberMap(x, 0, 255, 0, 1023))
  14. def speed(x, y):
  15.     if x>0:
  16.         p_p5_out.write_digital(1)
  17.     else:
  18.         p_p5_out.write_digital(0)
  19.     if y>0:
  20.         p_p6_out.write_digital(1)
  21.     else:
  22.         p_p6_out.write_digital(0)
  23.     x = nm(abs(x))
  24.     y = nm(abs(y))
  25.     p_p8_pwm.write_analog(x)
  26.     p_p16_pwm.write_analog(y)
  27. #获取线中心位置函数
  28. def process_frame(img):
  29.     global lp1
  30.     global lp2
  31.     global lcs
  32.     #转换为hsv色彩空间
  33.     hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
  34.     # 定义红色的HSV范围
  35.     lower_red = np.array([0, 100, 100])
  36.     upper_red = np.array([10, 255, 255])
  37.     # 创建红色掩码
  38.     mask = cv2.inRange(hsv, lower_red, upper_red)
  39.     # 形态学操作:膨胀和腐蚀
  40.     kernel = np.ones((5, 5), np.uint8)
  41.     mask = cv2.dilate(mask, kernel, iterations=4)
  42.     mask = cv2.erode(mask, kernel, iterations=4)
  43.     colorp1=mask[lp1]
  44.     colorp2=mask[lp2]
  45.     try:
  46.         lineccp1 = np.sum(colorp1==lcs)
  47.         lineccp2 = np.sum(colorp2==lcs)
  48.         lineip1 = np.where(colorp1==lcs)
  49.         lineip2 = np.where(colorp2==lcs)
  50.         if lineccp1 == 0:
  51.             lineccp1 = 1
  52.         if lineccp2 == 0:
  53.             lineccp2 = 1
  54.         leftp1 = lineip1[0][lineccp1-1]
  55.         rightp1 = lineip1[0][0]
  56.         centerp1 = int((leftp1+rightp1)/2)
  57.         leftp2 = lineip2[0][lineccp2-1]
  58.         rightp2 = lineip2[0][0]
  59.         centerp2 = int((leftp2+rightp2)/2)
  60.         center = int((centerp1+centerp2)/2)
  61.     except:
  62.         center = None
  63.     return mask,center
  64. def pid(x):
  65.     global kp,kd,old_err
  66.     err = x-320
  67.     offset = int(err*kp+(err-old_err)*kd)
  68.     old_err = err
  69.     return offset
  70. Board().begin()
  71. p_p5_out=Pin(Pin.P5, Pin.OUT)
  72. p_p8_pwm=Pin(Pin.P8, Pin.PWM)
  73. p_p6_out=Pin(Pin.P6, Pin.OUT)
  74. p_p16_pwm=Pin(Pin.P16, Pin.PWM)
  75. cv2.namedWindow("n", cv2.WINDOW_NORMAL)
  76. cv2.moveWindow("n", 0, 0)
  77. cv2.resizeWindow("n", 240, 320)
  78. vd = cv2.VideoCapture()
  79. vd.set(cv2.CAP_PROP_FRAME_WIDTH,240)
  80. vd.set(cv2.CAP_PROP_FRAME_HEIGHT,320)
  81. vd.open(0)
  82. if vd.isOpened():
  83.     lp1 = 320  #检测线1
  84.     lp2 = 380  #检测线2
  85.     lcs = 255  #检测点为白色=255 黑色=0
  86.     bs = 60    #基础速度
  87.     kp = 0.010
  88.     kd = 0.040
  89.     old_err = 0
  90.     while True:
  91.         ret, cvi = vd.read()
  92.         # 获取图像 h = 480  w = 640
  93.         h,w,c = cvi.shape
  94.         res,center=process_frame(cvi)
  95.         cv2.line(cvi, (0,lp1), (640,lp1), (0,255,0), 3, cv2.FILLED)
  96.         cv2.drawMarker(cvi, (center, int(lp1+((lp2-lp1)/2))), (0,0,255), cv2.MARKER_CROSS, 20, 5, cv2.FILLED)
  97.         cv2.line(cvi, (0,lp2), (640,lp2), (0,255,255), 3, cv2.FILLED)
  98.         cv2.imshow("n", cvi)
  99.         f = 0
  100.         if center!= None:
  101.             offset = pid(center)
  102.             if center>320 :
  103.                 f = 1
  104.             else:
  105.                 f = 2
  106.             ls = bs + offset*10
  107.             rs = bs - offset*10
  108.         else:
  109.             if f==1:
  110.                 for i in range(10):
  111.                     ls,rs = bs,-1*bs
  112.                     time.sleep(0.1)
  113.             elif f==2:
  114.                 for i in range(10):
  115.                     ls,rs= -1*bs,bs
  116.                     time.sleep(0.1)
  117.             else:
  118.                 ls,rs= bs,bs
  119.         speed(ls,rs)#设置两轮速度
  120.         if cv2.waitKey(20) & 0xff== 97:
  121.             speed(0,0)
  122.             break
  123.         print(ls,rs,center)
  124. vd.release()
  125. cv2.destroyAllWindows()
复制代码

总结

     这块扩展版在使用过程中感觉是非常好用的,后续还会去用它做一些智能识别的项目,另外如果这个板子能扩展出四路电机控制的话,做一个麦轮小车应该会更好玩。


ASH腻  管理员

发表于 2024-10-9 14:42:02

写得很棒 支持一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
关于楼主
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail