【CV】基于python和OpenCV構(gòu)建智能停車(chē)系統(tǒng)

當(dāng)今時(shí)代最令人頭疼的事情就是找不到停車(chē)位,尤其是找20分鐘還沒(méi)有找到停車(chē)位。
根據(jù)復(fù)雜性和效率的不同,任何問(wèn)題都具有一個(gè)或多個(gè)解決方案。目前智能停車(chē)系統(tǒng)的解決方案,主要包括基于深度學(xué)習(xí)實(shí)現(xiàn),以及基于重量傳感器、光傳感器實(shí)現(xiàn)等。
本期我們將一起通過(guò)使用攝像頭和少量代碼來(lái)實(shí)現(xiàn)最簡(jiǎn)單的智能停車(chē)系統(tǒng)。該解決方案所使用的概念非常簡(jiǎn)單。它由具有以下兩個(gè)腳本組成:
將該解決方案分成兩個(gè)腳本的原因是,避免在每次確定是否有可用停車(chē)位的時(shí)候,就進(jìn)行停車(chē)位的選擇。
為了使這一過(guò)程盡可能簡(jiǎn)單,從現(xiàn)在開(kāi)始,我們將這兩個(gè)腳本稱(chēng)為selector和detector。
相關(guān)依賴(lài)
在本文中,我們使用python 3.7.6,但其他版本(例如3.6或3.8)當(dāng)然也可以使用。首先我們要檢查python的版本,我們通過(guò)在控制臺(tái)中編寫(xiě)python –version,即可返回已安裝的python版本。
C:\Users\Razvan>python --versionPython 3.7.6
在開(kāi)始構(gòu)建該系統(tǒng)依賴(lài)項(xiàng)之前,我們可以設(shè)置一個(gè)虛擬環(huán)境。通過(guò)以下鏈接我們可以了解更多有關(guān)虛擬環(huán)境的信息https://docs.python.org/3.7/tutorial/venv.html。
也可以使用conda創(chuàng)建和管理環(huán)境。有關(guān)更多信息見(jiàn)https://docs.anaconda.com/anaconda/。
在python中設(shè)置完所有內(nèi)容后, 最重要的依賴(lài)關(guān)系將是OpenCV庫(kù)。通過(guò)pip將其添加到虛擬環(huán)境中,可以運(yùn)行pip install opencv-python。
要檢查所有設(shè)置是否正確,我們可以使用以下cv2.__version__命令打印環(huán)境中可用的當(dāng)前OpenCV版本。
(OpenCV) C:\Users\Razvan>pythonPython 3.7.6 (default, Jan 8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32Type "help", "copyright", "credits" or "license" for more information.>>> import cv2>>> print(cv2.__version__)4.2.0>>>
在第一行中,我們可以看到在該項(xiàng)目中使用了名為OpenCV的虛擬環(huán)境。
首先,我們需要安裝一個(gè)停車(chē)場(chǎng)攝像頭。由于我們并沒(méi)有一個(gè)窗戶(hù)可以看到的任何停車(chē)場(chǎng),因此我們選擇使用舊汽車(chē)玩具和印刷紙。另外,我在停車(chē)場(chǎng)上方設(shè)置了一個(gè)網(wǎng)絡(luò)攝像頭,以獲取良好的圖像,因此我們正在處理的圖像如下所示:

接下來(lái),我們來(lái)介紹編碼部分。首先,我們需要構(gòu)建選擇器。我們從導(dǎo)入所需模塊開(kāi)始
import cv2import csv
之后,我們開(kāi)始獲取圖像,在該圖像上選擇停車(chē)位。為此,我們可以選擇攝網(wǎng)絡(luò)攝像頭提供的第一幀,保存并使用該圖像選擇停車(chē)位。下面的代碼是這樣的:
VIDEO_SOURCE = 1cap = cv2.VideoCapture(VIDEO_SOURCE)suc, image = cap.read()cv2.imwrite("frame0.jpg", image)cap.release()cv2.destroyAllWindows()img = cv2.imread("frame0.jpg")
現(xiàn)在,我們已經(jīng)保存了第一幀并在img變量中將其打開(kāi),可以使用selectROIs函數(shù)標(biāo)記停車(chē)位。ROI被定義為感興趣的區(qū)域,代表圖像的一部分,我們將在其上應(yīng)用不同的函數(shù)以及濾波器來(lái)獲取結(jié)果。
返回到selectROIs函數(shù),這將返回一個(gè)列表(類(lèi)型:numpy.ndarray),其中包含我們組裝圖像所需的數(shù)字及其邊界ROI。
r = cv2.selectROIs('Selector', img, showCrosshair = False, fromCenter = False)我們的列表將保存在變量r中。賦予cv2.selectROIs函數(shù)的參數(shù)如下:

選擇所有停車(chē)位之后,是時(shí)候?qū)⑺鼈儗?xiě)入.csv文件了。為此,我們需要將r變量轉(zhuǎn)換為python列表,可以使用rlist = r.tolist()命令實(shí)現(xiàn)。
擁有適當(dāng)?shù)臄?shù)據(jù)后,我們將其保存到.csv文件中,以備將來(lái)使用。
with open('data/rois.csv', 'w', newline='') as outf:csvw = csv.writer(outf)csvw.writerows(rlist)
現(xiàn)在我們已經(jīng)選擇了停車(chē)位,是時(shí)候進(jìn)行一些圖像處理了。解決這個(gè)問(wèn)題的方法如下:
1. 從.csv文件獲取坐標(biāo)。
2. 從中構(gòu)建新圖像。
對(duì)于所有這些操作,我們需要定義一個(gè)要應(yīng)用于每個(gè)位置的函數(shù)。該函數(shù)如下所示:
def drawRectangle(img, a, b, c, d):sub_img = img[b:b + d, a:a + c]edges = cv2.Canny(sub_img, lowThreshold, highThreshold)pix = cv2.countNonZero(edges)if pix in range(min, max):cv2.rectangle(img, (a, b), (a + c, b + d), (0, 255, 0), 3)spots.loc += 1else:cv2.rectangle(img, (a, b), (a + c, b + d), (0, 0, 255), 3)
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了所需的功能,如果我們直接將其應(yīng)用于.csv文件中的每組坐標(biāo)效果可能并不好。因此我們做如下處理
首先,我們的有一些參數(shù)如果可以在腳本運(yùn)行時(shí)(也可以在通過(guò)GUI)實(shí)時(shí)調(diào)整它們,那就更好了。為此,我們需要構(gòu)建一些軌跡欄。OpenCV為我們提供這項(xiàng)功能。
我們需要一個(gè)回調(diào)函數(shù),該函數(shù)不執(zhí)行任何操作,但作為使用OpenCV創(chuàng)建軌跡欄的參數(shù)是必需的。實(shí)際上,回調(diào)參數(shù)具有明確定義的用途,但我們?cè)诖瞬皇褂盟R私庥嘘P(guān)此內(nèi)容的更多信息,查閱OpenCV文檔。
def callback(foo):pass
現(xiàn)在我們需要?jiǎng)?chuàng)建軌跡欄。我們將使用cv2.namedWindow和cv2.createTrackbar功能。
cv2.namedWindow('parameters')cv2.createTrackbar('Threshold1', 'parameters', 186, 700, callback)cv2.createTrackbar('Threshold2', 'parameters', 122, 700, callback)cv2.createTrackbar('Min pixels', 'parameters', 100, 1500, callback)cv2.createTrackbar('Max pixels', 'parameters', 323, 1500, callback)
現(xiàn)在,我們已經(jīng)創(chuàng)建了用于操作參數(shù)的GUI,只剩下一件事了。這就是圖像中可用斑點(diǎn)的數(shù)量。在drawRectangle中定義為spot.loc。這是一個(gè)靜態(tài)變量,必須在程序開(kāi)始時(shí)進(jìn)行定義。該變量為靜態(tài)變量的原因是,我們希望調(diào)用的每個(gè)drawRectangle函數(shù)都將其寫(xiě)入相同的全局變量,而不是每個(gè)函數(shù)都使用一個(gè)單獨(dú)的變量。這樣可以防止返回的可用空間數(shù)量大于實(shí)際的可用空間數(shù)量。
為了實(shí)現(xiàn)這一點(diǎn),我們只需要使用它的loc靜態(tài)變量創(chuàng)建spots類(lèi)。
class spots:loc = 0
現(xiàn)在我們已經(jīng)準(zhǔn)備就緒,只需要從.csv文件中獲取數(shù)據(jù),將其所有數(shù)據(jù)轉(zhuǎn)換為整數(shù),然后在無(wú)限循環(huán)中應(yīng)用構(gòu)建的函數(shù)即可。
with open('data/rois.csv', 'r', newline='') as inf:csvr = csv.reader(inf)rois = list(csvr)rois = [[int(float(j)) for j in i] for i in rois]VIDEO_SOURCE = 1cap = cv2.VideoCapture(VIDEO_SOURCE)while True:spots.loc = 0ret, frame = cap.read()ret2, frame2 = cap.read()min = cv2.getTrackbarPos('Min pixels', 'parameters')max = cv2.getTrackbarPos('Max pixels', 'parameters')lowThreshold = cv2.getTrackbarPos('Threshold1', 'parameters')highThreshold = cv2.getTrackbarPos('Threshold2', 'parameters')for i in range(len(rois)):drawRectangle(frame, rois[i][0], rois[i][1], rois[i][2], rois[i][3])font = cv2.FONT_HERSHEY_SIMPLEXcv2.putText(frame, 'Available spots: ' + str(spots.loc), (10, 30), font, 1, (0, 255, 0), 3)cv2.imshow('Detector', frame)canny = cv2.Canny(frame2, lowThreshold, highThreshold)cv2.imshow('canny', canny)if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()
在我們的循環(huán)中實(shí)際上只是調(diào)用的構(gòu)建函數(shù)要復(fù)雜一點(diǎn)。
首先,我們將空間的數(shù)量初始化為0,以防止每幀添加數(shù)字。
其次,我們進(jìn)入兩個(gè)處理流以顯示真實(shí)圖像和已處理的圖像。這有助于更好地了解此腳本的工作方式以及圖像的處理方式。
然后,我們需要在每次迭代中獲取我們創(chuàng)建的參數(shù)?GUI中的參數(shù)值。這是通過(guò)cv2.getTrackbarPos功能完成的。
接下來(lái)最重要的部分,將drawRectangle函數(shù)應(yīng)用到Selector腳本獲取的所有坐標(biāo)上。
最后,在結(jié)果圖像上寫(xiě)下可用斑點(diǎn)的數(shù)量,顯示Canny函數(shù)的結(jié)果,顯然,這是一種眾所周知的停止循環(huán)的方法。
我們現(xiàn)在便完成了一個(gè)智能停車(chē)項(xiàng)目!

如今,智能停車(chē)已成為熱門(mén)話題之一,并且有許多實(shí)現(xiàn)方式可以導(dǎo)致良好的功能系統(tǒng)。我們這處理方法并不是完美的,有許多方法可以更好地優(yōu)化結(jié)果,并且可以在更多情況下使用。但是,即使這不能解決停車(chē)場(chǎng)危機(jī),也可能是導(dǎo)致危機(jī) 的主要原因。
往期精彩回顧
