树莓派视觉小车 -- 小球追踪(颜色追踪)(OpenCV色彩空间HSV)
目錄
效果展示?
基礎理論(HSV)
為什么用HSV空間而不是RGB空間?
HSV
1、Hue(色相)
2、Value(明度)
3、Saturation(飽和度)
一、初始化
滑動條初始化
1、創建回調函數
2、窗口設置(名稱)?
3、滑動條設置
代碼
二、運動函數
三、舵機控制
四、在HSV空間下獲取二值圖
1、獲取滑動條的值
2、轉HSV(三通道:H、S、V)
3、轉二值圖(單通道閾值處理)
4、結合HSV三個通道,得到最終二值圖
代碼
五、圖像處理(總)
1、打開攝像頭
2、獲取HSV色彩空間得到的二值圖
3、高斯濾波
4、開運算去噪
5、閉運算
6、霍夫圓檢測
原理
API
?6-1、霍夫圓檢測
6-2、獲取圓心和半徑坐標
6-3、畫圓
代碼
六、運動控制
總代碼
效果展示?
?
?
基礎理論(HSV)
為什么用HSV空間而不是RGB空間?
因為RGB通道并不能很好地反映出物體具體的顏色信息。
而HSV空間能夠非常直觀的表達色彩的明暗、色調、以及鮮艷程度,方便進行顏色之間的對比。
(RGB受光線影響很大,所以采取HSV)
這里用HSV的目的:得到合適的二值圖。
HSV
?
?
Hue(H):色調、色相(具體的顏色)
Saturation(S):飽和度、色彩純凈度
Value(V):明度
Hue范圍是[0,179],飽和范圍是[0,255],值范圍是[0,255]
(寫代碼的時候,犯了蠢錯誤:把3個都設成了179,發現死活調不對)
1、Hue(色相)
Hue:色相(具體的顏色)
2、Value(明度)
明度:色彩的明亮程度,單通道亮度(并不等同于整體發光量)。
(明度越高越白,越低越黑,一般提高明度會同時提高R、G、B三通道的數值)
3、Saturation(飽和度)
Saturation:飽和度、色彩純度(越低越灰,越高越純)??。
(一般調高飽和度會降低RGB中相對較低的數值,凸顯主要顏色的純度。 )
B站視頻講解:
短動畫慢語速1分鐘講清影視調色中色彩形成原理基礎——RGB與HSV
一、初始化
滑動條初始化
這次的初始化,除了電機、舵機、窗口等等的初始化,還有滑動條的初始設置。?
在創建滑動條之前:
1、需要設置窗口名稱,不然窗口都沒有,自然也就無法設置滑動條了。
2、創建回調函數。
1、創建回調函數
def nothing(*arg):pass
2、窗口設置(名稱)?
def Trackbar_Init():# 1 create windowscv2.namedWindow('h_binary')cv2.namedWindow('s_binary')cv2.namedWindow('v_binary')
3、滑動條設置
# 2 Create Trackbarcv2.createTrackbar('hmin', 'h_binary', 6, 179, nothing) cv2.createTrackbar('hmax', 'h_binary', 26, 179, nothing) cv2.createTrackbar('smin', 's_binary', 110, 255, nothing)cv2.createTrackbar('smax', 's_binary', 255, 255, nothing)cv2.createTrackbar('vmin', 'v_binary', 140, 255, nothing)cv2.createTrackbar('vmax', 'v_binary', 255, 255, nothing)# 創建滑動條 滑動條值名稱 窗口名稱 滑動條值 滑動條閾值 回調函數
代碼
def Motor_Init():global L_Motor, R_MotorL_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_front,GPIO.OUT)GPIO.setup(l_motor,GPIO.OUT)GPIO.setup(right_front,GPIO.OUT)GPIO.setup(right_back,GPIO.OUT)GPIO.setup(r_motor,GPIO.OUT)def Servo_Init():global pwm_servopwm_servo=Adafruit_PCA9685.PCA9685()def Trackbar_Init():# 1 create windowscv2.namedWindow('h_binary')cv2.namedWindow('s_binary')cv2.namedWindow('v_binary')# 2 Create Trackbarcv2.createTrackbar('hmin', 'h_binary', 6, 179, nothing) cv2.createTrackbar('hmax', 'h_binary', 26, 179, nothing) cv2.createTrackbar('smin', 's_binary', 110, 255, nothing)cv2.createTrackbar('smax', 's_binary', 255, 255, nothing)cv2.createTrackbar('vmin', 'v_binary', 140, 255, nothing)cv2.createTrackbar('vmax', 'v_binary', 255, 255, nothing)# 創建滑動條 滑動條值名稱 窗口名稱 滑動條值 滑動條閾值 回調函數def Init():GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM)Direction_Init()Servo_Init()Motor_Init()Trackbar_Init()# 回調函數
def nothing(*arg):pass
二、運動函數
常規操作:向前、向后、向左、向右、停止。?
def Front(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,1) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,1) #right_frontGPIO.output(right_back,0) #right_backdef Back(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,1) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,1) #right_backdef Left(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,1) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,1) #right_frontGPIO.output(right_back,0) #right_backdef Right(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,1) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,1) #right_backdef Stop():L_Motor.ChangeDutyCycle(0)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(0)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,0) #right_back
三、舵機控制
def set_servo_angle(channel,angle):angle=4096*((angle*11)+500)/20000pwm_servo.set_pwm_freq(50) #frequency==50Hz (servo)pwm_servo.set_pwm(channel,0,int(angle))
set_servo_angle(4, 110) #top servo lengthwise#0:back 180:front set_servo_angle(5, 90) #bottom servo crosswise#0:left 180:right
四、在HSV空間下獲取二值圖
1、獲取滑動條的值
# 1 get trackbar's valuehmin = cv2.getTrackbarPos('hmin', 'h_binary')hmax = cv2.getTrackbarPos('hmax', 'h_binary')smin = cv2.getTrackbarPos('smin', 's_binary')smax = cv2.getTrackbarPos('smax', 's_binary')vmin = cv2.getTrackbarPos('vmin', 'v_binary')vmax = cv2.getTrackbarPos('vmax', 'v_binary')
2、轉HSV(三通道:H、S、V)
# 2 to HSVhsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)cv2.imshow('hsv', hsv)h, s, v = cv2.split(hsv)
?
3、轉二值圖(單通道閾值處理)
分別對H、S、V三個道閾值處理:
# 3 set threshold (binary image)# if value in (min, max):white; otherwise:blackh_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))v_binary = cv2.inRange(np.array(v), np.array(vmin), np.array(vmax))# 5 Showcv2.imshow('h_binary', h_binary)cv2.imshow('s_binary', s_binary)cv2.imshow('v_binary', v_binary)
4、結合HSV三個通道,得到最終二值圖
對H、S、V三個通道與操作。(H&&S&&V)
# 4 get binarybinary = cv2.bitwise_and(h_binary, cv2.bitwise_and(s_binary, v_binary))
代碼
# 在HSV色彩空間下得到二值圖
def Get_HSV(image):# 1 get trackbar's valuehmin = cv2.getTrackbarPos('hmin', 'h_binary')hmax = cv2.getTrackbarPos('hmax', 'h_binary')smin = cv2.getTrackbarPos('smin', 's_binary')smax = cv2.getTrackbarPos('smax', 's_binary')vmin = cv2.getTrackbarPos('vmin', 'v_binary')vmax = cv2.getTrackbarPos('vmax', 'v_binary')# 2 to HSVhsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)cv2.imshow('hsv', hsv)h, s, v = cv2.split(hsv)# 3 set threshold (binary image)# if value in (min, max):white; otherwise:blackh_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))v_binary = cv2.inRange(np.array(v), np.array(vmin), np.array(vmax))# 4 get binary(對H、S、V三個通道分別與操作)binary = cv2.bitwise_and(h_binary, cv2.bitwise_and(s_binary, v_binary))# 5 Showcv2.imshow('h_binary', h_binary)cv2.imshow('s_binary', s_binary)cv2.imshow('v_binary', v_binary)cv2.imshow('binary', binary)return binary
五、圖像處理(總)
1、打開攝像頭
# 1 Capture the framesret, frame = camera.read()image = framecv2.imshow('frame', frame)
2、獲取HSV色彩空間得到的二值圖
(Get_HSV(frame)其實就是步驟四的HSV處理)
# 2 get HSVbinary = Get_HSV(frame)
3、高斯濾波
# 3 Gausi blurblur = cv2.GaussianBlur(binary,(9,9),0)
4、開運算去噪
# 4 Openkernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))Open = cv2.morphologyEx(blur, cv2.MORPH_OPEN, kernel)cv2.imshow('Open',Open)
5、閉運算
# 5 CloseClose = cv2.morphologyEx(Open, cv2.MORPH_CLOSE, kernel)cv2.imshow('Close',Close)
?
6、霍夫圓檢測
原理
霍夫變換圓檢測是基于圖像梯度實現:
圓心檢測的原理︰圓心是圓周法線的交匯處,設置一個閾值,在某點的相交的直線的條數大于這個閾值就認為該交匯點為圓心。
圓半徑確定原理:圓心到圓周上的距離〔半徑)是相同的,設置一個閾值,只要相同距離的數量大于該閾值,就認為該距離是該圓心的半徑。
API
def HoughCircles(image: Any,method: Any,dp: Any,minDist: Any,circles: Any = None,param1: Any = None,param2: Any = None,minRadius: Any = None,maxRadius: Any = None) -> None參數:?
- 第二個參數,int類型的method,即使用的檢測方法,目前OpenCV中就霍夫梯度法一種可以使用,它的標識符為CV_HOUGH_GRADIENT,在此參數處填這個標識符即可。
- 第三個參數,double類型的dp,用來檢測圓心的累加器圖像的分辨率于輸入圖像之比的倒數,且此參數允許創建一個比輸入圖像分辨率低的累加器。上述文字不好理解的話,來看例子吧。例如,如果dp= 1時,累加器和輸入圖像具有相同的分辨率。如果dp=2,累加器便有輸入圖像一半那么大的寬度和高度。
- 第四個參數,double類型的minDist,為霍夫變換檢測到的圓的圓心之間的最小距離,即讓我們的算法能明顯區分的兩個不同圓之間的最小距離。這個參數如果太小的話,多個相鄰的圓可能被錯誤地檢測成了一個重合的圓。反之,這個參數設置太大的話,某些圓就不能被檢測出來了。
- 第五個參數,InputArray類型的circles,經過調用HoughCircles函數后此參數存儲了檢測到的圓的輸出矢量,每個矢量由包含了3個元素的浮點矢量(x, y, radius)表示。
- 第六個參數,double類型的param1,有默認值100。它是第三個參數method設置的檢測方法的對應的參數。對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示傳遞給canny邊緣檢測算子的高閾值,而低閾值為高閾值的一半。
- 第七個參數,double類型的param2,也有默認值100。它是第三個參數method設置的檢測方法的對應的參數。對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在檢測階段圓心的累加器閾值。它越小的話,就可以檢測到更多根本不存在的圓,而它越大的話,能通過檢測的圓就更加接近完美的圓形了。
- 第八個參數,int類型的minRadius,有默認值0,表示圓半徑的最小值。
- 第九個參數,int類型的maxRadius,也有默認值0,表示圓半徑的最大值。
?6-1、霍夫圓檢測
# 6 Hough Circle detectcircles = cv2.HoughCircles(Close,cv2.HOUGH_GRADIENT,2,120,param1=120,param2=20,minRadius=20,maxRadius=0)# param2:決定圓能否被檢測到(越少越容易檢測到圓,但相應的也更容易出錯)
6-2、獲取圓心和半徑坐標
# 1 獲取圓的圓心和半徑x, y, r = int(circles[0][0][0]),int(circles[0][0][1]),int(circles[0][0][2])print(x, y, r)
6-3、畫圓
# 2 畫圓cv2.circle(image, (x, y), r, (255,0,255),5)cv2.imshow('image', image)
代碼
# 圖像處理
def Image_Processing():global h, s, v# 1 Capture the framesret, frame = camera.read()image = framecv2.imshow('frame', frame)# 2 get HSVbinary = Get_HSV(frame)# 3 Gausi blurblur = cv2.GaussianBlur(binary,(9,9),0)# 4 Openkernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))Open = cv2.morphologyEx(blur, cv2.MORPH_OPEN, kernel)cv2.imshow('Open',Open)# 5 CloseClose = cv2.morphologyEx(Open, cv2.MORPH_CLOSE, kernel)cv2.imshow('Close',Close)# 6 Hough Circle detectcircles = cv2.HoughCircles(Close,cv2.HOUGH_GRADIENT,2,120,param1=120,param2=20,minRadius=20,maxRadius=0)# param2:決定圓能否被檢測到(越少越容易檢測到圓,但相應的也更容易出錯)# judge if circles is existif circles is not None:# 1 獲取圓的圓心和半徑x, y, r = int(circles[0][0][0]),int(circles[0][0][1]),int(circles[0][0][2])print(x, y, r)# 2 畫圓cv2.circle(image, (x, y), r, (255,0,255),5)cv2.imshow('image', image)else:(x,y),r = (0,0), 0return (x,y), r
六、運動控制
根據檢測到的圓,獲取到的坐標、半徑,進行運動控制。
這里可以做到跟蹤小球,前進和后退相配合,“敵進我退,敵退我進”。
# 運動控制(這里可以做到跟蹤小球,前景和后退相配合,“敵進我退,敵退我進”)
def Move((x,y), r):low_xlimit = width/4high_xlimit = 0.75 * width#low_ylimit = 3/4 * heightylimit = 0.75 * heightprint(high_xlimit, ylimit)# 沒檢測到,停止不動if x==0:Stop()# 檢測到在圖片0.75以上的區域(距離正常)elif x>low_xlimit and x<high_xlimit and y<ylimit:Front(60)# 檢測到在圖片0.75以下的區域(距離過近,后退)elif x>low_xlimit and x<high_xlimit and y>=ylimit:Back(60)# 在左0.25區域,向左跟蹤elif x<low_xlimit:Left(60)# 在右0.25區域,向右跟蹤elif x>high_xlimit:Right(60)
總代碼
#Ball Tracking(HSV)
import RPi.GPIO as GPIO
import time
import Adafruit_PCA9685
import numpy as np
import cv2#set capture window
width, height = 320, 240
camera = cv2.VideoCapture(0)
camera.set(3,width)
camera.set(4,height) l_motor = 18
left_front = 22
left_back = 27r_motor = 23
right_front = 25
right_back = 24def Motor_Init():global L_Motor, R_MotorL_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_front,GPIO.OUT)GPIO.setup(l_motor,GPIO.OUT)GPIO.setup(right_front,GPIO.OUT)GPIO.setup(right_back,GPIO.OUT)GPIO.setup(r_motor,GPIO.OUT)def Servo_Init():global pwm_servopwm_servo=Adafruit_PCA9685.PCA9685()def Trackbar_Init():# 1 create windowscv2.namedWindow('h_binary')cv2.namedWindow('s_binary')cv2.namedWindow('v_binary')# 2 Create Trackbarcv2.createTrackbar('hmin', 'h_binary', 6, 179, nothing) cv2.createTrackbar('hmax', 'h_binary', 26, 179, nothing) cv2.createTrackbar('smin', 's_binary', 110, 255, nothing)cv2.createTrackbar('smax', 's_binary', 255, 255, nothing)cv2.createTrackbar('vmin', 'v_binary', 140, 255, nothing)cv2.createTrackbar('vmax', 'v_binary', 255, 255, nothing)# 創建滑動條 滑動條值名稱 窗口名稱 滑動條值 滑動條閾值 回調函數def Init():GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM)Direction_Init()Servo_Init()Motor_Init()Trackbar_Init()def Front(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,1) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,1) #right_frontGPIO.output(right_back,0) #right_backdef Back(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,1) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,1) #right_backdef Left(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,1) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,1) #right_frontGPIO.output(right_back,0) #right_backdef Right(speed):L_Motor.ChangeDutyCycle(speed)GPIO.output(left_front,1) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(speed)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,1) #right_backdef Stop():L_Motor.ChangeDutyCycle(0)GPIO.output(left_front,0) #left_frontGPIO.output(left_back,0) #left_backR_Motor.ChangeDutyCycle(0)GPIO.output(right_front,0) #right_frontGPIO.output(right_back,0) #right_backdef set_servo_angle(channel,angle):angle=4096*((angle*11)+500)/20000pwm_servo.set_pwm_freq(50) #frequency==50Hz (servo)pwm_servo.set_pwm(channel,0,int(angle))# 回調函數
def nothing(*arg):pass# 在HSV色彩空間下得到二值圖
def Get_HSV(image):# 1 get trackbar's valuehmin = cv2.getTrackbarPos('hmin', 'h_binary')hmax = cv2.getTrackbarPos('hmax', 'h_binary')smin = cv2.getTrackbarPos('smin', 's_binary')smax = cv2.getTrackbarPos('smax', 's_binary')vmin = cv2.getTrackbarPos('vmin', 'v_binary')vmax = cv2.getTrackbarPos('vmax', 'v_binary')# 2 to HSVhsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)cv2.imshow('hsv', hsv)h, s, v = cv2.split(hsv)# 3 set threshold (binary image)# if value in (min, max):white; otherwise:blackh_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))v_binary = cv2.inRange(np.array(v), np.array(vmin), np.array(vmax))# 4 get binary(對H、S、V三個通道分別與操作)binary = cv2.bitwise_and(h_binary, cv2.bitwise_and(s_binary, v_binary))# 5 Showcv2.imshow('h_binary', h_binary)cv2.imshow('s_binary', s_binary)cv2.imshow('v_binary', v_binary)cv2.imshow('binary', binary)return binary# 圖像處理
def Image_Processing():global h, s, v# 1 Capture the framesret, frame = camera.read()image = framecv2.imshow('frame', frame)# 2 get HSVbinary = Get_HSV(frame)# 3 Gausi blurblur = cv2.GaussianBlur(binary,(9,9),0)# 4 Openkernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))Open = cv2.morphologyEx(blur, cv2.MORPH_OPEN, kernel)cv2.imshow('Open',Open)# 5 CloseClose = cv2.morphologyEx(Open, cv2.MORPH_CLOSE, kernel)cv2.imshow('Close',Close)# 6 Hough Circle detectcircles = cv2.HoughCircles(Close,cv2.HOUGH_GRADIENT,2,120,param1=120,param2=20,minRadius=20,maxRadius=0)# param2:決定圓能否被檢測到(越少越容易檢測到圓,但相應的也更容易出錯)# judge if circles is existif circles is not None:# 1 獲取圓的圓心和半徑x, y, r = int(circles[0][0][0]),int(circles[0][0][1]),int(circles[0][0][2])print(x, y, r)# 2 畫圓cv2.circle(image, (x, y), r, (255,0,255),5)cv2.imshow('image', image)else:(x,y),r = (0,0), 0return (x,y), r# 運動控制(這里可以做到跟蹤小球,前景和后退相配合,“敵進我退,敵退我進”)
def Move((x,y), r):low_xlimit = width/4high_xlimit = 0.75 * width#low_ylimit = 3/4 * heightylimit = 0.75 * heightprint(high_xlimit, ylimit)# 沒檢測到,停止不動if x==0:Stop()# 檢測到在圖片0.75以上的區域(距離正常)elif x>low_xlimit and x<high_xlimit and y<ylimit:Front(60)# 檢測到在圖片0.75以下的區域(距離過近,后退)elif x>low_xlimit and x<high_xlimit and y>=ylimit:Back(60)# 在左0.25區域,向左跟蹤elif x<low_xlimit:Left(60)# 在右0.25區域,向右跟蹤elif x>high_xlimit:Right(60)if __name__ == '__main__':Init()set_servo_angle(4, 110) #top servo lengthwise#0:back 180:front set_servo_angle(5, 90) #bottom servo crosswise#0:left 180:right while 1:# 1 Image Process(x,y), r = Image_Processing()# 2 MoveMove((x,y), r)# must include this codes(otherwise you can't open camera successfully)if cv2.waitKey(1) & 0xFF == ord('q'):Stop()GPIO.cleanup() break
這里的HSV是根據我自己當前的情況調節的,更改場景以后可能需要重新調節H、S、V三通道的閾值(max && min)
基礎的視覺檢測+運動,沒有太多的運動控制算法(PID等等都沒有涉及到)。有好的想法和建議歡迎交流討論,Thanks?(・ω・)ノ
總結
以上是生活随笔為你收集整理的树莓派视觉小车 -- 小球追踪(颜色追踪)(OpenCV色彩空间HSV)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV(基础补充)颜色空间HSV
- 下一篇: 机器学习(18)-- SVM支持向量机(