車牌定位與畸變校訂(python3.7,opencv4.0)

1、前言及思路簡析

  目前車牌識別系統在各小區門口隨處可見,識別效果貌似都還能夠。查閱資料後,發現整個過程又能夠細化爲車牌定位、畸變校訂、車牌分割和內容識別四部分。本篇隨筆主要介紹車牌定位及畸變校訂兩部分,在python環境下經過opencv實現。python

1.1 車牌定位

  目前主流的車牌定位方法從大的方面來講能夠分爲兩類:一種是基於車牌的背景顏色特徵;另外一種基於車牌的輪廓形狀特徵。基於顏色特徵的又可分爲兩類:一種在RGB空間識別,另外一種在HSV空間識別。經測試後發現,單獨使用任何一種方法,效果均不太理想。目前比較廣泛的作法是幾種定位方法同時使用,或用一種識別,另外一種驗證。本文主要經過顏色特徵對車牌進行定位,以HSV空間的H份量爲主,以RGB空間的R份量和B份量爲輔,後續再用車牌的長寬比例排除干擾。ide

1.2 畸變校訂

  在車牌的圖像採集過程當中,相機鏡頭一般都不是垂直於車牌的,因此待識別圖像中車牌或多或少都會有必定程度的畸變,這給後續的車牌內容識別帶來了必定的困難。所以須要對車牌進行畸變校訂,消除畸變帶來的不利影響。函數

2、代碼實現

2.1 車牌定位

2.1.1 經過顏色特徵選定可疑區域

  取了不一樣光照環境下車牌的圖像,截取其背景顏色,利用opencv進行通道分離和顏色空間轉換,經試驗後,總結出車牌背景色的如下特徵:測試

  (1)在HSV空間下,H份量的值一般都在115附近徘徊,S份量和V份量因光照不一樣而差別較大(opencv中H份量的取值範圍是0到179,而不是圖像學中的0到360;S份量和V份量的取值範圍是到255);spa

  (2)在RGB空間下,R份量一般較小,通常在30如下,B份量一般較大,通常在80以上,G份量波動較大;3d

  (3)在HSV空間下對圖像進行補光和加飽和度處理,即將圖像的S份量和V份量均置爲255,再進行色彩空間轉換,由HSV空間轉換爲RGB空間,發現R份量所有變爲0,B份量所有變爲255(此操做會引入較大的干擾,後續沒有使用)。code

  根據以上特徵可初步篩選出可疑的車牌區域。隨後對灰度圖進行操做,將可疑位置的像素值置爲255,其餘位置的像素值置爲0,即根據特徵對圖像進行了二值化。二值化圖像中,可疑區域用白色表示,其餘區域均爲黑色。隨後可經過膨脹腐蝕等操做對圖像進一步處理。orm

for i in range(img_h):
    for j in range(img_w):
        # 普通藍色車牌,同時排除透明反光物質的干擾
        if ((img_HSV[:, :, 0][i, j]-115)**2 < 15**2) and (img_B[i, j] > 70) and (img_R[i, j] < 40):
            img_gray[i, j] = 255
        else:
            img_gray[i, j] = 0

 

2.1.2 尋找車牌外圍輪廓

  選定可疑區域並將圖像二值化後,通常狀況下,圖像中就只有車牌位置的像素顏色爲白,但在一些特殊狀況下還會存在一些噪聲。如上圖所示,因爲圖像右上角存在藍色支架,與車牌顏色特徵相符,所以也被當作車牌識別了出來,由此引入了噪聲。blog

  通過觀察能夠發現,車牌區域與噪聲之間存在較大的差別,且車牌區域特徵比較明顯:圖片

  (1)根據我國常規車牌的形狀可知,車牌的形狀爲扁平矩形,長寬比約爲3:1;

  (2)車牌區域面積遠大於噪聲區域,通常爲圖像中最大的白色區域。

             

  能夠經過cv2.findContours()函數尋找二值化後圖像中白色區域的輪廓。

  注意:在opencv2和opencv4中,cv2.findContours()的返回值有兩個,而在opencv3中,返回值有3個。視opencv版本不一樣,代碼的寫法也會存在必定的差別。

# 檢測全部外輪廓,只留矩形的四個頂點
# opencv4.0, opencv2.x
contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# opencv3.x
_, contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

  在本例中,由於二值化圖像中共有三塊白色區域(車牌及兩處噪聲),所以返回值contours爲長度爲3的list。list內裝有3個array,每一個array內各存放着一塊白色區域的輪廓信息。每一個array的shape均爲(n, 1, 2),即每一個array存放着對應白色區域輪廓上n個點的座標。

  目前獲得了3個array,即3組輪廓信息,但咱們並不清楚其中哪一個是車牌區域對應的那一組輪廓信息。此時能夠根據車牌的上述特徵篩選出車牌區域的輪廓。

