实验采用树莓派作为控制系统的核心部件,操控L298N电机、摄像头、超声波传感器来进行小车的自动化行驶。小车通过摄像头获取寻迹线位置,经过数字图像处理、PID调节得到与小车自身位置的行驶偏差,经过程序判断改变行驶方向,实现自动驾驶。整体代码思路见流程图
软件代码基于python编写,实现了摄像头寻迹功能。使用了BOARD编号规范。主要流程是首先打开摄像头捕获图像,将图像转化为灰度图,再将灰度图像二值化;通过计算黑色赛道像素中心点的位置,得到其与小车中心的偏移量;再根据偏移量计算PID调整参数,从而调整脉冲的占空比,通过左右四个车轮的前进、后退控制小车的运行速度与方向。 在代码主体部分,首先利用函数cv.VideoCapture(0)开启摄像头,开启while(1)循环读入摄像头捕获的图像。在我们得到3维图像数据之后,需要进行图像处理。我们主要思想就是要通过阈值化把赛道与环境区分开来。但是由于收集到的数据是一个三维数组,计算量较大,为了降低计算量,需要将RGB彩图转换为灰度图,这里用到了cv.cvtColor()函数,输入参数分别是待处理图像和转换的类型。类型采用BGR2GRAY(表示从RGB图像转化成灰度图)。输出参数表示灰度图。灰度化的原理就是将RGB三个通道的数值大致以3:6:1的权重加权,得到灰度值,此时的RGB三个值相同,就可以将三维数组降成一维,大大降低了计算量。之后再用cv.threshold()函数实现二值化。函数的最后一个参数cv.THRESH_BINARY表示分割方式。其具体实现思路是判断灰度图的像素点的像素值是否大于阈值50,大于即赋255(表示白色),小于赋0(标识黑色)。从而将0-255的颜色范围简化为二值0与255,减小了检测黑色像素点的难度。下图为初始图像、灰度化处理图像、二值化处理图像的对比。 我们选取图像第400行像素值来确定方向,找到黑色像素群的中心像素点位置(即为赛道中心位置),计算它与摄像图捕获图像中心点的偏移量,根据偏移量操纵小车的行进方向。当偏移较小时,我们让两侧轮子仍然直行;当偏移量较大时,我们采取将需要偏转的方向反方向侧轮子前进获取较大的转向速度。并且加入了PID调节,让小车偏转过程更加平稳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 import RPi.GPIO as GPIO import cv2 as cv import numpy as np GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) class MyCar(object): def __init__(self): moter_lf_num = 12 moter_lb_num = 16 moter_rf_num = 18 moter_rb_num = 22 pwm = 500 GPIO.setup(moter_lf_num, GPIO.OUT) GPIO.setup(moter_lb_num, GPIO.OUT) GPIO.setup(moter_rf_num, GPIO.OUT) GPIO.setup(moter_rb_num, GPIO.OUT) self.moter_lf = GPIO.PWM(moter_lf_num, pwm) self.moter_lb = GPIO.PWM(moter_lb_num, pwm) self.moter_rf = GPIO.PWM(moter_rf_num, pwm) self.moter_rb = GPIO.PWM(moter_rb_num, pwm) self.moter_lf.start(0) self.moter_lb.start(0) self.moter_rf.start(0) self.moter_rb.start(0) def forward(self, speed): self.moter_lf.ChangeDutyCycle(speed) self.moter_lb.ChangeDutyCycle(0) self.moter_rf.ChangeDutyCycle(speed) self.moter_rb.ChangeDutyCycle(0) def backward(self, speed): self.moter_lf.ChangeDutyCycle(0) self.moter_lb.ChangeDutyCycle(speed) self.moter_rf.ChangeDutyCycle(0) self.moter_rb.ChangeDutyCycle(speed) def left(self, speed): self.moter_lf.ChangeDutyCycle(0) self.moter_lb.ChangeDutyCycle(0) self.moter_rf.ChangeDutyCycle(speed) self.moter_rb.ChangeDutyCycle(0) def right(self, speed): self.moter_lf.ChangeDutyCycle(speed) self.moter_lb.ChangeDutyCycle(0) self.moter_rf.ChangeDutyCycle(0) self.moter_rb.ChangeDutyCycle(0) def brake(self): self.moter_lf.ChangeDutyCycle(0) self.moter_lb.ChangeDutyCycle(0) self.moter_rf.ChangeDutyCycle(0) self.moter_rb.ChangeDutyCycle(0) def MotorRelease(self): self.moter_lf.stop() self.moter_lb.stop() self.moter_rf.stop() self.moter_rb.stop() def sign(x): if x>0: return 1.0 else : return -1.0 error = [0.0] * 3 adjust = [0.0] * 3 kp = 1.2 ki = 0.1 kd = 0.0 if __name__ == '__main__' : try: car = MyCar() V = 50 cap = cv.VideoCapture(0) last_act = 0 LEFT = 1 RIGHT = 2 while (True): ret, frame = cap.read() b, g, r = cv.split(frame) blur = cv.blur(g, (3, 3)) retval, dst = cv.threshold(blur, 50, 255, cv.THRESH_BINARY) dilate = cv.dilate(dst, (5, 5)) erode = cv.erode(dilate, (5, 5)) detect_arr = [400, 450, 300] i = 0 miss = 0 color = erode[detect_arr[i]] black_cnt = np.sum(color == 0) while black_cnt == 0: i = i + 1 if i != 3: color = erode[detect_arr[i]] black_cnt = np.sum(color == 0) else : i = 0 miss = 1 if miss == 1 and last_act == LEFT: print ("undetect_left!" ) car.left(V * 1.6) break ; if miss == 1 and last_act == RIGHT: print ("undetect_right!" ) car.right(V * 1.6) break ; if miss != 1: black_index = np.where(color == 0) center = np.mean(black_index[0]) direction = center - 320 error[0] = error[1] error[1] = error[2] error[2] = direction adjust[0] = adjust[1] adjust[1] = adjust[2] adjust[2] = adjust[1] + kp * (error[2] - error[1]) + ki * error[2] + kd * (error[2] - 2 * error[1] + error[0]) if center <= 220: print ("left" ) car.left(V + adjust[2] / 200) last_act = LEFT elif center > 420: print ("right" ) car.right(V + abs(adjust[2]) / 200) last_act = RIGHT else : print ("forward" ) car.forward(V) except KeyboardInterrupt: print ("Stopped by User" ) car.brake() car.MotorRelease() GPIO.cleanup()