目录
试错
试错1:形态学处理
一开始用的形态学处理,自行改变阈值,调试之后,进行处理,发现效果不是太好,于是改成了HSV色彩空间。
试错2:HSV色彩空间
之前没注意到,HSV色彩空间很难识别白色:
HSV:
不难看出,如果寻白色线的话,HSV色彩空间不是一个很好的选择,下面引入HSL色彩空间:
HSL:
所以,如果是巡白色的话,建议用HSL色彩空间。
注意:巡线小车的摄像头不能太低,如果太低了,可能让小车自己的影子会阻碍光线。
hsv中的效果:
hsl中的效果:
可以看出,已经能大致找到白线了。
基础理论
1、HSV与HSL色彩空间
HSV:
不难看出,如果寻白色线的话,HSV色彩空间不是一个很好的选择,下面引入HSL色彩空间:
HSL:
所以,如果是巡白色的话,建议用HSL色彩空间。
2、PID调节
个人理解:
P:拉力
I:推动力
D:阻力
一、OpenCV图像处理
1、在HSL色彩空间下得到二值图
# 在HSV色彩空间下得到二值图
def Get_HSV(image):
# 1 get trackbar's value
hmin = cv2.getTrackbarPos('hmin', 'h_binary')
hmax = cv2.getTrackbarPos('hmax', 'h_binary')
smin = cv2.getTrackbarPos('smin', 's_binary')
smax = cv2.getTrackbarPos('smax', 's_binary')
lmin = cv2.getTrackbarPos('lmin', 'l_binary')
lmax = cv2.getTrackbarPos('lmax', 'l_binary')
# 2 to HSV
hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
cv2.imshow('hls', hls)
h, l, s = cv2.split(hls)
# 3 set threshold (binary image)
# if value in (min, max):white; otherwise:black
h_binary = cv2.inRange(np.array(h), np.array(hmin), np.array(hmax))
s_binary = cv2.inRange(np.array(s), np.array(smin), np.array(smax))
l_binary = cv2.inRange(np.array(l), np.array(lmin), np.array(lmax))
# 4 get binary(对H、S、V三个通道分别与操作)
binary = 255 - cv2.bitwise_and(h_binary, cv2.bitwise_and(s_binary, l_binary))
# 5 Show
cv2.imshow('h_binary', h_binary)
cv2.imshow('s_binary', s_binary)
cv2.imshow('l_binary', l_binary)
cv2.imshow('binary', binary)
return binary
2、 对二值图形态学处理
# 图像处理
def Image_Processing():
global frame, binary
# Capture the frames
ret, frame = camera.read()
# to binary
binary = Get_HSV(frame)
blur = cv2.GaussianBlur(binary, (5, 5), 0)
cv2.imshow('blur', blur)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (35, 35))
Open = cv2.morphologyEx(blur, cv2.MORPH_OPEN, kernel)
cv2.imshow('Open', Open)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
Erode = cv2.morphologyEx(Open, cv2.MORPH_ERODE, kernel)
cv2.imshow('Erode', Erode)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
Dilate = cv2.morphologyEx(Erode, cv2.MORPH_DILATE, kernel)
cv2.imshow('Dilate', Dilate)
binary = Erode#Dilate
3、找出线的轮廓和中心点坐标
# 找线
def Find_Line():
global x, y, image
# 1 找出所有轮廓
bin2, contours, hierarchy = cv2.findContours(binary, 1, cv2.CHAIN_APPROX_NONE)
# 2 找出最大轮廓
if len(contours) > 0:
# 最大轮廓
c = max(contours, key=cv2.contourArea)
M = cv2.moments(c)
# 中心点坐标
x = int(M['m10'] / M['m00'])
y = int(M['m01'] / M['m00'])
#print(x, y)
# 显示
image = frame.copy()
# 标出中心位置
cv2.line(image, (x, 0), (x, 720), (0, 0, 255), 1)
cv2.line(image, (0, y), (1280, y), (0, 0, 255), 1)
# 画出轮廓
cv2.drawContours(image, contours, -1, (128, 0, 128), 2)
cv2.imshow("image", image)
else:
print("not found the line")
(x,y) = (0, 0)
二、PID
def Pid():
global turn_speed, x, y, speed
global error, last_error, pre_error, out_pid
error = abs(x - width / 2)
out_pid = int(proportion * error - integral * last_error + derivative * pre_error)
turn_speed = out_pid
# 保存本次误差,以便下一次运算
pre_error = last_error
last_error = error
# 限值
if (turn_speed < 30):
turn_speed = 30
elif (turn_speed > 100):
turn_speed = 100
if (speed < 0):
speed = 0
elif (speed > 100):
speed = 100
print(error, out_pid, turn_speed, (x, y))
三、运动控制
# 巡线
def Follow_Line():
global turn_speed, x, y,speed, back_speed
'''if(x < width / 2 and y>2*height/3):
Left(turn_speed)
elif(x>3*width/2 and y>2*height/3):
Right(turn_speed)'''
if(0<x<width/4):
Left(turn_speed)
print("turn left")
elif(3*width/4<x<width):
Right(turn_speed)
print("turn right")
#直角拐弯
elif(y>3*height/4):
if(x<width/2):
Left(turn_speed*2)
print("turn left")
elif(x>=width/2):
Right(turn_speed*2)
print("turn right")
elif(x>=width/4 and x<=3*width/4):
Forward(speed)
elif(x==0 and y==0):
Back(back_speed)
总代码
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import numpy as np
import cv2
import Adafruit_PCA9685
import RPi.GPIO as GPIO
import time
l_motor = 18
left_Forward = 22
left_back = 27
r_motor = 23
right_Forward = 25
right_back = 24
pwm_servo = Adafruit_PCA9685.PCA9685()
width, height = 160, 120
camera = cv2.VideoCapture(0)
camera.set(3, width)
camera.set(4, height)
# pid
error = 0 # 当前误差e[k]
last_error = 0 # 上一次误差e[k-1]
pre_error = 0 # 上上次误差e[k-2]
proportion = 1 # 比例系数3 0.2
integral = 0.5 # 积分系数1.2
derivative = 0 # 微分系数1.2
stop_flag = 1
control_flag = 1
turn_speed = 30
speed = 30
back_speed = 30
def Motor_Init():
global L_Motor, R_Motor
L_Motor = GPIO.PWM(l_motor, 100)
R_Motor = GPIO.PWM(r_motor, 100)
L_Motor.start(0)
R_Motor.start(0)
def Direction_Init():
GPIO.setup(left_back, GPIO.OUT)
GPIO.setup(left_Forward, GPIO.OUT)
GPIO.setup(l_motor, GPIO.OUT)
GPIO.setup(right_Forward, GPIO.OUT)
GPIO.setup(right_back, GPIO.OUT)
GPIO.setup(r_motor, GPIO.OUT)
def set_servo_angle(channel, angle):
angle = 4096 * ((angle * 11) + 500) / 20000
pwm_servo.set_pwm_freq(50) # frequency==50Hz (servo)
pwm_servo.set_pwm(channel, 0, int(angle))
def TrackBar_Init():
# 1 create windows
cv2.namedWindow('h_binary')
cv2.namedWindow('s_binary')
cv2.namedWindow('l_binary')
# 2 Create Trackbar
cv2.createTrackbar('hmin', 'h_binary', 0, 179, call_back)
cv2.createTrackbar('hmax', 'h_binary', 110, 179, call_back)
cv2.createTrackbar('smin', 's_binary', 0, 255, call_back)
cv2.createTrackbar('smax', 's_binary', 51, 255, call_back) # 51
cv2.createTrackbar('lmin', 'l_binary', 0, 255, call_back)
cv2.createTrackbar('lmax', 'l_binary', 255, 255, call_back)
'''cv2.namedWindow('binary')
cv2.createTrackbar('thresh', 'binary', 154, 255, call_back) '''
# 创建滑动条 滑动条值名称 窗口名称 滑动条值 滑动条阈值 回调函数
def Init():
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
Direction_Init()
Motor_Init()
TrackBar_Init()
def Forward(turn_speed):
L_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(left_Forward, 1) # left_Forward
GPIO.output(left_back, 0) # left_back
R_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(right_Forward, 1) # right_Forward
GPIO.output(right_back, 0) # right_back
def Back(turn_speed):
L_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(left_Forward, 0) # left_Forward
GPIO.output(left_back, 1) # left_back
R_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(right_Forward, 0) # right_Forward
GPIO.output(right_back, 1) # right_back
def Left(turn_speed):
L_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(left_Forward, 0) # left_Forward
GPIO.output(left_back, 1) # left_back
R_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(right_Forward, 1) # right_Forward
GPIO.output(right_back, 0) # right_back
def Right(turn_speed):
L_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(left_Forward, 1) # left_Forward
GPIO.output(left_back, 0) # left_back
R_Motor.ChangeDutyCycle(turn_speed)
GPIO.output(right_Forward, 0) # right_Forward
GPIO.output(right_back, 1) # right_back
def Stop():
L_Motor.ChangeDutyCycle(0)
GPIO.output(left_Forward, 0) # left_Forward
GPIO.output(left_back, 0) # left_back
R_Motor.ChangeDutyCycle(0)
GPIO.output(right_Forward, 0) # right_Forward
GPIO.output(right_back, 0) # right_back
# 回调函数
def call_back(*arg):
pass
# 在HSV色彩空间下得到二值图
def Get_HSV(image):
# 1 get trackbar's value
hmin = cv2.getTrackbarPos('hmin', 'h_binary')
hmax = cv2.getTrackbarPos('hmax', 'h_binary')
smin = cv2.getTrackbarPos('smin', 's_binary')
smax = cv2.getTrackbarPos('smax', 's_binary')
lmin = cv2.getTrackbarPos('lmin', 'l_binary')
lmax = cv2.getTrackbarPos('lmax', 'l_binary')
# 2 to HSV
hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
cv2.imshow('hls', hls)
h, l, s = cv2.split(hls)
# 3 set threshold (binary image)
# if value in (min, max):white; otherwise:black
h_binary = cv2.inRange(np.array(h), np.array(hmin), np.array(hmax))
s_binary = cv2.inRange(np.array(s), np.array(smin), np.array(smax))
l_binary = cv2.inRange(np.array(l), np.array(lmin), np.array(lmax))
# 4 get binary(对H、S、V三个通道分别与操作)
binary = 255 - cv2.bitwise_and(h_binary, cv2.bitwise_and(s_binary, l_binary))
# 5 Show
cv2.imshow('h_binary', h_binary)
cv2.imshow('s_binary', s_binary)
cv2.imshow('l_binary', l_binary)
cv2.imshow('binary', binary)
return binary
# 手动控制小车(上下左右,案件事件判断)
# 控制方式:w、s、a、d分别表示:上、下、左、右
def Key_Control(keyboard):
global stop_flag, control_flag
if keyboard == ord("w"):
Forward(50)
time.sleep(0.1)
Stop()
elif keyboard == ord("s"):
Back(50)
time.sleep(0.1)
Stop()
elif keyboard == ord("a"):
Left(50)
time.sleep(0.1)
Stop()
elif keyboard == ord("d"):
Right(50)
time.sleep(0.1)
Stop()
# 图像处理
def Image_Processing():
global frame, binary
# Capture the frames
ret, frame = camera.read()
# to binary
binary = Get_HSV(frame)
blur = cv2.GaussianBlur(binary, (5, 5), 0)
cv2.imshow('blur', blur)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (35, 35))
Open = cv2.morphologyEx(blur, cv2.MORPH_OPEN, kernel)
cv2.imshow('Open', Open)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
Erode = cv2.morphologyEx(Open, cv2.MORPH_ERODE, kernel)
cv2.imshow('Erode', Erode)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
Dilate = cv2.morphologyEx(Erode, cv2.MORPH_DILATE, kernel)
cv2.imshow('Dilate', Dilate)
binary = Erode # Dilate
# 找线
def Find_Line():
global x, y, image
# 1 找出所有轮廓
bin2, contours, hierarchy = cv2.findContours(binary, 1, cv2.CHAIN_APPROX_NONE)
# 2 找出最大轮廓
if len(contours) > 0:
# 最大轮廓
c = max(contours, key=cv2.contourArea)
M = cv2.moments(c)
# 中心点坐标
x = int(M['m10'] / M['m00'])
y = int(M['m01'] / M['m00'])
# print(x, y)
# 显示
image = frame.copy()
# 标出中心位置
cv2.line(image, (x, 0), (x, 720), (0, 0, 255), 1)
cv2.line(image, (0, y), (1280, y), (0, 0, 255), 1)
# 画出轮廓
cv2.drawContours(image, contours, -1, (128, 0, 128), 2)
cv2.imshow("image", image)
else:
print("not found the line")
(x, y) = (0, 0)
def Pid():
global turn_speed, x, y, speed
global error, last_error, pre_error, out_pid
error = abs(x - width / 2)
out_pid = int(proportion * error - integral * last_error + derivative * pre_error)
turn_speed = out_pid
# 保存本次误差,以便下一次运算
pre_error = last_error
last_error = error
# 限值
if (turn_speed < 30):
turn_speed = 30
elif (turn_speed > 100):
turn_speed = 100
if (speed < 0):
speed = 0
elif (speed > 100):
speed = 100
print(error, out_pid, turn_speed, (x, y))
# 巡线
def Follow_Line():
global turn_speed, x, y, speed, back_speed
'''if(x < width / 2 and y>2*height/3):
Left(turn_speed)
elif(x>3*width/2 and y>2*height/3):
Right(turn_speed)'''
if (0 < x < width / 4):
Left(turn_speed)
print("turn left")
elif (3 * width / 4 < x < width):
Right(turn_speed)
print("turn right")
# 直角拐弯
elif (y > 3 * height / 4):
if (x < width / 2):
Left(turn_speed * 2)
print("turn left")
elif (x >= width / 2):
Right(turn_speed * 2)
print("turn right")
elif (x >= width / 4 and x <= 3 * width / 4):
Forward(speed)
elif (x == 0 and y == 0):
Back(back_speed)
def Control():
global control_flag, speed, proportion, integral
keyboard = cv2.waitKey(1)
# 加速减速
if (keyboard == ord('k')):
speed += 5
elif (keyboard == ord('l')):
speed -= 5
print(speed)
if keyboard == ord("n"):
integral += 0.01
elif keyboard == ord("m"):
integral -= 0.01
print(integral)
if (control_flag == -1):
Follow_Line()
if keyboard == 32:
control_flag *= -1
Stop()
else:
Key_Control(keyboard)
if keyboard == 32:
control_flag *= -1
Stop()
print(control_flag)
if __name__ == '__main__':
Init()
set_servo_angle(4, 140) # top servo lengthwise
# 0:back 180:front
set_servo_angle(5, 90) # bottom servo crosswise
# 0:left 180:right
while True:
Image_Processing()
Find_Line()
Pid()
Control()
if cv2.waitKey(1) == ord('q'):
cv2.destroyAllWindows()
break
其实一开始主要是想玩机器视觉,小车的运动控制研究的不算精细,PID研究的也不深(甚至可能有误)。
有很多是自己的想法,有错误欢迎指正,有建议也欢迎交流,谢谢。