目錄
試錯
1、沒有膨脹/膨脹過小:無法連接上單個字符,
2、膨脹過大:錯誤連接相鄰字符,
一、直方圖處理原理
1、橫向分割
2、縱向分割
程序:
一、中值濾波、灰度化
二、二值化(統一黑底白字)
三、膨脹處理
四、統計各行各列白色像素個數(為繪制直方圖做準備)
五、繪制直方圖(橫、縱)
六、分割車牌影像
1、橫向分割:分割上下邊框
1-1、下半圖波谷
1-2、上半圖波谷
代碼 及效果
2、縱向分割:分割字符
2-0、極大值判斷(去除左右兩邊過大的噪聲)
2-1、前谷(前一個波谷)
2-2、字符開始(上升)
2-3、字符結束
2-4、分割尾部雜質
2-5、最后的處理:檢查收尾情況
總代碼及效果
參考資料
試錯
一開始嘗試的是形態學提取字符,但發現有些情況是相悖的,會顧此失彼,無法兼顧很多情況,
1、沒有膨脹/膨脹過小:無法連接上單個字符,
可以看到,一些沒有連接上的漢字,輪廓提取就把它們當成了多個字符,


2、膨脹過大:錯誤連接相鄰字符,
給予膨脹之后,膨脹又太大了,雖然同一個字符的不同筆畫連接成功,但是又錯誤地把兩個字符連接在了一起,


上面的演算法感覺很難行得通了,除非借助其他分割演算法,下面是優化后的更佳演算法,用直方圖處理,
一、直方圖處理原理

1、橫向分割
分割上下邊框
把影像分為上下兩部分,根據像素點的個數,找到最小的像素行(最小波谷), 上半部分的最小波谷作為頂,下半部分的最小波谷作為底,


2、縱向分割
切割字符,
每一個字符前面都一定會有波谷出現,波谷出現再上升的時候,作為字符開始的判斷標志,


程序:
一、中值濾波、灰度化
# 1、中值濾波
mid = cv.medianBlur(image, 5)
# 2、灰度化
gray = cv.cvtColor(mid, cv.COLOR_BGR2GRAY)
二、二值化(統一黑底白字)
# 3、二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)
# 統一得到黑底白字
if(IsWhiteMore(binary)): #白色部分多則為真,意味著背景是白色,需要黑底白字
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU | cv.THRESH_BINARY_INV)
cv.imshow('binary', binary)
# 得到黑底白字(白色多則回傳真)
def IsWhiteMore(binary):
white = black = 0
height, width = binary.shape
# 遍歷每個像素
for i in range(height):
for j in range(width):
if binary[i,j]==0:
black+=1
else:
white+=1
if white >= black:
return True
else:
return False

三、膨脹處理
主要就是為了連接像“川”這樣的橫向不連接的字符,
# 4、膨脹(粘貼橫向字符)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (7,1)) #橫向連接字符
dilate = cv.dilate(binary, kernel)
cv.imshow('dilate', dilate)

四、統計各行各列白色像素個數(為繪制直方圖做準備)
# 5、統計各行各列白色像素個數(為了得到直方圖橫縱坐標)
ptx, pty = White_Statistic(dilate)
# 二-5、統計白色像素點(分別統計每一行、每一列)
def White_Statistic(image):
ptx = [] # 每行白色像素個數
pty = [] # 每列白色像素個數
height, width = image.shape
# 逐行遍歷
for i in range(height):
num = 0
for j in range(width):
if(image[i][j]==255):
num = num+1
ptx.append(num)
# 逐列遍歷
for i in range(width):
num = 0
for j in range(height):
if (image[j][i] == 255):
num = num + 1
pty.append(num)
return ptx, pty
五、繪制直方圖(橫、縱)
有了直方圖,可以很直觀地看出每一行、每一列的像素分布情況,
# 6、繪制直方圖(橫、縱)
Draw_Hist(ptx, pty)
# 二-6、繪制直方圖
def Draw_Hist(ptx, pty):
# 依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 橫向直方圖
plt.barh(row, ptx, color='black', height=1)
# 縱 橫
plt.show()
# 縱向直方圖
plt.bar(col, pty, color='black', width=1)
# 橫 縱
plt.show()