#形狀及大小篩選校驗
det_x_max = 0
det_y_max = 0
num = 0
for i in range(len(contours)):
    x_min = np.min(contours[i][ :, :, 0])
    x_max = np.max(contours[i][ :, :, 0])
    y_min = np.min(contours[i][ :, :, 1])
    y_max = np.max(contours[i][ :, :, 1])
    det_x = x_max - x_min
    det_y = y_max - y_min
    if (det_x / det_y > 1.8) and (det_x > det_x_max ) and (det_y > det_y_max ):
        det_y_max = det_y
        det_x_max = det_x
        num = i
# 獲取最可疑區域輪廓點集
points = np.array(contours[num][:, 0])

  最終獲得的points的shape爲(n, 2),即存放了n個點的座標,這n個點均分佈在車牌的邊緣上。

2.1.3 車牌區域定位

  獲取車牌輪廓上的點集後,可用cv2.minAreaRect()獲取點集的最小外接矩形。返回值rect內包含該矩形的中心點座標、高度寬度及傾斜角度等信息,使用cv2.boxPoints()可獲取該矩形的四個頂點座標。

# 獲取最小外接矩陣,中心點座標,寬高,旋轉角度
rect = cv2.minAreaRect(points)
# 獲取矩形四個頂點,浮點型
box = cv2.boxPoints(rect)
# 取整
box = np.int0(box)

  但咱們並不清楚這四個座標點各對應着矩形的哪個頂點,所以沒法充分地利用這些座標信息。

  能夠從座標值的大小特徵入手,將四個座標與矩形的四個頂點匹配起來:在opencv的座標體系下,縱座標最小的是top_point,縱座標最大的是bottom_point, 橫座標最小的是left_point,橫座標最大的是right_point。

# 獲取四個頂點座標
left_point_x = np.min(box[:, 0])
right_point_x = np.max(box[:, 0])
top_point_y = np.min(box[:, 1])
bottom_point_y = np.max(box[:, 1])

left_point_y = box[:, 1][np.where(box[:, 0] == left_point_x)][0]
right_point_y = box[:, 1][np.where(box[:, 0] == right_point_x)][0]
top_point_x = box[:, 0][np.where(box[:, 1] == top_point_y)][0]
bottom_point_x = box[:, 0][np.where(box[:, 1] == bottom_point_y)][0]
# 上下左右四個點座標
vertices = np.array([[top_point_x, top_point_y], [bottom_point_x, bottom_point_y], [left_point_x, left_point_y], [right_point_x, right_point_y]])

 

2.2 畸變校訂

 2.2.1 畸變後車牌頂點定位

  要想實現車牌的畸變矯正,必須找到畸變先後對應點的位置關係。

  能夠看出,本是矩形的車牌畸變後變成了平行四邊形,所以車牌輪廓和得出來的矩形輪廓並不契合。但有了矩形的四個頂點座標後,能夠經過簡單的幾何類似關係求出平行四邊形車牌的四個頂點座標。

  在本例中,平行四邊形四個頂點與矩形四個頂點之間有以下關係:矩形頂點Top_Point、Bottom_Point與平行四邊形頂點new_top_point、new_bottom_point重合,矩形頂點Top_Point的橫座標與平行四邊形頂點new_right_point的橫座標相同,矩形頂點Bottom_Point的橫座標與平行四邊形頂點new_left_point的橫座標相同。

  但事實上,因爲拍攝的角度不一樣,可能出現兩種不一樣的畸變狀況。能夠根據矩形傾斜角度的不一樣來判斷具體是哪一種畸變狀況。

 

   判斷出具體的畸變狀況後,選用對應的幾何類似關係,便可輕易地求出平行四邊形四個頂點座標,即獲得了畸變後車牌四個頂點的座標。

  要想實現車牌的校訂,還需獲得畸變前車牌四個頂點的座標。由於我國車牌的標準尺寸爲440X140,所以可規定畸變前車牌的四個頂點座標分別爲:(0,0),(440,0),(0,140),(440,140)。順序上需與畸變後的四個頂點座標相對應。

# 畸變狀況1
if rect[2] > -45:
    new_right_point_x = vertices[0, 0]
    new_right_point_y = int(vertices[1, 1] - (vertices[0, 0]- vertices[1, 0]) / (vertices[3, 0] - vertices[1, 0]) * (vertices[1, 1] - vertices[3, 1]))
    new_left_point_x = vertices[1, 0]
    new_left_point_y = int(vertices[0, 1] + (vertices[0, 0] - vertices[1, 0]) / (vertices[0, 0] - vertices[2, 0]) * (vertices[2, 1] - vertices[0, 1]))
    # 校訂後的四個頂點座標
    point_set_1 = np.float32([[440, 0],[0, 0],[0, 140],[440, 140]])
