【opencv】(13) 案例:停车场空余车位检测,附python完整代码
各位同學好,今天和大家分享一下如何使用Opencv完成停車場的車位檢測,及空余車位計數,先放張圖看效果。
紅框代表該車位有車,綠框代表該車位空余,左上角記錄有幾個空余車位,黃色數字代表該車位內的像素個數,用于判斷車位上是否有車。
? ?
從圖中可以看到,由于視頻拍攝角度的問題,車位不是橫平豎直的,并且車位在屏幕上的大小和角度也是不相同的。需要用到旋轉矩形的操作,并調整單個矩形框使其能夠用于所有車位。
1. 處理旋轉矩形
首先,由于車位是矩形框經過一定角度旋轉之后得到的,因此在先定義一個處理旋轉矩形的函數,把它其他py文件放在同路徑的目錄內,這里命名為rotaParking.py
假設對圖片上任意點(x,y),繞一個坐標點(rx0,ry0)順時針旋轉a角度后的新的坐標設為(x0, y0),有公式:
x0 = (x - rx0) *?cos(a) - (y - ry0) * sin(a) + rx0?
y0 = (x - rx0) * sin(a) + (y - ry0) * cos(a) + ry0?
根據此公式,創建函數recRota完成旋轉矩形操作,后續在車位處理的py文件中直接調用它。返回值是img繪制旋轉矩形圖之后的圖像,以及angle是旋轉后的矩形的四個角的坐標構成的列表。
# 矩形框順時針旋轉
import cv2
import math# 傳入旋轉的參考點坐標,矩形框左上角坐標(x,y),框的寬w和高h,旋轉角度a
def angleRota(center_x, center_y, x, y, w, h, a):# 角度轉弧度a = (math.pi/180)*a# 旋轉前左上角坐標x1, y1 = x, y# 右上角坐標x2, y2 = x+w, y# 右下角坐標x3, y3 = x+w, y+h# 左下角坐標x4, y4 = x, y+h# 旋轉后的左上角坐標,像素坐標是整數px1 = int((x1 - center_x) * math.cos(a) - (y1 - center_y) * math.sin(a) + center_x) py1 = int((x1 - center_x) * math.sin(a) + (y1 - center_y) * math.cos(a) + center_y)# 右上角坐標px2 = int((x2 - center_x) * math.cos(a) - (y2 - center_y) * math.sin(a) + center_x)py2 = int((x2 - center_x) * math.sin(a) + (y2 - center_y) * math.cos(a) + center_y)# 右下角坐標px3 = int((x3 - center_x) * math.cos(a) - (y3 - center_y) * math.sin(a) + center_x) py3 = int((x3 - center_x) * math.sin(a) + (y3 - center_y) * math.cos(a) + center_y)# 左下角坐標px4 = int((x4 - center_x) * math.cos(a) - (y4 - center_y) * math.sin(a) + center_x) py4 = int((x4 - center_x) * math.sin(a) + (y4 - center_y) * math.cos(a) + center_y)# 保存每一個角的坐標pt1 = (px1, py1)pt2 = (px2, py2)pt3 = (px3, py3)pt4 = (px4, py4)# 存儲每個角的坐標angle = [pt1, pt2, pt3, pt4]# 返回調整后的坐標return angle# 繪制旋轉后的矩形框
def drawLine(img, angle, color, thickness):# 分別繪制四條邊cv2.line(img, angle[0], angle[1], color, thickness)cv2.line(img, angle[1], angle[2], color, thickness)cv2.line(img, angle[2], angle[3], color, thickness)cv2.line(img, angle[3], angle[0], color, thickness)# 返回繪制好旋轉矩形的圖像return img# 矩形旋轉
def recRota(img, center_x, center_y, x1, y1, w, h, rota, draw=True):'''img: 原圖像(center_X, center_y): 旋轉參考點的坐標(x1, y1): 矩形框左上角坐標w: 矩形框的寬h: 矩形框的高rota: 順時針的旋轉角度,如:30°'''color = (255,255,0) # 繪制停車線的線條顏色thickness = 2 # 停車線線條寬度#(1)計算旋轉一定角度后的四個角的坐標angle = angleRota(x1, y1, x1, y1, w, h, rota)#(2)繪制旋轉后的矩形if draw == True:img = drawLine(img, angle, color, thickness)# 返回繪制后的圖像,以及矩形框的四個角的坐標return img, angleelse:return angle
2. 處理單幀圖像,劃分車位
這里用到鼠標響應 cv2.setMouseCallback() 可以在圖像上用鼠標操作,觸發一些事件。
鼠標響應: setMousecallback(winname, onMouse, userdata=0)
參數:
winname: 窗口的名字
onMouse: 鼠標響應函數,回調函數。指定窗口里每次鼠標時間發生的時候,被調用的函數指針。
userdate:傳給回調函數的參數?
回調函數:?onMouse(int event, x,?y,? flags,?param)
event: 是 CV_EVENT_*?變量之一
x和y: 鼠標指針在圖像坐標系的坐標(像素坐標)
flags: 是CV_EVENT_FLAG的組合
param: 是用戶定義的傳遞到setMouseCallback函數調用的參數
常用事件event變量:
cv2_EVENT_MOUSEMOVE 0 # 滑動
cv2_EVENT_LBUTTONDOWN 1 # 左鍵點擊
cv2_EVENT_RBUTTONDOWN 2 # 右鍵點擊
cv2_EVENT_MBUTTONDOWN 3 # 中間點擊
cv2_EVENT_LBUTTONUP 4 # 左鍵釋放
cv2_EVENT_RBUTTONUP 5 # 右鍵釋放
cv2_EVENT_MBUTTONUP 6 # 中間釋放
cv2_EVENT_LBUTTONDBLCLK 7 # 左鍵雙擊
cv2_EVENT_RBUTTONDBLCLK 8 # 右鍵雙擊
cv2_EVENT_MBUTTONDBLCLK 9 # 中間釋放
cv2_EVENT_FLAG_LBUTTON 1 # 左鍵拖拽
cv2_EVENT_FLAG_RBUTTON 2 # 右鍵拖拽
cv2_EVENT_FLAG_MBUTTON 4 # 中間拖拽
。。。。。。。。。。。。。。。。。。。
在本案例中,如果鼠標左鍵單機圖像,那么就以鼠標所在位置坐標作為矩形框的左上角坐標,每點一個就生成一個矩形框,將坐標保存在 posList 中。如果畫錯了,那么就右鍵點擊需要刪除的矩形框,如果鼠標位置在遍歷后的某個矩形框內部,那么就在posList中刪除該矩形框的左上角坐標,并在下一幀圖上不顯示該矩形框。
將每一次點擊后更新好了的posList坐標信息保存在txt文件中,這樣在下一次打開文件時就不需要重復標定矩形框。在讀取坐標信息時使用try和except方法,如果這是第一次標定,沒有生成過txt文件,那么try方法拋出異常,執行except的方法。
在這一幀中標定的坐標,會在在下一幀中繪制,遍歷poList,以pos鼠標所在坐標為旋轉參考點坐標,繪制旋轉矩形框。
import cv2
import pickle # 將對象以文件的形式存放在磁盤上
from rotaParking import recRota # 導入自定義的旋轉矩形框函數#(1)讀入圖片
filepath = 'C:\\GameDownload\\Deep Learning\\parking_car.jpg'
filename = 'parking_position.txt' # 保存的車位坐標# 只需要確定一個停車位的大小,就可以將這個矩形框用于所有車位
w, h = 90, 160 # 矩形框的寬和高# 運行時讀取保存的車位坐標,保留前一次畫好了的車位線
try:with open(filename, 'rb') as file_object:posList = pickle.load(file_object)# 第一次運行時沒有寫過文件,那就執行下面的,生成一個列表保存坐標
except:# 創建一個列表接收觸發鼠標事件后的坐標信息posList = []#(2)創建一個回調函數,當鼠標事件觸發時,該函數執行
# cv2.setMouseCallback的參數,在圖像上點擊鼠標會發生什么
def onMouse(events, x, y, flag, params):# 如果在圖像上點擊鼠標左鍵,那么就將鼠標位置的(x,y)坐標保存起來if events == cv2.EVENT_LBUTTONDOWN:# 接收鼠標所在位置為xy坐標posList.append((x,y))# 如果右鍵點擊就將這個坐標從posList中刪除if events == cv2.EVENT_RBUTTONDOWN:# 如果當前的坐標點在某一個檢測框中就刪除這個檢測框for index, pos in enumerate(posList):# x和y代表鼠標當前所在位置if pos[0]<x<pos[0]+w and pos[1]<y<pos[1]+h:# 將這個pos坐標移除posList.pop(index)#(3)確認車位線后,把車位線坐標保存下來,保存在一個文本中with open(filename, 'wb') as file_object: # 將列表轉換字符串寫入pickle.dump(posList, file_object)#(4)繪制停車線矩形框
while True:# 像視頻一樣不斷播放這一幀img = cv2.imread(filepath)# 遍歷鼠標點擊的所有坐標,該坐標就是矩形框的左上角xy坐標for pos in posList:# 繪制旋轉后的矩形,傳入原圖,旋轉參考點坐標,矩形框左上角坐標,框的寬w和高h,逆時針轉4°img, angle = recRota(img, pos[0], pos[1], pos[0], pos[1], w, h, -4) # 顯示圖像cv2.imshow('img', img)# 鼠標點擊圖像,參數:窗口名必須和上面的名稱相同;傳入函數,如果點擊鼠標會發生什么cv2.setMouseCallback('img', onMouse)# '0'圖像不會自動消失key = cv2.waitKey(0) # 按任意鍵關閉圖像窗口cv2.destroyAllWindows()
繪制后的單幀圖像如下
3. 處理視頻,分割出所有車位
在對單幀圖像處理時,我們已經將所有的車位分割出來,并記錄下左上角坐標。接下來對視頻圖像處理時只需直接導入左上角坐標點的txt文件。
從上圖可以看到,每個車位框是傾斜的,如果要分割出每個車位,必須使用切片方法,但切片出來的圖像是橫平豎直的。因此針對每一個車位框,都以該框的左上角為旋轉參考,旋轉整張幀圖像,將車位框擺正之后再切片。
圖像旋轉方法:
獲得旋轉變化矩陣: M = cv2.getRotationMatrix2D(center, angle, scale)
center: 圖像旋轉的參考點坐標
angle: 旋轉角度(°),正數代表逆時針,負數代表順時針
scale: 旋轉后的圖像是原來縮放后的多少倍
M: 計算得到的旋轉矩陣
旋轉變化: dst =?cv2.warpAffine(src, M, dsize, flags, borderMode, borderValue)
src: 需要變化的圖像
M:?旋轉變換矩陣
dsize: 輸出圖像的大小
flags: 插值方法的組合(int 類型)
borderMode: 邊界像素模式(int 類型)
borderValue: 邊界填充值,默認情況下,它為0,也就是邊界填充默認是黑色。
flags 插值方式如下:
cv2.INTER_LINEAR # 線性插值(默認)
cv2.INTER_NEAREST # 最近鄰插值
cv2.INTER_AREA # 區域插值
cv2.INTER_CUBIC # 三次樣條插值
cv2.INTER_LANCZOS4 # Lanczos插值
將每個檢測框擺正之后就可以使用切片方法 rota_img[pos[1]:pos[1]+h, pos[0]:pos[0]+w],橫平豎直地分割出每個車位。pos[1]中存放y坐標,pos[0]中存放x坐標。切片時先指定高再指定寬。?
為了避免切割出來的每個車位有繪圖的痕跡,因此在繪圖之前將原圖像復制一份,imgCopy = img.copy(),在復制的圖像上繪制矩形框,這樣就不會在分割后的車位圖像上出現繪圖痕跡。
處理視頻的代碼如下:
# 處理視頻圖像
import cv2
import pickle
from rotaParking import recRota # 導入自定義的旋轉矩形框函數
from rotaParking import drawLine # 導入繪制旋轉矩形線條函數#(1)讀取視頻
filepath = 'C:\\GameDownload\\Deep Learning\\parking.mp4'
cap = cv2.VideoCapture(filepath) #(2)導入先前記錄下來的車位矩形框的左上角坐標
filename = 'parking_position.txt' # 保存的車位坐標
with open(filename, 'rb') as f:posList = pickle.load(f)#(3)處理每一幀圖像
while True:# 返回圖像是否讀取成功,以及讀取的幀圖像imgsuccess, img = cap.read()# 為了使裁剪后的單個車位里面沒有繪制的邊框,需要在畫車位框之前,把原圖像復制一份imgCopy = img.copy()# 獲得整每幀圖片的寬和高img_w, img_h = img.shape[:2] #shape是(w,h,c)# 由于這個視頻比較短,就循環播放這個視頻if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):# 如果當前幀==總幀數,那就重置當前幀為0cap.set(cv2.CAP_PROP_POS_FRAMES, 0)#(4)繪制停車線矩形框w, h = 90, 160 # 矩形框的寬和高# 遍歷所有的矩形框坐標for pos in posList:# 得到旋轉后的矩形的四個角坐標,傳入原圖,旋轉參考點坐標,矩形框左上角坐標,框的寬w和高h,逆時針轉4°angle = recRota(img, pos[0], pos[1], pos[0], pos[1], w, h, -4, draw=False) # 裁剪的車位不繪制車位圖 #(5)裁剪所有的車位框,由于我們的矩形是傾斜的,先要把矩形轉正之后再裁剪# 變換矩陣,以每個矩形框的左上坐標為參考點,順時針尋轉4°,旋轉后的圖像大小不變rota_params = cv2.getRotationMatrix2D(angle[0], angle=-4, scale=1) # 旋轉整張幀圖片,輸入img圖像,變換矩陣,指定輸出圖像大小rota_img = cv2.warpAffine(img, rota_params, (img_w, img_h))# 裁剪擺正了的矩形框,先指定高h,再指定寬wimgCrop = rota_img[pos[1]:pos[1]+h, pos[0]:pos[0]+w]# 顯示裁剪出的圖像cv2.imshow(f'imgCrop:{pos[0],pos[1]}', imgCrop)#(6)繪制所有車位的矩形框# 在復制后的圖像上繪制車位框imgCopy = drawLine(imgCopy, angle, (255,255,0), 3)#(7)顯示圖像,輸入窗口名及圖像數據cv2.imshow('img', imgCopy) if cv2.waitKey(10) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
分割出每一個車位的圖像
4. 檢測車位上是否有車
檢測思路是:如果車位上沒有車,那么二值圖中白點很少,如果有車,車身的白點很多,因此通過計算每個車位框內的像素個數來確定該車位內是否有車。
(1)先將讀入的圖像變成灰度圖,cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
(2)對灰度圖濾波操作,消除噪聲,采用高斯濾波,cv2.GaussianBlur(img, ksize, 標準差x, 標準差y),ksize卷積核大小,滑窗寬度高度為奇數;標準差x代表沿x方向的卷積核的標準差;標準差y代表沿y方向的卷積核的標準差,不設置的話則和x軸的標準差一致。
(3)對濾波后的灰度圖轉換為二值圖,采用自適應閾值方法:
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
src:?灰度化的圖片
maxValue:?當圖像像素超過了閾值或低于閾值(由type決定),所賦予的值
adaptiveMethod:?自適應方法。有2種,
ADAPTIVE_THRESH_MEAN_C: 為局部鄰域塊的平均值,該算法是先求出塊中的均值。
ADAPTIVE_THRESH_GAUSSIAN_C: 為局部鄰域塊的高斯加權和。該算法是在區域中(x, y)周圍的像素根據高斯函數按照他們離中心點的距離進行加權計算。
thresholdType: 二值化方法,有兩種
cv2.THRESH_BINARY: 二值法,超過閾值thresh部分取maxval(設定的最大值),否則取0
cv2.THRESH_BINARY_INV: 超過閾值的部分取0,小于閾值取maxval
blockSize: 分割計算的區域大小,取奇數
C: 常數,每個區域計算出的閾值的基礎上在減去這個常數作為這個區域的最終閾值,可以為負數
(4)消除上的零散白點,對二值化之后的圖像使用中值濾波,cv2.medianBlur(img, ksize)
,ksize代表濾波模板的尺寸大小,填一個數值,必須是大于1的奇數
(5)由于車位框內有車的話白點密集,沒車的話車位內一片黑,因此擴充白點數目,為了增大區別。使用膨脹方法,cv2.dilate(img, kernel, iterations),kernel為卷積核大小,iterations為迭代次數。
下圖中,第一張是二值化之后的圖像,第二張是中值濾波后的,第三張是膨脹操作后的
(6)計算每個分割出來的車位框中的白點個數,countNonZero(),返回灰度值不為0的像素數量。經過分析,如果白點數量大于3000,那么就表明車位上有車。
空余車位檢測的代碼如下:
# 處理視頻圖像
import cv2
import pickle
import numpy as np
from rotaParking import recRota # 導入自定義的旋轉矩形框函數
from rotaParking import drawLine # 導入繪制旋轉矩形線條函數#(1)讀取視頻
filepath = 'C:\\GameDownload\\Deep Learning\\parking.mp4'
cap = cv2.VideoCapture(filepath) # 初始的車位框顏色
color = (255,255,0)#(2)導入先前記錄下來的車位矩形框的左上角坐標
filename = 'parking_position.txt' # 保存的車位坐標
with open(filename, 'rb') as f:posList = pickle.load(f)#(3)處理每一幀圖像
while True:# 記錄有幾個空車位spacePark = 0# 返回圖像是否讀取成功,以及讀取的幀圖像imgsuccess, img = cap.read()# 為了使裁剪后的單個車位里面沒有繪制的邊框,需要在畫車位框之前,把原圖像復制一份imgCopy = img.copy()# 獲得整每幀圖片的寬和高img_w, img_h = img.shape[:2] #shape是(w,h,c)# ==1== 轉換灰度圖,通過形態學處理來檢測車位內有沒有車imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# ==2== 高斯濾波,卷積核3*3,沿x和y方向的卷積核的標準差為1imgGray = cv2.GaussianBlur(imgGray, (3,3), 1)# ==3== 二值圖,自適應閾值方法imgThresh = cv2.adaptiveThreshold(imgGray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 101, 20)# ==4== 刪除零散的白點,# 如果車位上有車,那么車位上的像素數量(白點)很多,如果沒有車,車位框內基本沒什么白點imgMedian = cv2.medianBlur(imgThresh, 5) # ==5== 擴張白色部分,膨脹kernel = np.ones((3,3), np.uint8) # 設置卷積核imgDilate = cv2.dilate(imgMedian, kernel, iterations=1) # 迭代次數為1# 由于這個視頻比較短,就循環播放這個視頻if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):# 如果當前幀==總幀數,那就重置當前幀為0cap.set(cv2.CAP_PROP_POS_FRAMES, 0)#(4)繪制停車線矩形框w, h = 90, 160 # 矩形框的寬和高# 遍歷所有的矩形框坐標for pos in posList:# 得到旋轉后的矩形的四個角坐標,傳入原圖,旋轉參考點坐標,矩形框左上角坐標,框的寬w和高h,逆時針轉4°angle = recRota(imgDilate, pos[0], pos[1], pos[0], pos[1], w, h, -4, draw=False) # 裁剪的車位不繪制車位圖 #(5)裁剪所有的車位框,由于我們的矩形是傾斜的,先要把矩形轉正之后再裁剪# 變換矩陣,以每個矩形框的左上坐標為參考點,順時針尋轉4°,旋轉后的圖像大小不變rota_params = cv2.getRotationMatrix2D(angle[0], angle=-4, scale=1) # 旋轉整張幀圖片,輸入img圖像,變換矩陣,指定輸出圖像大小rota_img = cv2.warpAffine(imgDilate, rota_params, (img_w, img_h))# 裁剪擺正了的矩形框,先指定高h,再指定寬wimgCrop = rota_img[pos[1]:pos[1]+h, pos[0]:pos[0]+w]# 顯示裁剪出的圖像cv2.imshow('imgCrop', imgCrop)#(6)計算每個裁剪出的單個車位有多少個像素點count = cv2.countNonZero(imgCrop)# 將計數顯示在矩形框上cv2.putText(imgCopy, str(count), (pos[0]+5, pos[1]+20), cv2.FONT_HERSHEY_COMPLEX, 0.8, (0,255,255), 2)#(7)確定車位上是否有車if count < 3000: # 像素數量小于2500輛就是沒有車color = (0,255,0) # 沒有車的話車位線就是綠色spacePark += 1 # 每檢測到一個空車位,數量就加一 else:color = (0,0,255) # 有車時車位線就是紅色#(8)繪制所有車位的矩形框# 在復制后的圖像上繪制車位框imgCopy = drawLine(imgCopy, angle, color, 3)# 繪制目前還剩余幾個空車位cv2.rectangle(imgCopy, (0,150), (200,210), (255,255,0), cv2.FILLED)cv2.rectangle(imgCopy, (5,155), (195,205), (255,255,255), 3)cv2.putText(imgCopy, 'FREE: '+str(spacePark), (31,191), cv2.FONT_HERSHEY_COMPLEX, 1, (255,0,255), 3)#(9)顯示圖像,輸入窗口名及圖像數據cv2.imshow('img', imgCopy) # 原圖cv2.imshow('imgGray', imgGray) # 高斯濾波后cv2.imshow('imgThresh', imgThresh) # 二值化后cv2.imshow('imgMedian', imgMedian) # 模糊后cv2.imshow('imgDilate', imgDilate) # 膨脹if cv2.waitKey(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
最終檢測效果圖如下:
總結
以上是生活随笔為你收集整理的【opencv】(13) 案例:停车场空余车位检测,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【机器视觉案例】(6) AI视觉,距离测
- 下一篇: 【机器视觉案例】(8) AI视觉,手势控