六、分割車牌影像
# 二-7、分割車牌影像(根據直方圖)
def Cut_Image(ptx, pty, binary, dilate):
h1 = h2 = 0
#頂 底
begin = False #標記開始/結束
# 1、依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 2、橫向分割:上下邊框
h1, h2 = Cut_X(ptx, rows)
# 3、縱向分割:分割字符
Cut_Y(pty, cols, h1, h2, binary)
1、橫向分割:分割上下邊框
把影像分為兩部分:上半圖和下半圖,分別找它們的波谷,就可以確定字符上下邊緣,進行分割,
1-1、下半圖波谷
在下半圖找波谷,確定字符的下邊緣,
# 1、下半圖波谷
min, r = 300, 0
for i in range(int(rows / 2)):
if ptx[i] < min:
min = ptx[i]
r = i
h1 = r # 添加下行(作為頂)
1-2、上半圖波谷
在上半圖找波谷,確定字符的上邊緣,
# 2、上半圖波谷
min, r = 300, 0
for i in range(int(rows / 2), rows):
if ptx[i] < min:
min = ptx[i]
r = i
h2 = r # 添加上行(作為底)
代碼 及效果
# 2、橫向分割:上下邊框
h1, h2 = Cut_X(ptx, rows)
cut_x = binary[h1:h2, :]
cv.imshow('cut_x', cut_x)
# 二-7-2、橫向分割:上下邊框
def Cut_X(ptx, rows):
# 橫向切割(分為上下兩張圖,分別找其波谷,確定頂和底)
# 1、下半圖波谷
min, r = 300, 0
for i in range(int(rows / 2)):
if ptx[i] < min:
min = ptx[i]
r = i
h1 = r # 添加下行(作為頂)
# 2、上半圖波谷
min, r = 300, 0
for i in range(int(rows / 2), rows):
if ptx[i] < min:
min = ptx[i]
r = i
h2 = r # 添加上行(作為底)
return h1, h2
可以看到,上下邊框明顯得到了適當的切割,尤其是下邊框,


2、縱向分割:分割字符
2-0、極大值判斷(去除左右兩邊過大的噪聲)
如果在左邊或者右邊出現極大值(左右按10%計算),則視為噪聲,進行處理,




# 0、極大值判斷
if pty[j] == max(pty):
if j < 30: # 左邊(跳過)
w2 = j
if begin == True:
begin = False
continue
elif j > 270: # 右邊(直接收尾)
if begin == True:
begin = False
w2 = j
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
break
2-1、前谷(前一個波谷)
# 1、前谷(前面的波谷)
if pty[j] < 12 and begin == False: # 前谷判斷:像素數量<12
last = pty[j]
w = j

2-2、字符開始(上升)
# 2、字符開始(上升)
elif last < 12 and pty[j] > 20:
last = pty[j]
w1 = j
begin = True

2-3、字符結束
情景一:單個字符:直接分割(判斷陳述句中過濾掉噪聲)
# 3-1、分割并顯示(排除過小情況)
if 10 < width < WIDTH + 3: # 要排除掉干擾,又不能過濾掉字符”1“
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
情景二:多個字符:從多字符中分割單字符

# 3-2、從多個貼合字符中提取單個字符
elif width >= WIDTH + 3:
# 統計貼合字符個數
num = int(width / WIDTH + 0.5) # 四舍五入
for k in range(num):
# w1和w2坐標向后移(用w3、w4代替w1和w2)
w3 = w1 + k * WIDTH
w4 = w1 + (k + 1) * WIDTH
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w3:w4]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
2-4、分割尾部雜質
由于車牌提取的時候沒處理好,導致車牌尾部有些許雜質 ,


# 4、分割尾部噪聲(距離過遠默認沒有字符了)
elif begin == False and (j - w2) > 30:
break
2-5、最后的處理:檢查收尾情況
如果最后沒有下降沿(波谷),那么它就不會判斷為字符,那么可能會遺漏掉最后一個字符,所以我們進行一個收尾的操作,