# 畸變狀況2
elif rect[2] < -45:
    new_right_point_x = vertices[1, 0]
    new_right_point_y = int(vertices[0, 1] + (vertices[1, 0] - vertices[0, 0]) / (vertices[3, 0] - vertices[0, 0]) * (vertices[3, 1] - vertices[0, 1]))
    new_left_point_x = vertices[0, 0]
    new_left_point_y = int(vertices[1, 1] - (vertices[1, 0] - vertices[0, 0]) / (vertices[1, 0] - vertices[2, 0]) * (vertices[1, 1] - vertices[2, 1]))
    # 校訂後的四個頂點座標
    point_set_1 = np.float32([[0, 0],[0, 140],[440, 140],[440, 0]])

# 校訂前平行四邊形四個頂點座標
new_box = np.array([(vertices[0, 0], vertices[0, 1]), (new_left_point_x, new_left_point_y), (vertices[1, 0], vertices[1, 1]), (new_right_point_x, new_right_point_y)])
point_set_0 = np.float32(new_box)

2.2.2 校訂

  該畸變是因爲攝像頭與車牌不垂直而引發的投影形成的,所以可用cv2.warpPerspective()來進行校訂。

# 變換矩陣
mat = cv2.getPerspectiveTransform(point_set_0, point_set_1)
# 投影變換
lic = cv2.warpPerspective(img, mat, (440, 140))

 

2.3 源碼分享

  1 # Created by 秋沐霖 on 2019/3/19.
  2 import cv2
  3 import numpy as np
  4 
  5 # 預處理
  6 def imgProcess(path):
  7     img = cv2.imread(path)
  8     # 統一規定大小
  9     img = cv2.resize(img, (640,480))
 10     # 高斯模糊
 11     img_Gas = cv2.GaussianBlur(img,(5,5),0)
 12     # RGB通道分離
 13     img_B = cv2.split(img_Gas)[0]
 14     img_G = cv2.split(img_Gas)[1]
 15     img_R = cv2.split(img_Gas)[2]
 16     # 讀取灰度圖和HSV空間圖
 17     img_gray = cv2.cvtColor(img_Gas, cv2.COLOR_BGR2GRAY)
 18     img_HSV = cv2.cvtColor(img_Gas, cv2.COLOR_BGR2HSV)
 19     return img, img_Gas, img_B, img_G, img_R, img_gray, img_HSV
 20 
 21 # 初步識別
 22 def preIdentification(img_gray, img_HSV, img_B, img_R):
 23     for i in range(480):
 24         for j in range(640):
 25             # 普通藍色車牌,同時排除透明反光物質的干擾
 26             if ((img_HSV[:, :, 0][i, j]-115)**2 < 15**2) and (img_B[i, j] > 70) and (img_R[i, j] < 40):
 27                 img_gray[i, j] = 255
 28             else:
 29                 img_gray[i, j] = 0
 30     # 定義核
 31     kernel_small = np.ones((3, 3))
 32     kernel_big = np.ones((7, 7))
 33 
 34     img_gray = cv2.GaussianBlur(img_gray, (5, 5), 0) # 高斯平滑
 35     img_di = cv2.dilate(img_gray, kernel_small, iterations=5) # 腐蝕5次
 36     img_close = cv2.morphologyEx(img_di, cv2.MORPH_CLOSE, kernel_big) # 閉操做
 37     img_close = cv2.GaussianBlur(img_close, (5, 5), 0) # 高斯平滑
 38     _, img_bin = cv2.threshold(img_close, 100, 255, cv2.THRESH_BINARY) # 二值化
 39     return img_bin
 40 
 41 # 定位
 42 def fixPosition(img, img_bin):
 43     # 檢測全部外輪廓,只留矩形的四個頂點
 44     contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
 45     #形狀及大小篩選校驗
 46     det_x_max = 0
 47     det_y_max = 0
 48     num = 0
 49     for i in range(len(contours)):
 50         x_min = np.min(contours[i][ :, :, 0])
 51         x_max = np.max(contours[i][ :, :, 0])
 52         y_min = np.min(contours[i][ :, :, 1])
 53         y_max = np.max(contours[i][ :, :, 1])
 54         det_x = x_max - x_min
 55         det_y = y_max - y_min
 56         if (det_x / det_y > 1.8) and (det_x > det_x_max ) and (det_y > det_y_max ):
 57             det_y_max = det_y
 58             det_x_max = det_x
 59             num = i
 60     # 獲取最可疑區域輪廓點集
 61     points = np.array(contours[num][:, 0])
 62     return points
 63 
 64 
 65 #img_lic_canny = cv2.Canny(img_lic_bin, 100, 200)
 66 
 67 
 68 def findVertices(points):
 69     # 獲取最小外接矩陣,中心點座標,寬高,旋轉角度
 70     rect = cv2.minAreaRect(points)
 71     # 獲取矩形四個頂點,浮點型
 72     box = cv2.boxPoints(rect)
 73     # 取整
 74     box = np.int0(box)
 75     # 獲取四個頂點座標
 76     left_point_x = np.min(box[:, 0])
 77     right_point_x = np.max(box[:, 0])
 78     top_point_y = np.min(box[:, 1])
 79     bottom_point_y = np.max(box[:, 1])
 80 
 81     left_point_y = box[:, 1][np.where(box[:, 0] == left_point_x)][0]
 82     right_point_y = box[:, 1][np.where(box[:, 0] == right_point_x)][0]
 83     top_point_x = box[:, 0][np.where(box[:, 1] == top_point_y)][0]
 84     bottom_point_x = box[:, 0][np.where(box[:, 1] == bottom_point_y)][0]
 85     # 上下左右四個點座標
 86     vertices = np.array([[top_point_x, top_point_y], [bottom_point_x, bottom_point_y], [left_point_x, left_point_y], [right_point_x, right_point_y]])
 87     return vertices, rect
 88 
 89 def tiltCorrection(vertices, rect):
 90     # 畸變狀況1
 91     if rect[2] > -45:
 92         new_right_point_x = vertices[0, 0]
 93         new_right_point_y = int(vertices[1, 1] - (vertices[0, 0]- vertices[1, 0]) / (vertices[3, 0] - vertices[1, 0]) * (vertices[1, 1] - vertices[3, 1]))
 94         new_left_point_x = vertices[1, 0]
 95         new_left_point_y = int(vertices[0, 1] + (vertices[0, 0] - vertices[1, 0]) / (vertices[0, 0] - vertices[2, 0]) * (vertices[2, 1] - vertices[0, 1]))
 96         # 校訂後的四個頂點座標
 97         point_set_1 = np.float32([[440, 0],[0, 0],[0, 140],[440, 140]])
 98     # 畸變狀況2
 99     elif rect[2] < -45:
