Opencv之python下車牌識別
目錄
七、根據contours資訊,構建外界矩形,並判斷該輪廓是否合理
八、對合理矩形(區域),進行floodFill泛洪處理 & 綜合後續
基於https://blog.csdn.net/jinshengtao/article/details/17883075該部落格下的Python實現
一、讀入原始圖片,灰度處理
# Step1 讀入灰度圖
initial_car = cv2.imread(r'F:\ml_summer\Opencv\Image\car.jpg') #(600, 800, 3) 行,列,通道數
gray_car = cv2.cvtColor(initial_car,cv2.COLOR_BGR2GRAY)
原始影象
灰度影象
二、高斯模糊處理,去噪
採用5*5模版對影象進行高斯模糊來退出由照相機或其他環境噪聲(如果不這麼做,我們會得到很多垂直邊緣,導致錯誤檢測。)
# Step2 高斯模糊處理 blur_car = cv2.GaussianBlur(gray_car,(5,5),0)
關於
cv2.GaussianBlur(img,kernel_size,sigMax)
函式的具體情況,參見https://blog.csdn.net/qq_37385726/article/details/82020214
三、Sobel濾波,邊緣檢測
為了識別出車牌這個資訊,我們有效利用車牌矩形的特徵,邊緣資訊明顯,故我們使用Sobel邊緣檢測的方法進行邊緣的識別
cv2.Sobel(img,dtype,dx,dy) 【dx是進行垂直邊緣檢測,dy是對於水平邊緣檢測】
關於Sobel邊緣檢測,參見https://blog.csdn.net/qq_37385726/article/details/82020725
#Step3 Sobel計算水平導數
sobel_car = cv2.Sobel(blur_car,cv2.CV_16S,1,0)
sobel_car = cv2.convertScaleAbs(sobel_car) #轉回uint8
四、 Otsu大津演算法自適應閾值二值化處理
為去除掉背景的噪聲,單獨的處理車牌這一目標物件,我們將原始的灰度影象進行二值化處理。
利用Otus演算法進行二值處理,利用
cv2.threshold(img,threshold, maxval,type)
實現,具體參見https://blog.csdn.net/qq_37385726/article/details/82015545 (固定閾值的二值化處理可以實現大津演算法)
更多過於二值化處理(自適應閾值二值化處理)參見https://blog.csdn.net/qq_37385726/article/details/82017177
#Step4 Otsu大津演算法自適應閾值二值化
_, otsu_car = cv2.threshold(sobel_car,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)
五、形態學操作,閉操作
利用形態學下的閉操作,將剛得到的二值化影象進行閉操作,消除黑色小塊,填充閉合區域,將車牌區域連線起來,將車牌區域變成連通的區域,以便之後在輪廓提取的時候能將車牌作為一個區域提取出來
關於閉操作,參見https://blog.csdn.net/qq_37385726/article/details/82021970
#Step5 閉操作
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(8,8))
close_car = cv2.morphologyEx(otsu_car,cv2.MORPH_CLOSE,kernel)
六、輪廓提取
從閉操作得到的結果影象,提取影象中的輪廓資訊(點集),該點集是有一個list包裹的,list中的每一個元素都是一個numpy.ndarray 點的集合。該點的集合就是我們提取到的一個區域的輪廓資訊。
關於
cv2.findContours()
參見
#Step6 提取外部輪廓
img, contours, hierarchy = cv2.findContours(close_car,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
其中contours就是我們提取到的點集list
七、根據contours資訊,構建外界矩形,並判斷該輪廓是否合理
因為車牌是規則的矩形,有其長寬的資訊,我們可以對構建的外接矩形的長寬資訊進行處理來判斷該外接矩形是否和車牌接近。
【閾值:長寬比為4.727272,允許誤差範圍正負40%,面積範圍15*15至125*125】
故定義verifySizes函式
# 對minAreaRect獲得的最小外接矩形,用縱橫比進行判斷
def verifySizes(RotatedRect):
error = 0.4
aspect = 4.7272
min = 15 * aspect * 15
max = 125 * aspect * 125
rmin = aspect - aspect * error
rmax = aspect + aspect * error
height,width = RotatedRect[1]
if height==0 or width==0:
return False
area = height * width
r = width/height
if r < 1:
r = height/width
if (area < min or area > max) or (r < rmin or r > rmax):
return False
else:
return True
依據定義的驗證函式,使用for迴圈一次遍歷輪廓大點集中的點集集合
# 對minAreaRect獲得的最小外接矩形,用縱橫比進行判斷
save = [] #儲存合理輪廓
rectall = [] #儲存對應的在最小面積矩形
for contour in contours:
rect = cv2.minAreaRect(contour)
if verifySizes(rect):
save.append(contour)
rectall.append(rect)
下圖為經過驗證函式處理後得到的合理輪廓的點集,畫成的輪廓圖
cv2.drawContours(initial_car,save,-1,(0,0,255),2)
八、對合理矩形(區域),進行floodFill泛洪處理 & 綜合後續
為了進一步提高效果,因為剛得到的合理的矩形所包括的車牌區域可能並不完整,所以我們使用泛洪處理來將得到的區域更為完整
step 1 : 利用合理矩形的中心點(rect[0])為中心,生成十個周圍的隨機種子點
Step 2:對生成的十個隨機種子點,依次使用cv2.floodFill泛洪演算法進行處理
生成的隨機種子點圖片(種子點:黃色,以矩形中心作圓:紅色)
關於
cv2.floodFill(img,mask,(seed_x,seed_y),newvalue(b,g,r),(loDiff,loDiff,loDiff),(upDiff,upDiff,upDiff),flag)
的詳細資訊,參見https://blog.csdn.net/qq_37385726/article/details/82313004
- 我們使用floodFill演算法,對掩碼層進行處理,則我們可以通過掩碼層中被標記為255的畫素點來判斷是不是目標區域。
- 再將目標區域的點都儲存成一個新點集,使用minAreaRect函式來提取最小面積矩形。
- 對最小面積矩形再次應用verifySize函式進行處理,得到合理的矩形
- 對得到的合理矩形的區域,在原圖上進行影象切割
- 對切割後的影象,進行高斯模糊,去噪,直方圖均衡化處理
- 儲存
#Step7 得到矩形中心附近隨機數點
for step,rect in enumerate(rectall):
x,y = rect[0] #x:列數,y:行數
x = int(x)
y = int(y)
cv2.circle(initial_car,(x,y),3,(0,255,0),2)
width, height = rect[1]
minimum = width if width<height else height
minimum = 0.5*minimum
h,w=initial_car.shape[:2] #600,
mask = np.zeros((h + 2, w + 2), dtype=np.uint8)
for i in range(10):
seed_x = int(x+0.5*(np.random.random_integers(0,100000)%int(minimum)-(minimum/2)))
seed_y = int(y+0.5*(np.random.random_integers(0,100000)%int(minimum)-(minimum/2)))
cv2.circle(initial_car,(seed_x,seed_y),1,(0,255,255))
loDiff = 7.95
upDiff = 30
Connectivity = 4
flag = Connectivity + (255<<8) + cv2.FLOODFILL_MASK_ONLY
cv2.floodFill(initial_car,mask,(seed_x,seed_y),(255,0,0),(loDiff,loDiff,loDiff),(upDiff,upDiff,upDiff),flag)
# cv2.imshow(str(step),mask)
points = []
row,column = mask.shape
for i in range(row):
for j in range(column):
if mask[i][j]==255:
points.append((j,i)) #點應該輸入點座標(列,行)
points = np.asarray(points)
new_rect = cv2.minAreaRect(points)
if verifySizes(new_rect):
# 寬,高
x,y = new_rect[0]
new_width, new_height = new_rect[1]
angel = new_rect[2]
point1 = cv2.boxPoints(new_rect)[0]
point2 = cv2.boxPoints(new_rect)[1]
point3 = cv2.boxPoints(new_rect)[2]
point4 = cv2.boxPoints(new_rect)[3]
# cv2.line(initial_car,tuple(point1),tuple(point2),(255,255,255),2)
# cv2.line(initial_car, tuple(point2), tuple(point3), (255, 255, 255), 2)
# cv2.line(initial_car, tuple(point3), tuple(point4), (255, 255, 255), 2)
# cv2.line(initial_car, tuple(point4), tuple(point1), (255, 255, 255), 2) #width
rotate = cv2.getRotationMatrix2D((x,y),90+angel,1)
res = cv2.warpAffine(initial_car,rotate,initial_car.shape[:2])
#img,(列,行),(中心)
res = cv2.getRectSubPix(res,(int(new_height),int(new_width)),(x,y))
#img,(列,行)
res = cv2.resize(res,(105,25),interpolation=cv2.INTER_AREA)
res = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
res = cv2.GaussianBlur(res,(3,3),0)
res = cv2.equalizeHist(res)
path = './Image/Sample/sample_'+str(step)+'.jpg'
cv2.imwrite(path,res)
九、完整程式碼
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Step1 讀入灰度圖
initial_car = cv2.imread(r'F:\ml_summer\Opencv\Image\car.jpg') #(600, 800, 3) 行,列,通道數
gray_car = cv2.cvtColor(initial_car,cv2.COLOR_BGR2GRAY)
# Step2 高斯模糊處理
blur_car = cv2.GaussianBlur(gray_car,(5,5),0)
#Step3 Sobel計算水平導數
sobel_car = cv2.Sobel(blur_car,cv2.CV_16S,1,0)
sobel_car = cv2.convertScaleAbs(sobel_car) #轉回uint8
#Step4 Otsu大津演算法自適應閾值二值化
_, otsu_car = cv2.threshold(sobel_car,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)
#Step5 閉操作
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(8,8))
close_car = cv2.morphologyEx(otsu_car,cv2.MORPH_CLOSE,kernel)
# cv2.imshow('sss',close_car)
#Step6 提取外部輪廓
img, contours, hierarchy = cv2.findContours(close_car,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
save = [] #儲存合理輪廓
rectall = [] #儲存對應的在最小面積矩形
# 對minAreaRect獲得的最小外接矩形,用縱橫比進行判斷
def verifySizes(RotatedRect):
error = 0.4
aspect = 4.7272
min = 15 * aspect * 15
max = 125 * aspect * 125
rmin = aspect - aspect * error
rmax = aspect + aspect * error
height,width = RotatedRect[1]
if height==0 or width==0:
return False
area = height * width
r = width/height
if r < 1:
r = height/width
if (area < min or area > max) or (r < rmin or r > rmax):
return False
else:
return True
for contour in contours:
rect = cv2.minAreaRect(contour)
if verifySizes(rect):
save.append(contour)
rectall.append(rect)
# cv2.drawContours(initial_car,save,-1,(0,0,255),2)
#Step7 得到矩形中心附近隨機數點
for step,rect in enumerate(rectall):
x,y = rect[0] #x:列數,y:行數
x = int(x)
y = int(y)
cv2.circle(initial_car,(x,y),3,(0,255,0),2)
width, height = rect[1]
minimum = width if width<height else height
minimum = 0.5*minimum
h,w=initial_car.shape[:2] #600,
mask = np.zeros((h + 2, w + 2), dtype=np.uint8)
for i in range(10):
seed_x = int(x+0.5*(np.random.random_integers(0,100000)%int(minimum)-(minimum/2)))
seed_y = int(y+0.5*(np.random.random_integers(0,100000)%int(minimum)-(minimum/2)))
cv2.circle(initial_car,(seed_x,seed_y),1,(0,255,255))
loDiff = 7.95
upDiff = 30
Connectivity = 4
flag = Connectivity + (255<<8) + cv2.FLOODFILL_MASK_ONLY
cv2.floodFill(initial_car,mask,(seed_x,seed_y),(255,0,0),(loDiff,loDiff,loDiff),(upDiff,upDiff,upDiff),flag)
# cv2.imshow(str(step),mask)
points = []
row,column = mask.shape
for i in range(row):
for j in range(column):
if mask[i][j]==255:
points.append((j,i)) #點應該輸入點座標(列,行)
points = np.asarray(points)
new_rect = cv2.minAreaRect(points)
if verifySizes(new_rect):
# 寬,高
x,y = new_rect[0]
new_width, new_height = new_rect[1]
angel = new_rect[2]
point1 = cv2.boxPoints(new_rect)[0]
point2 = cv2.boxPoints(new_rect)[1]
point3 = cv2.boxPoints(new_rect)[2]
point4 = cv2.boxPoints(new_rect)[3]
# cv2.line(initial_car,tuple(point1),tuple(point2),(255,255,255),2)
# cv2.line(initial_car, tuple(point2), tuple(point3), (255, 255, 255), 2)
# cv2.line(initial_car, tuple(point3), tuple(point4), (255, 255, 255), 2)
# cv2.line(initial_car, tuple(point4), tuple(point1), (255, 255, 255), 2) #width
rotate = cv2.getRotationMatrix2D((x,y),90+angel,1)
res = cv2.warpAffine(initial_car,rotate,initial_car.shape[:2])
#img,(列,行),(中心)
res = cv2.getRectSubPix(res,(int(new_height),int(new_width)),(x,y))
#img,(列,行)
res = cv2.resize(res,(105,25),interpolation=cv2.INTER_AREA)
res = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
res = cv2.GaussianBlur(res,(3,3),0)
res = cv2.equalizeHist(res)
# 繪製直方圖
# hist = cv2.calcHist([res],[0],None,[256],[0,255])
# plt.plot(hist,'r')
# plt.hist(res.ravel(), 256, [0, 256],color='r')
# plt.show()
path = './Image/Sample/sample_'+str(step)+'.jpg'
cv2.imwrite(path,res)
# cv2.imshow('now',initial_car)
cv2.waitKey(0)