行空板扩展板+视觉PID巡线初体验
本帖最后由 韩亚楠 于 2024-9-29 14:32 编辑前言
非常有幸参与到本次的双路电机扩展板试用活动,收到板子后第一感觉做工还是一如既往的精致,各种功能接口也是非常丰富,那既然是双路电机扩展板,那就优先做一个巡线小车项目来测试一下。
项目目录
[*]硬件组成
[*]双路电机驱动方法
[*]视觉识线
[*]PID巡线
[*]完整程序及效果视频
[*]总结
硬件组成
材料清单:
[*]行空板 * 1
[*]行空板双路电机驱动IO扩展板 * 1
[*]USB摄像头 * 1、3D打印摄像头支架*1
[*]小车底盘( 直流减速电机* 2、轮子 * 2、五金若干)
[*]减速电机尽量选择减速比大一些的,初步搞视觉巡线,速度一开始不能太快,需要能够慢下来且扭矩大一些的。
[*]电源 * 1 (6-12V)
组装示意:ps:由于是使用之前的小车底盘,而且后续要继续测试高速光电巡线,所以图中的光电传感器并未拆除,本项目中所有光电传感器均处于未连接状态。
双路电机驱动
扩展板两路电机管脚控制方法:
M1 方向管脚P5 (为1前进,为0后退) 速度管脚 P8 (数值范围0-1023)M2 方向管脚P6 (为1前进,为0后退) 速度管脚 P16(数值范围0-1023)
自己写了一个两轮驱动函数,把M1和M2的速度范围映射为-255~255,speed函数两个参数分别对应M1速度和M2速度。def numberMap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
def nm(x):
return int(numberMap(x, 0, 255, 0, 1023))
def speed(x, y):
if x>0:
p_p5_out.write_digital(1)
else:
p_p5_out.write_digital(0)
if y>0:
p_p6_out.write_digital(1)
else:
p_p6_out.write_digital(0)
x = nm(abs(x))
y = nm(abs(y))
p_p8_pwm.write_analog(x)
p_p16_pwm.write_analog(y)视觉识线
视觉巡线的话还是首推opencv,使用方便。
大致思路:
1.首先对摄像头获取到的图片转换为HSV色彩空间,这样有助于找寻红色,然后根据找到的颜色来创建掩码(0(黑色)和255(白色)组成),在进行膨胀和腐蚀操作,改善图像特征并消除噪声,我们能得到如下图效果。2.取掩码中 第320行和380行的像素点(下图中绿色线代表第320行,黄色线代表380行),统计并计算出画面中红线中心点的位置坐标(下图中红色十字星位置)。
代码:#获取线中心位置函数
def process_frame(img):
global lp1
global lp2
global lcs
#转换为hsv色彩空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义红色的HSV范围
lower_red = np.array()
upper_red = np.array()
# 创建红色掩码
mask = cv2.inRange(hsv, lower_red, upper_red)
# 形态学操作:膨胀和腐蚀
kernel = np.ones((5, 5), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=4)
mask = cv2.erode(mask, kernel, iterations=4)
colorp1=mask
colorp2=mask
try:
lineccp1 = np.sum(colorp1==lcs)
lineccp2 = np.sum(colorp2==lcs)
lineip1 = np.where(colorp1==lcs)
lineip2 = np.where(colorp2==lcs)
if lineccp1 == 0:
lineccp1 = 1
if lineccp2 == 0:
lineccp2 = 1
leftp1 = lineip1
rightp1 = lineip1
centerp1 = int((leftp1+rightp1)/2)
leftp2 = lineip2
rightp2 = lineip2
centerp2 = int((leftp2+rightp2)/2)
center = int((centerp1+centerp2)/2)
except:
center = None
return mask,center
PID巡线拿到准确的线中心坐标后,就可以代入PID公式计算控制信号了,关于PID原理部分这里不再赘述,然后就可以调整Kp和Kd来让小车达到理想的巡线效果。 ①kp是PID控制器中的比例调节系数,它决定了系统对误差的响应速度。kp越大,系统对误差的响应越敏感,调整速度越快,但是过大的kp会造成小车疯狂摆头,就是过冲、震荡。②kd是PID控制器中的微分调节系数,它决定了系统对误差变化率的响应。kd越大,系统对误差变化的抑制作用越强,有助于减少过冲和震荡。③在实际应用中,通常通过“试凑法”来调整kp、kd的值。就是从小变大逐步调整每个参数,并观察小车巡线的响应状态,直到找到最佳组合。④除了以上因素外,小车减速电机的一致性与摄像头的角度也是影响效果的重要因素,这个因车而异,大家自行调整。
代码def pid(x):
global kp,kd,old_err
err = x-320
offset = int(err*kp+(err-old_err)*kd)
old_err = err
return offset
效果展示
https://www.bilibili.com/video/BV1yyxre5EPU/?spm_id_from=333.999.0.0&vd_source=360d33ec159ae1656997a91953f7a0e6
完整代码
#-*- coding: UTF-8 -*-
# MindPlus
# Python
import cv2
import numpy as np
from pinpong.board import Board
from pinpong.board import NeoPixel
from pinpong.board import Board,Pin
from pinpong.extension.unihiker import *
def numberMap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
def nm(x):
return int(numberMap(x, 0, 255, 0, 1023))
def speed(x, y):
if x>0:
p_p5_out.write_digital(1)
else:
p_p5_out.write_digital(0)
if y>0:
p_p6_out.write_digital(1)
else:
p_p6_out.write_digital(0)
x = nm(abs(x))
y = nm(abs(y))
p_p8_pwm.write_analog(x)
p_p16_pwm.write_analog(y)
#获取线中心位置函数
def process_frame(img):
global lp1
global lp2
global lcs
#转换为hsv色彩空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义红色的HSV范围
lower_red = np.array()
upper_red = np.array()
# 创建红色掩码
mask = cv2.inRange(hsv, lower_red, upper_red)
# 形态学操作:膨胀和腐蚀
kernel = np.ones((5, 5), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=4)
mask = cv2.erode(mask, kernel, iterations=4)
colorp1=mask
colorp2=mask
try:
lineccp1 = np.sum(colorp1==lcs)
lineccp2 = np.sum(colorp2==lcs)
lineip1 = np.where(colorp1==lcs)
lineip2 = np.where(colorp2==lcs)
if lineccp1 == 0:
lineccp1 = 1
if lineccp2 == 0:
lineccp2 = 1
leftp1 = lineip1
rightp1 = lineip1
centerp1 = int((leftp1+rightp1)/2)
leftp2 = lineip2
rightp2 = lineip2
centerp2 = int((leftp2+rightp2)/2)
center = int((centerp1+centerp2)/2)
except:
center = None
return mask,center
def pid(x):
global kp,kd,old_err
err = x-320
offset = int(err*kp+(err-old_err)*kd)
old_err = err
return offset
Board().begin()
p_p5_out=Pin(Pin.P5, Pin.OUT)
p_p8_pwm=Pin(Pin.P8, Pin.PWM)
p_p6_out=Pin(Pin.P6, Pin.OUT)
p_p16_pwm=Pin(Pin.P16, Pin.PWM)
cv2.namedWindow("n", cv2.WINDOW_NORMAL)
cv2.moveWindow("n", 0, 0)
cv2.resizeWindow("n", 240, 320)
vd = cv2.VideoCapture()
vd.set(cv2.CAP_PROP_FRAME_WIDTH,240)
vd.set(cv2.CAP_PROP_FRAME_HEIGHT,320)
vd.open(0)
if vd.isOpened():
lp1 = 320#检测线1
lp2 = 380#检测线2
lcs = 255#检测点为白色=255 黑色=0
bs = 60 #基础速度
kp = 0.010
kd = 0.040
old_err = 0
while True:
ret, cvi = vd.read()
# 获取图像 h = 480w = 640
h,w,c = cvi.shape
res,center=process_frame(cvi)
cv2.line(cvi, (0,lp1), (640,lp1), (0,255,0), 3, cv2.FILLED)
cv2.drawMarker(cvi, (center, int(lp1+((lp2-lp1)/2))), (0,0,255), cv2.MARKER_CROSS, 20, 5, cv2.FILLED)
cv2.line(cvi, (0,lp2), (640,lp2), (0,255,255), 3, cv2.FILLED)
cv2.imshow("n", cvi)
f = 0
if center!= None:
offset = pid(center)
if center>320 :
f = 1
else:
f = 2
ls = bs + offset*10
rs = bs - offset*10
else:
if f==1:
for i in range(10):
ls,rs = bs,-1*bs
time.sleep(0.1)
elif f==2:
for i in range(10):
ls,rs= -1*bs,bs
time.sleep(0.1)
else:
ls,rs= bs,bs
speed(ls,rs)#设置两轮速度
if cv2.waitKey(20) & 0xff== 97:
speed(0,0)
break
print(ls,rs,center)
vd.release()
cv2.destroyAllWindows()
总结
这块扩展版在使用过程中感觉是非常好用的,后续还会去用它做一些智能识别的项目,另外如果这个板子能扩展出四路电机控制的话,做一个麦轮小车应该会更好玩。
写得很棒 支持一下{:6_209:} 牛逼牛逼,向大佬学习{:6_215:}
页:
[1]