一、實(shí)驗(yàn)?zāi)康?/h2>
(1)掌握車(chē)牌閾值分割;
(2)掌握基于形態(tài)學(xué)計(jì)算的圖像分割;
(3)掌握?qǐng)D像的二值化;
(4)掌握基于像素投影的字符分割;
(5)掌握字符識(shí)別原理。
二、實(shí)驗(yàn)主要儀器設(shè)備
(1)計(jì)算機(jī);
(2)Python 3.x及PyCharm軟件;
(3)需進(jìn)行車(chē)牌識(shí)別的圖片。
- 注:opencv-python 使用的是3.x 版本
三、實(shí)驗(yàn)原理
(1) 圖像灰度化
灰度數(shù)字圖像是每個(gè)像素只有一個(gè)采樣顏色的圖像。這類(lèi)圖像通常顯示為從最暗黑色到最亮的白色的灰度,盡管理論上這個(gè)采樣可以任何顏色的不同深淺,甚至可以是不同亮度上的不同顏色?;叶葓D像與黑白圖像不同,在計(jì)算機(jī)圖像領(lǐng)域中黑白圖像只有黑白兩種顏色,灰度圖像在黑色與白色之間還有許多級(jí)的顏色深度。
(2) 圖像二值化
圖像二值化就是將圖像上的像素點(diǎn)的灰度值設(shè)置為 0 或 255,也就是將整個(gè)圖像呈現(xiàn)出明顯的黑白效果。
(3) 圖像形態(tài)學(xué)運(yùn)算
用具有一定形態(tài)的結(jié)構(gòu)元素去度量和提取圖像中的對(duì)應(yīng)形狀以達(dá)到對(duì)圖像分析和識(shí)別的目的。
(4) 閾值分割原理
閾值分割算法是圖形分割中應(yīng)用場(chǎng)景最多的算法之一。簡(jiǎn)單地說(shuō),對(duì)灰度圖像進(jìn)行閾值分割就是先確定一個(gè)處于圖像灰度取值范圍內(nèi)的閾值,然后將圖像中各個(gè)像素的灰度值與這個(gè)閾值比較,并根據(jù)比較的結(jié)果將對(duì)應(yīng)的像素劃分為兩類(lèi):像素灰度大于閾值的一類(lèi)和像素值小于閾值的另一類(lèi),灰度值等于閾值的像素可以歸入這兩類(lèi)之一。分割后的兩類(lèi)像素一般分屬圖像的兩個(gè)不同區(qū)域,所以對(duì)像素根據(jù)閾值分類(lèi)達(dá)到了區(qū)域分割的目的。
(5) 字符分割原理
二值化后的圖像,在沒(méi)有字符的區(qū)域,y方向上像素灰度和為0,在有字符的區(qū)域?yàn)榛叶群头?。
四、實(shí)驗(yàn)指導(dǎo)
4.1 車(chē)牌定位
按照下面給出的閾值遍歷圖片,選取適當(dāng)區(qū)域進(jìn)行分割。遍歷圖像可利用for循環(huán)遍歷圖片上所有點(diǎn),遍歷方法為:
for i=1:m
for j=1:n
Rij=I(i,j,1);
Gij=I(i,j,2);
Bij=I(i,j,3);
其中 I I I 為大小是 m ? n m*n m?n 的RGB圖像, R i j R_{ij} Rij?、 G i j G_{ij} Gij?、 B i j B_{ij} Bij? 分別為 ( i , j ) (i,j) (i,j) 點(diǎn)像素的R、G、B值,將三個(gè)值與下方給出的閾值比較,可得出像素是否屬于車(chē)牌區(qū)域。
定位車(chē)牌區(qū)域時(shí)可以分別從行和列的角度進(jìn)行遍歷,即若某行符合要求的像素點(diǎn)數(shù)量大于等于某閾值時(shí)則認(rèn)為該行屬于車(chē)牌區(qū)域;遍歷列時(shí)亦然,即若某列符合要求的像素點(diǎn)數(shù)量大于等于某閾值時(shí)則認(rèn)為該列屬于車(chē)牌區(qū)域。
車(chē)牌分割參考閾值:
- RGB圖像參考閾值
若 R i j R_{ij} Rij?、 G i j G_{ij} Gij?、 B i j B_{ij} Bij? 分別為 ( i , j ) (i,j) (i,j) 點(diǎn)的RGB值,則
R i j R_{ij} Rij?/ B i j B_{ij} Bij?<0.35, G i j G_{ij} Gij?/ B i j B_{ij} Bij?<0.9, B i j B_{ij} Bij?>90 或 G i j G_{ij} Gij?/ B i j B_{ij} Bij?< 0.35, R i j R_{ij} Rij?/ B i j B_{ij} Bij?<0.9, B i j B_{ij} Bij?<90;
- HSV圖像參考閾值
也可將RGB圖像轉(zhuǎn)化為HSV圖像進(jìn)行閾值比較,記 H i j H_{ij} Hij?、 S i j S_{ij} Sij?、 V i j V_{ij} Vij? 分別為 ( i , j ) (i,j) (i,j) 點(diǎn)的HSV值,則
190 < H i j H_{ij} Hij? < 245,0.35 < S i j S_{ij} Sij? <1,0.3 < V i j V_{ij} Vij? < 1。
根據(jù)檢測(cè)到區(qū)域,將照片中車(chē)牌區(qū)域單獨(dú)分割出來(lái),為后面的字符分割做準(zhǔn)備。
4.2 分割區(qū)域灰度化、二值化
將 4.1 中獲得的車(chē)牌區(qū)域圖片轉(zhuǎn)化為灰度圖像、二值圖像,為后面的字符分割做準(zhǔn)備。
4.3 車(chē)牌分割
二值化后的圖像,在列方向,沒(méi)有字符的區(qū)域,y方向上像素灰度和為0,在有字符的區(qū)域?yàn)榛叶群头?,因此可根據(jù)灰度值在縱軸的投影對(duì)車(chē)牌二值圖像進(jìn)行分割;同理在行方向上也一樣。根據(jù)此原理,可以將車(chē)牌中的字符單獨(dú)分割出來(lái),并且去除每個(gè)字符的上下方向上多余的邊框。因此得到的字符分割結(jié)果,字符應(yīng)該占滿整個(gè)分割圖像區(qū)域。為了便于后期的識(shí)別,因此將分割結(jié)果圖片統(tǒng)一縮放為25*15大小。
4.4 車(chē)牌識(shí)別
本實(shí)驗(yàn)依照模版匹配進(jìn)行識(shí)別。由于所給的模板中字符并沒(méi)有占整個(gè)模板圖片的區(qū)域,因此需要對(duì)模板進(jìn)行去邊框、縮放處理,原理與步驟 4.3 中類(lèi)似。
將分割結(jié)果
I
I
I 分別與模版
I
’
I’
I’ 進(jìn)行比對(duì),得出其差值
∣
I
?
I
′
∣
|I-I'|
∣I?I′∣,則所得差值最小的模版即為識(shí)別結(jié)果。其中
I
I
I 為分割后的字符圖像,
I
’
I’
I’ 為模版圖像。由于 opencv-python 中圖像是以 numpy 數(shù)組形式存儲(chǔ)的,所以
∣
I
?
I
′
∣
|I-I'|
∣I?I′∣ 相當(dāng)于直接將兩矩陣相減取絕對(duì)值即可,取絕對(duì)值的函數(shù)為 numpy.abs()
。
五、實(shí)驗(yàn)內(nèi)容及代碼
5.1 實(shí)驗(yàn)數(shù)據(jù)
本次實(shí)驗(yàn)將使用到一張待車(chē)牌識(shí)別的圖像以及一個(gè)車(chē)牌模板文件,待進(jìn)行車(chē)牌識(shí)別的圖像如 圖1 所示,車(chē)牌模板文件夾如 圖2 所示:
?
?
?
?
?
?
?
?
具體的數(shù)據(jù)我已打包分享至如下百度網(wǎng)盤(pán)鏈接:5-carNumber_免費(fèi)高速下載|百度網(wǎng)盤(pán)-分享無(wú)限制 (baidu.com)
5.2 實(shí)驗(yàn)代碼
import os
import cv2
import numpy as np
img = cv2.imread(r'./data/5.jpg') # 最終用于識(shí)別的圖像
# 1.車(chē)牌定位
def license_region(image):
r = image[:, :, 2]
g = image[:, :, 1]
b = image[:, :, 0]
# 求出三種閾值
license_region_thresh = np.zeros(np.append(3, r.shape)) # 創(chuàng)建一個(gè)空的三維數(shù)組用于存放三種閾值
license_region_thresh[0, :, :] = r/b
license_region_thresh[1, :, :] = g/b
license_region_thresh[2, :, :] = b
# 存放滿足閾值條件的像素點(diǎn)坐標(biāo)
region_origin = []
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if (license_region_thresh[0, i, j] < 0.35 and
license_region_thresh[1, i, j] < 0.9 and
license_region_thresh[2, i, j] > 90) or (
license_region_thresh[1, i, j] < 0.35 and
license_region_thresh[0, i, j] < 0.9 and
license_region_thresh[2, i, j] < 90):
region_origin.append([i, j])
region_origin = np.array(region_origin)
# 進(jìn)一步縮小行的索引范圍
row_index = np.unique(region_origin[:, 0])
row_index_number = np.zeros(row_index.shape, dtype=np.uint8)
for i in range(region_origin.shape[0]):
for j in range(row_index.shape[0]):
if region_origin[i, 0] == row_index[j]:
row_index_number[j] = row_index_number[j]+1
row_index_out = row_index_number > 10 # 將誤判的點(diǎn)去除
row_index_out = row_index[row_index_out]
# 進(jìn)一步縮小列的索引范圍
col_index = np.unique(region_origin[:, 1])
col_index_number = np.zeros(col_index.shape, dtype=np.uint8)
for i in range(region_origin.shape[0]):
for j in range(col_index.shape[0]):
if region_origin[i, 1] == col_index[j]:
col_index_number[j] = col_index_number[j]+1
col_index_out = col_index_number > 10
col_index_out = col_index[col_index_out]
# 得出最后的區(qū)間
region_out = np.array([[np.min(row_index_out), np.max(row_index_out)],
[np.min(col_index_out), np.max(col_index_out)]])
return region_out
region = license_region(img)
# 顯示車(chē)牌區(qū)域
img_test = img.copy() # 拷貝時(shí)不能直接等號(hào)賦值
cv2.rectangle(img_test, pt1=(region[1, 0], region[0, 0]), pt2=(region[1, 1], region[0, 1]),
color=(0, 0, 255), thickness=2)
cv2.imshow('car_license_region', img_test)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 2.分割區(qū)域灰度化、二值化
img_car_license = img[region[0, 0]:region[0, 1], region[1, 0]:region[1, 1], :]
img_car_license_gray = cv2.cvtColor(img_car_license, cv2.COLOR_BGR2GRAY) # 將RGB圖像轉(zhuǎn)化為灰度圖像
# otus二值化
img_car_license_binary = cv2.threshold(img_car_license_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# 3.車(chē)牌分割(均分割為25*15的圖片)height=25,width=15
# 模板分割函數(shù),只針對(duì)單個(gè)字符,用于去除其周?chē)倪吘?,并resize
def template_segmentation(origin_img):
# 提取字符各列滿足條件(有兩個(gè)255的單元格)的索引
col_index = []
for col in range(origin_img.shape[1]): # 對(duì)于圖像的所有列
if np.sum(origin_img[:, col]) >= 2*255:
col_index.append(col)
col_index = np.array(col_index)
# 提取字符各行滿足條件(有兩個(gè)255的單元格)的索引
row_index = []
for row in range(origin_img.shape[0]):
if np.sum(origin_img[row, :]) >= 2*255:
row_index.append(row)
row_index = np.array(row_index)
# 按索引提取字符(符合條件的行列中取min-max),并resize到25*15大小
output_img = origin_img[np.min(row_index):np.max(row_index)+1, np.min(col_index):np.max(col_index)+1]
output_img = np.uint8(output_img)
if col_index.shape[0] <= 3 or row_index.shape[0] <= 3:
output_img = origin_img[np.min(row_index):np.max(row_index)+1, np.min(col_index):np.max(col_index)+1]
pad_row1 = np.int8(np.floor((25 - output_img.shape[0]) / 2))
pad_row2 = np.int8(np.ceil((25 - output_img.shape[0]) / 2))
pad_col1 = np.int8(np.floor((15 - output_img.shape[1]) / 2))
pad_col2 = np.int8(np.ceil((15 - output_img.shape[1]) / 2))
output_img = np.pad(output_img, ((pad_row1, pad_row2), (pad_col1, pad_col2)), 'constant',
constant_values=(0, 0))
output_img = np.uint8(output_img)
else:
output_img = cv2.resize(output_img, (15, 25), interpolation=0)
return output_img
# 對(duì)原始車(chē)牌摳圖,摳出每一個(gè)字符
temp_col_index = []
for col in range(img_car_license_binary.shape[1]):
if np.sum(img_car_license_binary[:, col]) >= 2*255: # 提取大于等于2個(gè)255的列
temp_col_index.append(col)
temp_col_index = np.array(temp_col_index)
flag = 0 # 值是7個(gè)字符的起始列
flag_i = 0 # 值的變化范圍:從0到6(對(duì)應(yīng)車(chē)牌的7個(gè)字符)
car_license_out_col = np.uint8(np.zeros([7, 30])) # 7行的數(shù)組存儲(chǔ)車(chē)牌上的7個(gè)需識(shí)別的字
for j in range(temp_col_index.shape[0]-1):
if temp_col_index[j+1]-temp_col_index[j] >= 2: # 提取的>=2個(gè)255的列之間不是相鄰的(可初步解決川的分割問(wèn)題)
temp = temp_col_index[flag:j+1]
temp = np.append(temp, np.zeros(30-temp.shape[0])) # 補(bǔ)成30維的向量,方便最后賦值給car_license_out_col
temp = np.uint8(temp.reshape(1, 30))
car_license_out_col[flag_i, :] = temp
flag = j+1
flag_i = flag_i+1
temp = temp_col_index[flag:]
temp = np.append(temp, np.zeros(30-temp.shape[0]))
temp = np.uint8(temp.reshape(1, 30))
car_license_out_col[flag_i, :] = temp
# 分別提取7個(gè)字符
car_license_out_row = np.uint8(np.zeros([7, 30]))
for row in range(car_license_out_row.shape[0]): # car_license_out_row.shape[0]
temp = car_license_out_col[row, :]
index = 0
for i in range(temp.shape[0]): # 去除列索引中多余的0
if temp[i] == 0:
index = i
break
col_temp = temp[0:index]
temp_img = img_car_license_binary[:, np.min(col_temp):np.max(col_temp)+1]
t = np.nonzero(np.sum(temp_img, axis=1))
if row == 0:
province1 = temp_img[t, :] # 漢字后續(xù)擴(kuò)展成40*40
province1 = province1[0, :, :]
province1 = template_segmentation(province1)
province1 = np.uint8(province1)
if row == 1:
province2 = temp_img[t, :] # 字母和數(shù)字后續(xù)擴(kuò)展成40*40
province2 = province2[0, :, :]
province2 = template_segmentation(province2)
province2 = np.uint8(province2)
if row == 2:
car_number1 = temp_img[t, :]
car_number1 = car_number1[0, :, :]
car_number1 = template_segmentation(car_number1)
car_number1 = np.uint8(car_number1)
if row == 3:
car_number2 = temp_img[t, :]
car_number2 = car_number2[0, :, :]
car_number2 = template_segmentation(car_number2)
car_number2 = np.uint8(car_number2)
if row == 4:
car_number3 = temp_img[t, :]
car_number3 = car_number3[0, :, :]
car_number3 = template_segmentation(car_number3)
car_number3 = np.uint8(car_number3)
if row == 5:
car_number4 = temp_img[t, :]
car_number4 = car_number4[0, :, :]
car_number4 = template_segmentation(car_number4)
car_number4 = np.uint8(car_number4)
if row == 6:
car_number5 = temp_img[t, :]
car_number5 = car_number5[0, :, :]
car_number5 = template_segmentation(car_number5)
car_number5 = np.uint8(car_number5)
cv2.imshow('province1', province1)
cv2.imshow('province2', province2)
cv2.imshow('car_number1', car_number1)
cv2.imshow('car_number2', car_number2)
cv2.imshow('car_number3', car_number3)
cv2.imshow('car_number4', car_number4)
cv2.imshow('car_number5', car_number5)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 4.車(chē)牌識(shí)別
# 讀取原始圖片并生成模板的函數(shù)
def template_array_generator(template_path, template_size):
template_img_out = np.zeros([template_size, 25, 15], dtype=np.uint8)
index = 0
files = os.listdir(template_path)
for file in files:
template_img = cv2.imdecode(np.fromfile(template_path + '/' + file, dtype=np.uint8), -1)
template_img_gray = cv2.cvtColor(template_img, cv2.COLOR_BGR2GRAY)
template_img_binary = cv2.threshold(template_img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
template_img_binary = 255-template_img_binary # 模板給出的與車(chē)牌上的是相反的,所有用255相減進(jìn)行匹配
template_img_out[index, :, :] = template_segmentation(template_img_binary)
index = index + 1
return template_img_out
# 讀取所有的漢字并生成模板
Chinese_character = open(r'./data/5-carNumber./漢字.txt', encoding="gbk").read()
Chinese_character = Chinese_character.split("\n")
Chinese_char_template = template_array_generator(r'./data/5-carNumber./漢字', len(Chinese_character))
# 讀取所有的數(shù)字并生成模板
Number_character = open(r'./data/5-carNumber./數(shù)字.txt', encoding="gbk").read()
Number_character = Number_character.split("\n")
Number_char_template = template_array_generator(r'./data/5-carNumber./數(shù)字', len(Number_character))
# 讀取所有的字母并生成模板
Alphabet_character = open(r'./data/5-carNumber./英文.txt', encoding="gbk").read()
Alphabet_character = Alphabet_character.split("\n")
Alphabet_char_template = template_array_generator(r'./data/5-carNumber./英文', len(Alphabet_character))
# 進(jìn)行字符識(shí)別
car_character = np.uint8(np.zeros([7, 25, 15]))
car_character[0, :, :] = province1.copy()
car_character[1, :, :] = province2.copy()
car_character[2, :, :] = car_number1.copy()
car_character[3, :, :] = car_number2.copy()
car_character[4, :, :] = car_number3.copy()
car_character[5, :, :] = car_number4.copy()
car_character[6, :, :] = car_number5.copy()
match_length = Chinese_char_template.shape[0]+Alphabet_char_template.shape[0]+Number_char_template.shape[0]
match_mark = np.zeros([7, match_length])
Chinese_char_start = 0
Chinese_char_end = Chinese_char_template.shape[0]
Alphabet_char_start = Chinese_char_template.shape[0]
Alphabet_char_end = Chinese_char_template.shape[0]+Alphabet_char_template.shape[0]
Number_char_start = Chinese_char_template.shape[0]+Alphabet_char_template.shape[0]
Number_char_end = match_length
for i in range(match_mark.shape[0]): # 7個(gè)需識(shí)別的字符
for j in range(Chinese_char_start, Chinese_char_end): # 所有的漢字模板
match_mark[i, j] = cv2.matchTemplate(car_character[i, :, :], Chinese_char_template[j, :, :], cv2.TM_CCOEFF)
# 所有的字母模板
for j in range(Alphabet_char_start, Alphabet_char_end):
match_mark[i, j] = cv2.matchTemplate(car_character[i, :, :],
Alphabet_char_template[j-Alphabet_char_start, :, :],
cv2.TM_CCOEFF)
# 所有的數(shù)字模板
for j in range(Number_char_start, Number_char_end):
match_mark[i, j] = cv2.matchTemplate(car_character[i, :, :],
Number_char_template[j-Number_char_start, :, :],
cv2.TM_CCOEFF)
output_index = np.argmax(match_mark, axis=1)
output_char = []
for i in range(output_index.shape[0]):
if 0 <= output_index[i] <= 28:
output_char.append(Chinese_character[output_index[i]])
if 29 <= output_index[i] <= 54:
output_char.append(Alphabet_character[output_index[i]-29])
if 55 <= output_index[i] <= 64:
output_char.append(Number_character[output_index[i]-55])
# 打印識(shí)別結(jié)果
for i in range(len(output_char)):
if i == 0:
print('province1:'+output_char[0])
if i == 1:
print('province1:'+output_char[1])
if i == 2:
print('car1:'+output_char[2])
if i == 3:
print('car2:' + output_char[3])
if i == 4:
print('car3:' + output_char[4])
if i == 5:
print('car4:' + output_char[5])
if i == 6:
print('car5:' + output_char[6])
車(chē)牌區(qū)域定位結(jié)果:
?
各車(chē)牌字符分割結(jié)果:
?
?
?
?
?
?
?
車(chē)牌識(shí)別結(jié)果:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-482073.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-482073.html
到了這里,關(guān)于數(shù)字圖像處理——實(shí)驗(yàn)五 基于圖像分割的車(chē)牌定位識(shí)別的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!