# 最后檢查收尾情況
if begin == True:
w2 = 295
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
總代碼及效果
# 車牌識別
import cv2 as cv
import numpy as np
import os
from matplotlib import pyplot as plt
# 得到黑底白字(白色多則回傳真)
def IsWhiteMore(binary):
white = black = 0
height, width = binary.shape
# 遍歷每個像素
for i in range(height):
for j in range(width):
if binary[i,j]==0:
black+=1
else:
white+=1
if white >= black:
return True
else:
return False
# 限制影像大小(車牌)
def Limit(image):
height, width, channel = image.shape
# 設定權重
weight = width/300
# 計算輸出影像的寬和高
last_width = int(width/weight)
last_height = int(height/weight)
image = cv.resize(image, (last_width, last_height))
return image
# 二-5、統計白色像素點(分別統計每一行、每一列)
def White_Statistic(image):
ptx = [] # 每行白色像素個數
pty = [] # 每列白色像素個數
height, width = image.shape
# 逐行遍歷
for i in range(height):
num = 0
for j in range(width):
if(image[i][j]==255):
num = num+1
ptx.append(num)
# 逐列遍歷
for i in range(width):
num = 0
for j in range(height):
if (image[j][i] == 255):
num = num + 1
pty.append(num)
return ptx, pty
# 二-6、繪制直方圖
def Draw_Hist(ptx, pty):
# 依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 橫向直方圖
plt.barh(row, ptx, color='black', height=1)
# 縱 橫
plt.show()
# 縱向直方圖
plt.bar(col, pty, color='black', width=1)
# 橫 縱
plt.show()
# 二-7-2、橫向分割:上下邊框
def Cut_X(ptx, rows):
# 橫向切割(分為上下兩張圖,分別找其波谷,確定頂和底)
# 1、下半圖波谷
min, r = 300, 0
for i in range(int(rows / 2)):
if ptx[i] < min:
min = ptx[i]
r = i
h1 = r # 添加下行(作為頂)
# 2、上半圖波谷
min, r = 300, 0
for i in range(int(rows / 2), rows):
if ptx[i] < min:
min = ptx[i]
r = i
h2 = r # 添加上行(作為底)
return h1, h2
# 二-7-3、縱向分割:分割字符
def Cut_Y(pty, cols, h1, h2, binary):
WIDTH = 32 # 經過測驗,一個字符寬度約為32
w = w1 = w2 = 0 # 前谷 字符開始 字符結束
begin = False # 字符開始標記
last = 10 # 上一次的值
con = 0 # 計數
# 縱向切割(正式切割字符)
for j in range(int(cols)):
# 0、極大值判斷
if pty[j] == max(pty):
if j < 30: # 左邊(跳過)
w2 = j
if begin == True:
begin = False
continue
elif j > 270: # 右邊(直接收尾)
if begin == True:
begin = False
w2 = j
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
break
# 1、前谷(前面的波谷)
if pty[j] < 12 and begin == False: # 前谷判斷:像素數量<12
last = pty[j]
w = j
# 2、字符開始(上升)
elif last < 12 and pty[j] > 20:
last = pty[j]
w1 = j
begin = True
# 3、字符結束
elif pty[j] < 13 and begin == True:
begin = False
last = pty[j]
w2 = j
width = w2 - w1
# 3-1、分割并顯示(排除過小情況)
if 10 < width < WIDTH + 3: # 要排除掉干擾,又不能過濾掉字符”1“
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
# 3-2、從多個貼合字符中提取單個字符
elif width >= WIDTH + 3:
# 統計貼合字符個數
num = int(width / WIDTH + 0.5) # 四舍五入
for k in range(num):
# w1和w2坐標向后移(用w3、w4代替w1和w2)
w3 = w1 + k * WIDTH
w4 = w1 + (k + 1) * WIDTH
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w3:w4]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
con += 1
# 4、分割尾部噪聲(距離過遠默認沒有字符了)
elif begin == False and (j - w2) > 30:
break
# 最后檢查收尾情況
if begin == True:
w2 = 295
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
# 二-7、分割車牌影像(根據直方圖)
def Cut_Image(ptx, pty, binary, dilate):
h1 = h2 = 0
#頂 底
begin = False #標記開始/結束
# 1、依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 2、橫向分割:上下邊框
h1, h2 = Cut_X(ptx, rows)
# cut_x = binary[h1:h2, :]
# cv.imshow('cut_x', cut_x)
# 3、縱向分割:分割字符
Cut_Y(pty, cols, h1, h2, binary)
# 一、形態學提取車牌
def Get_Licenses(image):
# 1、轉灰度圖
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# cv.imshow('gray', gray)
# 2、頂帽運算
# gray = cv.equalizeHist(gray)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (17,17))
tophat = cv.morphologyEx(gray, cv.MORPH_TOPHAT, kernel)
# cv.imshow('tophat', tophat)
# 3、Sobel算子提取y方向邊緣(揉成一坨)
y = cv.Sobel(tophat, cv.CV_16S, 1, 0)
absY = cv.convertScaleAbs(y)
# cv.imshow('absY', absY)
# 4、自適應二值化(閾值自己可調)
ret, binary = cv.threshold(absY, 75, 255, cv.THRESH_BINARY)
# cv.imshow('binary', binary)
# 5、開運算分割(縱向去噪,分隔)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 15))
Open = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
# cv.imshow('Open', Open)
# 6、閉運算合并,把影像閉合、揉團,使影像區域化,便于找到車牌區域,進而得到輪廓
kernel = cv.getStructuringElement(cv.MORPH_RECT, (41, 15))
close = cv.morphologyEx(Open, cv.MORPH_CLOSE, kernel)
# cv.imshow('close', close)
# 7、膨脹/腐蝕(去噪得到車牌區域)
# 中遠距離車牌識別
kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (25, 7))
kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 11))
# 近距離車牌識別
# kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (79, 15))
# kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 31))
# 7-1、腐蝕、膨脹(去噪)
erode_y = cv.morphologyEx(close, cv.MORPH_ERODE, kernel_y)
# cv.imshow('erode_y', erode_y)
dilate_y = cv.morphologyEx(erode_y, cv.MORPH_DILATE, kernel_y)
# cv.imshow('dilate_y', dilate_y)
# 7-1、膨脹、腐蝕(連接)(二次縫合)
dilate_x = cv.morphologyEx(dilate_y, cv.MORPH_DILATE, kernel_x)
# cv.imshow('dilate_x', dilate_x)
erode_x = cv.morphologyEx(dilate_x, cv.MORPH_ERODE, kernel_x)
# cv.imshow('erode_x', erode_x)
# 8、腐蝕、膨脹:去噪
kernel_e = cv.getStructuringElement(cv.MORPH_RECT, (25, 9))
erode = cv.morphologyEx(erode_x, cv.MORPH_ERODE, kernel_e)
# cv.imshow('erode', erode)
kernel_d = cv.getStructuringElement(cv.MORPH_RECT, (25, 11))
dilate = cv.morphologyEx(erode, cv.MORPH_DILATE, kernel_d)
# cv.imshow('dilate', dilate)
# 9、獲取外輪廓
img_copy = image.copy()
# 9-1、得到輪廓
contours, hierarchy = cv.findContours(dilate, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 9-2、畫出輪廓并顯示
cv.drawContours(img_copy, contours, -1, (255, 0, 255), 2)
# cv.imshow('Contours', img_copy)
# 10、遍歷所有輪廓,找到車牌輪廓
i = 0
for contour in contours:
# 10-1、得到矩形區域:左頂點坐標、寬和高
rect = cv.boundingRect(contour)
# 10-2、判斷寬高比例是否符合車牌標準,截取符合圖片
if rect[2]>rect[3]*3 and rect[2]<rect[3]*7:
# 截取車牌并顯示
print(rect)
img_copy = image.copy()
image = image[(rect[1]):(rect[1]+rect[3]), (rect[0]):(rect[0]+rect[2])] #高,寬
try:
# 限制大小(按照比例限制)
image = Limit(image)
cv.imshow('license plate%d-%d' % (count, i), image)
cv.imwrite('car_licenses/image%d-%d.jpg'%(count, i), image)
i += 1
return image
except:
pass
return image
# 二、直方圖提取字符
def Get_Character(image):
# 1、中值濾波
mid = cv.medianBlur(image, 5)
# 2、灰度化
gray = cv.cvtColor(mid, cv.COLOR_BGR2GRAY)
# 3、二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)
# 統一得到黑底白字
if(IsWhiteMore(binary)): #白色部分多則為真,意味著背景是白色,需要黑底白字
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU | cv.THRESH_BINARY_INV)
cv.imshow('binary', binary)
# 4、膨脹(粘貼橫向字符)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (7,1)) #橫向連接字符
dilate = cv.dilate(binary, kernel)
# cv.imshow('dilate', dilate)
# 5、統計各行各列白色像素個數(為了得到直方圖橫縱坐標)
ptx, pty = White_Statistic(dilate)
# 6、繪制直方圖(橫、縱)
Draw_Hist(ptx, pty)
# 7、分割(橫、縱)(橫向分割邊框、縱向分割字符)
Cut_Image(ptx, pty, binary, dilate)
# cv.waitKey(0)
if __name__ == '__main__':
global count
count=0 #計數:第幾張圖片
# 遍歷檔案夾中的每張圖片(車)
for car in os.listdir('cars'):
# 1、獲取路徑
path = 'cars/'+'car'+str(count)+'.jpg'
# 2、獲取圖片
img = cv.imread(path)
image = img.copy()
# cv.imshow('image', image)
# 3、提取車牌
image = Get_Licenses(image) #形態學提取車牌
# 4、提取字符
Get_Character(image)
count += 1
cv.waitKey(0)


(“滬”前面的痕跡由于提取的程序處理不到位,這里實在是不好處理,直方圖上和它都連一起了,所以無能為力,有辦法的伙伴可以建議建議)


(好家伙,直方圖上看完全就是一個字了,,,)
后面的一部分車牌提取都做的不到位,字符也分離自然不是很好,目前做的還挺菜的,有好建議的伙伴可以提出來,
參考資料
https://www.bilibili.com/video/BV1yg4y187kU?p=2
https://blog.csdn.net/shanglianlm/article/details/78045170?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163037372316780271535395%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163037372316780271535395&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-4-78045170.first_rank_v2_pc_rank_v29&utm_term=%E8%BD%A6%E7%89%8C%E8%AF%86%E5%88%AB%E5%AD%97%E7%AC%A6%E5%88%86%E5%89%B2%E6%96%B9%E6%B3%95&spm=1018.2226.3001.4187
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/297388.html
標籤:其他
下一篇:Java程式員必讀的書籍有哪些?
