概述
車牌識別是計算機視訊影像識別技術在車輛牌照識別中的一種應用,通常來講如果結合opencv進行車牌識別主要分為四個大步驟,分別為:
- 影像採集
- 車牌定位
- 分割車牌字元
- 字元識別
當然,如果結合了機器學習可能步驟會變得更為精簡,但是從opencv基礎方法開始也不失為一種學習進步,此案例僅僅從藍牌車牌入手,作為學習交流用,暫不打算花時間研究綠牌、黃牌車等車牌識別。
影像採集我們直接掠過,現在假設我們已經完成了影像採集,得到了包含車牌的圖片。我們直接從車牌定位開始。
*** 文中的車輛、車牌均來自網路,與現實中任何事務無關。***
取出車牌
現在我們目的是從一張圖片中識別出車牌位置,並將車牌位置的矩形單獨取出來形成一張圖片。為了達到這樣的目的我們需要完成以下幾步:
- 尺寸調整:將較大尺寸圖片處理成較小尺寸圖片,避免影像處理過慢
- 色域分析:取出藍色色彩範圍
- 色域疊加:將色域分析出的藍色範圍在圖片中進行疊加,更加方便識別
- 調整為灰度影像:牢記我們最終目的是進行車牌識別,調整為灰度影像更方便後續使用
- 高斯過濾:主要過濾噪點等干擾
- 形態學處理:通常使用一次侵蝕和膨脹作為基本操作來執行形態轉換,以方便後面的影像邊緣檢測
- 二值化:將灰度影像的某個值以下的全部設定為0,該值以上的全部設定為255,更加方便後續的圖片邊緣檢測
- 邊緣檢測:檢測圖片中的邊緣
- 特徵找牌:根據車牌寬高比的特徵取出車牌
當然,為什麼要進行以上幾步才找出車牌這個仁者見仁智者見智,就類似如果要吃飯首先要淘米,然後放入鍋中煮一樣,總而言之有更好更簡潔高效的方式也是可以的。
尺寸調整
剛才說到,尺寸調整的目的是避免因圖片尺寸過大而引起計算機處理速度過慢,在這裡需要重點注意的是在調整尺寸的時候需要注意保持好圖片的寬高比,假設我們需要做的是將圖片的寬度變為500px,並同時假設我們手中的圖片是19201080px,那麼我們最終處理好的圖片應該是889500,即500/1080*1920
以下程式碼來自網路,在python opencv中保持寬高比地處理圖片:
點選檢視程式碼
def resize_keep_aspectratio(image_src, dst_size):
src_h, src_w = image_src.shape[:2]
# print(src_h, src_w)
dst_h, dst_w = dst_size
# 判斷應該按哪個邊做等比縮放
h = dst_w * (float(src_h) / src_w) # 按照w做等比縮放
w = dst_h * (float(src_w) / src_h) # 按照h做等比縮放
h = int(h)
w = int(w)
if h <= dst_h:
image_dst = cv2.resize(image_src, (dst_w, int(h)))
else:
image_dst = cv2.resize(image_src, (int(w), dst_h))
h_, w_ = image_dst.shape[:2]
return image_dst
我們使用img = self.resize_keep_aspectratio(image, [500, 500])即可進行呼叫,其中雖然傳遞為[500,500],實際上改方法會根據寬高比自動調整。
色域分析&疊加
色域分析的關鍵函式是呼叫cv2.inRange方法,圍繞該方法我們寫出以下程式碼:
點選檢視程式碼
# hsv提取藍色部分
def hsv_color_find(img):
img_copy = img.copy()
"""
提取圖中的藍色部分 hsv範圍可以自行優化
"""
hsv = cv2.cvtColor(img_copy, cv2.COLOR_BGR2HSV)
low_hsv = np.array([100, 80, 80])
high_hsv = np.array([124, 255, 255])
# 設定HSV的閾值
mask = cv2.inRange(hsv, lowerb=low_hsv, upperb=high_hsv)
cv2.imshow("hsv_color_find", mask)
# 將掩膜與影像層逐畫素相加
res = cv2.bitwise_and(img_copy, img_copy, mask=mask)
cv2.imshow("hsv_color_find2", res)
return res
其中low_hsv以及high_hsv是根據經驗設定的在hsv色域取出藍色的值,最終效果如下:
原圖:
進行色域分析後的圖:
進行色域分析後進行疊加的圖:
調整為灰度影像
點選檢視程式碼
# RGB->灰度
gray_img = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
高斯過濾
點選檢視程式碼
gray_img_ = cv2.GaussianBlur(gray_img, (5, 5), 0, 0, cv2.BORDER_DEFAULT)
其中(5, 5)也是經驗值,可以自行調整
經過灰度處理和高斯過濾後結果如下:
形態學處理
點選檢視程式碼
kernel = np.ones((23, 23), np.uint8)
# 使用侵蝕和膨脹作為基本操作來執行高階形態轉換。任何操作都可以就地完成.在多通道影像的情況下,每個通道都是獨立處理的.
img_opening = cv2.morphologyEx(gray_img, cv2.MORPH_OPEN, kernel)
# 計算兩個陣列的加權和
img_opening = cv2.addWeighted(gray_img, 1, img_opening, -1, 0)
進行形態學處理的主要方法是cv2.morphologyEx,裡面包括了兩個重要的引數,對於這兩個重要的引數可以通過搜尋引擎得知其用途。
處理過後效果如下:
二值化
ret2, img_thresh2 = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY)
效果如下:
邊緣檢測
點選檢視程式碼
# # 使用開運算和閉運算讓影像邊緣成為一個整體
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel)
img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel)
img_edge3 = cv2.morphologyEx(img_thresh2, cv2.MORPH_CLOSE, kernel)
img_edge4 = cv2.morphologyEx(img_edge3, cv2.MORPH_CLOSE, kernel)
contours, hierarchy = cv2.findContours(img_edge4, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
真正檢測邊緣的程式碼實際上就是
cv2.findContours
但是在真正進行檢測的時候還做了幾次開運算和閉運算,這樣可以使碎片化的二值化影像形成一個整體,以便後續進行整體識別。
識別過後針對邊緣畫出的邊緣影像如下:
根據車牌寬高比的特徵取出車牌
從上一步得知,我們進行邊緣檢測得到的邊緣多達9個,我們需要從這些邊緣中找出車牌號的那塊,OpenCV自帶的cv2.contourArea()函式可以實現計算點集(輪廓)所圍區域的面積,cv2.minAreaRect()函式可以計算出點集的最小外包旋轉矩形,cv2.boxPoints()函式可以根據旋轉矩形的中心的座標、尺寸和旋轉角度,計算出旋轉矩形的四個頂點。
我們可以通過測量知道中國的藍牌和黑牌是440×140,黃牌前牌尺寸同,後牌為440×220;摩托車及輕便摩托車前牌是220×95,後牌是220×140。我們可以通如下程式碼篩選:
點選檢視程式碼
## 先篩選面積較大的地方
for contour in contours:
if cv2.contourArea(contour) > 2000:
temp_contours.append(contour)
for temp_contour in temp_contours:
rect_tupple = cv2.minAreaRect(temp_contour)
rect_width, rect_height = rect_tupple[1]
if rect_width < rect_height:
rect_width, rect_height = rect_height, rect_width
aspect_ratio = rect_width / rect_height
if aspect_ratio > 1.5 and aspect_ratio < 4.65: #此處可根據具體的情況針對比例進行具體的調整
car_plate1.append(temp_contour)
rect_vertices = cv2.boxPoints(rect_tupple)
#最後進行矩陣和投影變換,將斜著的矩形擺正
rect = cv2.minAreaRect(car_plate)
# 計算最小區域的座標
box = cv2.boxPoints(rect)
wh = np.int0(rect[1])
# 變換矩陣
if 0<rect[2]<=45:
mat = cv2.getPerspectiveTransform(np.float32([[box[0][0], box[0][1]], [box[1][0], box[1][1]], [box[2][0], box[2][1]], [box[3][0], box[3][1]]]),np.float32([[0, wh[1]], [0, 0], [wh[0], 0], [wh[0], wh[1]]]))
# 投影變換
lic = cv2.warpPerspective(img, mat, (wh[0], wh[1]))
if 45 < rect[2] <= 90:
mat = cv2.getPerspectiveTransform(np.float32(
[[box[0][0], box[0][1]], [box[1][0], box[1][1]], [box[2][0], box[2][1]],
[box[3][0], box[3][1]]]), np.float32([[0, 0], [wh[1], 0], [wh[1], wh[0]], [0, wh[0]]]))
# 投影變換
lic = cv2.warpPerspective(img, mat, (wh[1], wh[0]))
cv2.imshow("card_img", lic)
得到的結果如下:
至此,我們完成了車牌的提取,得到這張圖片後我們可以即可以進行後續的操作!