100         new_right_point_x = vertices[1, 0]
101         new_right_point_y = int(vertices[0, 1] + (vertices[1, 0] - vertices[0, 0]) / (vertices[3, 0] - vertices[0, 0]) * (vertices[3, 1] - vertices[0, 1]))
102         new_left_point_x = vertices[0, 0]
103         new_left_point_y = int(vertices[1, 1] - (vertices[1, 0] - vertices[0, 0]) / (vertices[1, 0] - vertices[2, 0]) * (vertices[1, 1] - vertices[2, 1]))
104         # 校訂後的四個頂點座標
105         point_set_1 = np.float32([[0, 0],[0, 140],[440, 140],[440, 0]])
106 
107     # 校訂前平行四邊形四個頂點座標
108     new_box = np.array([(vertices[0, 0], vertices[0, 1]), (new_left_point_x, new_left_point_y), (vertices[1, 0], vertices[1, 1]), (new_right_point_x, new_right_point_y)])
109     point_set_0 = np.float32(new_box)
110     return point_set_0, point_set_1, new_box
111 
112 def transform(img, point_set_0, point_set_1):
113     # 變換矩陣
114     mat = cv2.getPerspectiveTransform(point_set_0, point_set_1)
115     # 投影變換
116     lic = cv2.warpPerspective(img, mat, (440, 140))
117     return lic
118 
119 def main():
120     path = 'F:\\Python\\license_plate\\test\\9.jpg'
121     # 圖像預處理
122     img, img_Gas, img_B, img_G, img_R, img_gray, img_HSV = imgProcess(path)
123     # 初步識別
124     img_bin  = preIdentification(img_gray, img_HSV, img_B, img_R)
125     points = fixPosition(img, img_bin)
126     vertices, rect = findVertices(points)
127     point_set_0, point_set_1, new_box = tiltCorrection(vertices, rect)
128     img_draw = cv2.drawContours(img.copy(), [new_box], -1, (0,0,255), 3)
129     lic = transform(img, point_set_0, point_set_1)
130     # 原圖上框出車牌
131     cv2.namedWindow("Image")
132     cv2.imshow("Image", img_draw)
133     # 二值化圖像
134     cv2.namedWindow("Image_Bin")
135     cv2.imshow("Image_Bin", img_bin)
136     # 顯示校訂後的車牌
137     cv2.namedWindow("Lic")
138     cv2.imshow("Lic", lic)
139     # 暫停、關閉窗口
140     cv2.waitKey(0)
141     cv2.destroyAllWindows()
142 
143 if __name__ == '__main__':
144     main()
View Code

 3、後記

  因爲能力學識有限,代碼只能識別最多見的藍色車牌,對其餘車牌無識別能力,後續會持續改進完善項目。最後附上幾張實驗圖片:

 

    

  

    

  

相關文章
相關標籤/搜索