前言
以前制作智能車都是在STM32上制作的,學習了一點OpenCV想要實踐一下,一下就想到了買來一直在吃灰的樹莓派,做一個智能小車吧!
黑線識别
我們先在Windows上寫出基本能跑的檢測代碼:
首先我先用畫圖繪制了一張理想的跑道圖檔
在讀取并且進行二值化後:
img = cv2.imread('sd0.png')#讀取圖檔
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#轉為灰階圖
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)# 大津法二值化
理想情況真的很理想~(笑哭哈哈)
我們先檢測一條線上的情況,為了小車能夠更早的檢測到偏移并且做出反應,我們選擇靠近圖像頂部1/5處的直線作為檢測對象(如圖黑線):
我們将中心初始化為圖像的中心
rows,cols,channels = img.shape#擷取圖像尺寸
center = int(cols/2)#初始位置中心點處
然後我們在中心的附近進行黑線的尋找,在找到中心黑線後中心黑線的中心點即為新的中心位置!
總體代碼如下:
import cv2
import numpy as np
#圖像預處理
img = cv2.imread('sd0.png')#讀取圖檔
rows,cols,channels = img.shape#擷取圖像尺寸
center = int(cols/2)#初始位置中心點處
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#轉為灰階圖
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)# 大津法二值化
#确定位置
h1 = dst[int(rows*4/5)]# 找到4/5處
cv2.line(img,(0,int(rows*1/5)),(cols,int(rows*1/5)),(0,0,0),1,cv2.LINE_AA)#畫輔助線
l_post = r_post = center#從上次中心點開始尋找邊界
if h1[center] == 0:#未偏離黑線
#尋找黑線邊界
while h1[l_post] == 0:
l_post = l_post - 1
while h1[r_post] == 0:
r_post = r_post + 1
else:#偏離黑線
#尋找黑線
while h1[l_post] == 255:
l_post = l_post - 1
while h1[r_post] == 255:
r_post = r_post + 1
if (center - l_post) < (r_post - center):#黑線在左邊
r_post = l_post
while h1[l_post] == 0:
l_post = l_post - 1
else:#黑線在右
rl_post = r_post
while h1[r_post] == 0:
r_post = r_post + 1
center = (r_post + l_post)/2#确定位置
#确定中心點标記
cv2.circle(img,(int(center),int(rows*1/5)), 5, (0,0,255), -1)
#顯示效果
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
但是我在換了一張圖檔後識别失敗了!
分析了一波原因,感覺是二值化後左邊的深藍色區域也變為黑色,并且距離中心位置更近,導緻錯誤檢測。但是如果在開始對正,連續行駛的情況下應該問題不大(其實是不知道咋辦了~)!
import cv2
import RPi.GPIO as gpio
import time
import numpy as np
# 定義引腳
sg = 12
moto1 = 13
moto2 = 15
moto3 = 16
moto4 = 18
# 設定GPIO口為BOARD編号規範
gpio.setmode(gpio.BOARD)
# 設定GPIO口為輸出
gpio.setup(sg, gpio.OUT)
gpio.setup(moto1, gpio.OUT)
gpio.setup(moto2, gpio.OUT)
gpio.setup(moto3, gpio.OUT)
gpio.setup(moto4, gpio.OUT)
# 設定PWM波,頻率為500Hz
sg_pwm = gpio.PWM(sg, 50)
moto1_PWM = gpio.PWM(moto1, 50)
moto2_PWM = gpio.PWM(moto2, 50)
moto3_PWM = gpio.PWM(moto3, 50)
moto4_PWM = gpio.PWM(moto4, 50)
# pwm波控制初始化
#0.9-1.5-2.1
sg_pwm.start(1.5*100/20)
moto1_PWM.start(0)
moto2_PWM.start(0)
moto3_PWM.start(30)
moto4_PWM.start(30)
cap = cv2.VideoCapture(0)
print(cap.isOpened())
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
center = center_temp = 320 #初始位置中心點處
while(True):
ret, img = cap.read()#擷取圖像
#img = cv2.blur(img,(5,5))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#轉為灰階圖
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)# 大津法二值化
kernel = np.ones((5,5),np.uint8)#closing
closing = cv2.morphologyEx(dst,cv2.MORPH_CLOSE,kernel)
#确定位置
h1 = closing[int(480/2)]# 找到1/2處
# 找到黑色的像素點個數
black_count = np.sum(h1 == 0)
# 找到黑色的像素點索引
black_index = np.where(h1 == 0)
if black_count != 0:
center_temp = (black_index[0][black_count - 1] + black_index[0][0]) / 2
if (abs(center_temp - center) > 150):
pass
else:
center = center_temp
#change duty
duty = (center - 320)*0.015 + 1.5*100/20
if duty > 2.1*100/20:
duty = 2.1*100/20
elif duty < 0.9*100/20:
duty = 0.9*100/20
sg_pwm.ChangeDutyCycle(duty)
#cv2.line(img,(0,int(480/2)),(640,int(480/2)),(0,0,0),1,cv2.LINE_AA)#畫輔助線
#cv2.circle(img,(int(center),int(480/2)), 5, (0,0,255), -1)
#顯示效果
#cv2.imshow("img",closing)
if cv2.waitKey(1) & 0xFF == ord('q'):#監測到鍵盤輸入q關閉
break
cap.release()#釋放攝像頭
cv2.destroyAllWindows()#關閉視窗
pwm1.stop()
moto3_PWM.ChangeDutyCycle(0)
moto4_PWM.ChangeDutyCycle(0)
moto3_PWM.stop()
moto4_PWM.stop()
gpio.cleanup()
硬體
機械上面直接在網上買來了一個成品的車模~
就這樣的,電池盒是後面放上去的,想要用三節18650達到12V左右的電壓。
電路上先用子產品搭一下,電源出來12V電壓使用 LM2596S-5.0V 的穩壓到5v給樹莓派供電(之前手上有L7805,兩塊并聯都不行,發燙導緻樹莓派不斷重新開機),再用 LM2596S-adj 可調版本的給舵機供電,和電源連接配接上一個顯示電壓的數位管,友善檢測電壓及時充電。
電機驅動買了一個現成的子產品,如圖小紅闆。
買了一個超大的12V風扇放在電路頂端,給樹莓派、穩壓、電機驅動等的散熱。
攝像頭直接用的樹莓派攝像頭。