淄博企業(yè)網(wǎng)站建設(shè)公司在seo優(yōu)化中
1. 整個(gè)識(shí)別的流程圖:
2. 車牌定位中分割流程圖:
三、車牌識(shí)別中字符分割流程圖:
1.準(zhǔn)備數(shù)據(jù)集
下載車牌相關(guān)字符樣本用于訓(xùn)練和測(cè)試,本文使用14個(gè)漢字樣本和34個(gè)數(shù)字跟字母樣本,每個(gè)字符樣本數(shù)為40,樣本尺寸為28*28。
數(shù)據(jù)集下載地址
https://download.csdn.net/download/hai411741962/88248392
下載不了,評(píng)論區(qū)留言
2. 編碼訓(xùn)練代碼
import cv2
import numpy as np
from numpy.linalg import norm
import sys
import os
import jsonSZ = 20 #訓(xùn)練圖片長(zhǎng)寬
MAX_WIDTH = 1000 #原始圖片最大寬度
Min_Area = 2000 #車牌區(qū)域允許最大面積
PROVINCE_START = 1000
#不能保證包括所有省份
provinces = ["zh_cuan", "川","zh_e", "鄂","zh_gan", "贛","zh_gan1", "甘","zh_gui", "貴","zh_gui1", "桂","zh_hei", "黑","zh_hu", "滬","zh_ji", "冀","zh_jin", "津","zh_jing", "京","zh_jl", "吉","zh_liao", "遼","zh_lu", "魯","zh_meng", "蒙","zh_min", "閩","zh_ning", "寧","zh_qing", "靑","zh_qiong", "瓊","zh_shan", "陜","zh_su", "蘇","zh_sx", "晉","zh_wan", "皖","zh_xiang", "湘","zh_xin", "新","zh_yu", "豫","zh_yu1", "渝","zh_yue", "粵","zh_yun", "云","zh_zang", "藏","zh_zhe", "浙"
]class StatModel(object):def load(self, fn):self.model = self.model.load(fn)#從文件載入訓(xùn)練好的模型def save(self, fn):self.model.save(fn)#保存訓(xùn)練好的模型到文件中class SVM(StatModel):def __init__(self, C = 1, gamma = 0.5):self.model = cv2.ml.SVM_create()#生成一個(gè)SVM模型self.model.setGamma(gamma) #設(shè)置Gamma參數(shù),demo中是0.5self.model.setC(C)# 設(shè)置懲罰項(xiàng), 為:1self.model.setKernel(cv2.ml.SVM_RBF)#設(shè)置核函數(shù)self.model.setType(cv2.ml.SVM_C_SVC)#設(shè)置SVM的模型類型:SVC是分類模型,SVR是回歸模型#訓(xùn)練svmdef train(self, samples, responses):self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)#訓(xùn)練#字符識(shí)別def predict(self, samples):r = self.model.predict(samples)#預(yù)測(cè)return r[1].ravel()#來(lái)自opencv的sample,用于svm訓(xùn)練
def deskew(img):m = cv2.moments(img)if abs(m['mu02']) < 1e-2:return img.copy()skew = m['mu11']/m['mu02']M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)return img#來(lái)自opencv的sample,用于svm訓(xùn)練
def preprocess_hog(digits):samples = []for img in digits:gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)mag, ang = cv2.cartToPolar(gx, gy)bin_n = 16bin = np.int32(bin_n*ang/(2*np.pi))bin_cells = bin[:10,:10], bin[10:,:10], bin[:10,10:], bin[10:,10:]mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]hist = np.hstack(hists)# transform to Hellinger kerneleps = 1e-7hist /= hist.sum() + epshist = np.sqrt(hist)hist /= norm(hist) + epssamples.append(hist)return np.float32(samples)def save_traindata(model,modelchinese):if not os.path.exists("module\\svm.dat"):model.save("module\\svm.dat")if not os.path.exists("module\\svmchinese.dat"):modelchinese.save("module\\svmchinese.dat")def train_svm():#識(shí)別英文字母和數(shù)字model = SVM(C=1, gamma=0.5)#識(shí)別中文modelchinese = SVM(C=1, gamma=0.5)if os.path.exists("svm.dat"):model.load("svm.dat")else:chars_train = []chars_label = []for root, dirs, files in os.walk("train\\chars2"):if len(os.path.basename(root)) > 1:continueroot_int = ord(os.path.basename(root))for filename in files:filepath = os.path.join(root,filename)digit_img = cv2.imread(filepath)digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)chars_train.append(digit_img)#chars_label.append(1)chars_label.append(root_int)chars_train = list(map(deskew, chars_train))#print(chars_train)chars_train = preprocess_hog(chars_train)#print(chars_train)#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)chars_label = np.array(chars_label)model.train(chars_train, chars_label)if os.path.exists("svmchinese.dat"):modelchinese.load("svmchinese.dat")else:chars_train = []chars_label = []for root, dirs, files in os.walk("train\\charsChinese"):if not os.path.basename(root).startswith("zh_"):continuepinyin = os.path.basename(root)index = provinces.index(pinyin) + PROVINCE_START + 1 #1是拼音對(duì)應(yīng)的漢字for filename in files:filepath = os.path.join(root,filename)digit_img = cv2.imread(filepath)digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)chars_train.append(digit_img)#chars_label.append(1)chars_label.append(index)chars_train = list(map(deskew, chars_train))chars_train = preprocess_hog(chars_train)#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)chars_label = np.array(chars_label)print(chars_train.shape)modelchinese.train(chars_train, chars_label)save_traindata(model,modelchinese)train_svm()
運(yùn)行代碼后會(huì)生成兩個(gè)模型文件,下面驗(yàn)證兩個(gè)模型文件。
import cv2
import numpy as npimport json
import trainSZ = 20 #訓(xùn)練圖片長(zhǎng)寬
MAX_WIDTH = 1000 #原始圖片最大寬度
Min_Area = 2000 #車牌區(qū)域允許最大面積
PROVINCE_START = 1000
#讀取圖片文件
def imreadex(filename):return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_COLOR)def point_limit(point):if point[0] < 0:point[0] = 0if point[1] < 0:point[1] = 0#根據(jù)設(shè)定的閾值和圖片直方圖,找出波峰,用于分隔字符
def find_waves(threshold, histogram):up_point = -1#上升點(diǎn)is_peak = Falseif histogram[0] > threshold:up_point = 0is_peak = Truewave_peaks = []for i,x in enumerate(histogram):if is_peak and x < threshold:if i - up_point > 2:is_peak = Falsewave_peaks.append((up_point, i))elif not is_peak and x >= threshold:is_peak = Trueup_point = iif is_peak and up_point != -1 and i - up_point > 4:wave_peaks.append((up_point, i))return wave_peaks#根據(jù)找出的波峰,分隔圖片,從而得到逐個(gè)字符圖片
def seperate_card(img, waves):part_cards = []for wave in waves:part_cards.append(img[:, wave[0]:wave[1]])return part_cardsclass CardPredictor:def __init__(self):#車牌識(shí)別的部分參數(shù)保存在json中,便于根據(jù)圖片分辨率做調(diào)整f = open('config.json')j = json.load(f)for c in j["config"]:if c["open"]:self.cfg = c.copy()breakelse:raise RuntimeError('沒(méi)有設(shè)置有效配置參數(shù)')def load_svm(self):#識(shí)別英文字母和數(shù)字self.model = train.SVM(C=1, gamma=0.5)#SVM(C=1, gamma=0.5)#識(shí)別中文self.modelchinese = train.SVM(C=1, gamma=0.5)#SVM(C=1, gamma=0.5)self.model.load("module\\svm.dat")self.modelchinese.load("module\\svmchinese.dat")def accurate_place(self, card_img_hsv, limit1, limit2, color):row_num, col_num = card_img_hsv.shape[:2]xl = col_numxr = 0yh = 0yl = row_num#col_num_limit = self.cfg["col_num_limit"]row_num_limit = self.cfg["row_num_limit"]col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5#綠色有漸變for i in range(row_num):count = 0for j in range(col_num):H = card_img_hsv.item(i, j, 0)S = card_img_hsv.item(i, j, 1)V = card_img_hsv.item(i, j, 2)if limit1 < H <= limit2 and 34 < S and 46 < V:count += 1if count > col_num_limit:if yl > i:yl = iif yh < i:yh = ifor j in range(col_num):count = 0for i in range(row_num):H = card_img_hsv.item(i, j, 0)S = card_img_hsv.item(i, j, 1)V = card_img_hsv.item(i, j, 2)if limit1 < H <= limit2 and 34 < S and 46 < V:count += 1if count > row_num - row_num_limit:if xl > j:xl = jif xr < j:xr = jreturn xl, xr, yh, yldef predict(self, car_pic, resize_rate=1):if type(car_pic) == type(""):img = imreadex(car_pic)else:img = car_picpic_hight, pic_width = img.shape[:2]if resize_rate != 1:img = cv2.resize(img, (int(pic_width*resize_rate), int(pic_hight*resize_rate)), interpolation=cv2.INTER_AREA)pic_hight, pic_width = img.shape[:2]#cv2.imshow('img',img)#cv2.waitKey(0)print("h,w:", pic_hight, pic_width)blur = self.cfg["blur"]#高斯去噪if blur > 0:img = cv2.GaussianBlur(img, (blur, blur), 0)#圖片分辨率調(diào)整oldimg = imgimg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#去掉圖像中不會(huì)是車牌的區(qū)域kernel = np.ones((20, 20), np.uint8)img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0);#找到圖像邊緣ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)img_edge = cv2.Canny(img_thresh, 100, 200)#邊緣檢測(cè)#使用開(kāi)運(yùn)算和閉運(yùn)算讓圖像邊緣成為一個(gè)整體kernel = np.ones((self.cfg["morphologyr"], self.cfg["morphologyc"]), np.uint8)img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel)img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel)#查找圖像邊緣整體形成的矩形區(qū)域,可能有很多,車牌就在其中一個(gè)矩形區(qū)域中contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)print('len(contours)', len(contours))#找出區(qū)域contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area]print('len(contours)', len(contours))#cv2.contourArea計(jì)算面積#一一排除不是車牌的矩形區(qū)域car_contours = []for cnt in contours:rect = cv2.minAreaRect(cnt)#minAreaRectarea_width, area_height = rect[1]if area_width < area_height:area_width, area_height = area_height, area_widthwh_ratio = area_width / area_height#長(zhǎng)寬比#print(wh_ratio)#要求矩形區(qū)域長(zhǎng)寬比在2到5.5之間,2到5.5是車牌的長(zhǎng)寬比,其余的矩形排除if wh_ratio > 2 and wh_ratio < 5.5:car_contours.append(rect)box = cv2.boxPoints(rect)#cv2.boxPoints()可獲取該矩形的四個(gè)頂點(diǎn)坐標(biāo)。print(box)box = np.int0(box) #轉(zhuǎn)成整數(shù)print(box)oldimg = cv2.drawContours(oldimg, [box], 0, (0, 0, 255), 2)print(len(car_contours))print("精確定位")card_imgs = []#矩形區(qū)域可能是傾斜的矩形,需要矯正,以便使用顏色定位for rect in car_contours:if rect[2] > -1 and rect[2] < 1:#創(chuàng)造角度,使得左、高、右、低拿到正確的值angle = 1else:angle = rect[2]rect = (rect[0], (rect[1][0]+5, rect[1][1]+5), angle)#擴(kuò)大范圍,避免車牌邊緣被排除box = cv2.boxPoints(rect)heigth_point = right_point = [0, 0]left_point = low_point = [pic_width, pic_hight]for point in box:if left_point[0] > point[0]:left_point = pointif low_point[1] > point[1]:low_point = pointif heigth_point[1] < point[1]:heigth_point = pointif right_point[0] < point[0]:right_point = pointif left_point[1] <= right_point[1]:#正角度new_right_point = [right_point[0], heigth_point[1]]pts2 = np.float32([left_point, heigth_point, new_right_point])#字符只是高度需要改變pts1 = np.float32([left_point, heigth_point, right_point])M = cv2.getAffineTransform(pts1, pts2)dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))point_limit(new_right_point)point_limit(heigth_point)point_limit(left_point)card_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])]if(len(card_img)>0):card_imgs.append(card_img)elif left_point[1] > right_point[1]:#負(fù)角度new_left_point = [left_point[0], heigth_point[1]]pts2 = np.float32([new_left_point, heigth_point, right_point])#字符只是高度需要改變pts1 = np.float32([left_point, heigth_point, right_point])M = cv2.getAffineTransform(pts1, pts2)dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))point_limit(right_point)point_limit(heigth_point)point_limit(new_left_point)card_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])]card_imgs.append(card_img)#cv2.imshow("card", card_img)#cv2.waitKey(0)#開(kāi)始使用顏色定位,排除不是車牌的矩形,目前只識(shí)別藍(lán)、綠、黃車牌colors = []for card_index,card_img in enumerate(card_imgs):print(len(card_imgs))green = yello = blue = black = white = 0card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)print("card_img_hsv.shape")print(card_img_hsv.shape)#有轉(zhuǎn)換失敗的可能,原因來(lái)自于上面矯正矩形出錯(cuò)if card_img_hsv is None:continuerow_num, col_num= card_img_hsv.shape[:2]card_img_count = row_num * col_numfor i in range(row_num):for j in range(col_num):H = card_img_hsv.item(i, j, 0)S = card_img_hsv.item(i, j, 1)V = card_img_hsv.item(i, j, 2)if 11 < H <= 34 and S > 34:#圖片分辨率調(diào)整yello += 1elif 35 < H <= 99 and S > 34:#圖片分辨率調(diào)整green += 1elif 99 < H <= 124 and S > 34:#圖片分辨率調(diào)整blue += 1if 0 < H <180 and 0 < S < 255 and 0 < V < 46:black += 1elif 0 < H <180 and 0 < S < 43 and 221 < V < 225:white += 1color = "no"#根據(jù)HSV判斷車牌顏色limit1 = limit2 = 0if yello*2 >= card_img_count:color = "yello"limit1 = 11limit2 = 34#有的圖片有色偏偏綠elif green*2 >= card_img_count:color = "green"limit1 = 35limit2 = 99elif blue*2 >= card_img_count:color = "blue"limit1 = 100limit2 = 124#有的圖片有色偏偏紫elif black + white >= card_img_count*0.7:#TODOcolor = "bw"colors.append(color)print("blue, green, yello, black, white, card_img_count:")print(blue," " ,green," ", yello," ", black," ", white," ", card_img_count)print("車牌顏色:",color)# cv2.imshow("color", card_img)# cv2.waitKey(0)if limit1 == 0:continue#以上為確定車牌顏色#以下為根據(jù)車牌顏色再定位,縮小邊緣非車牌邊界xl, xr, yh, yl = self.accurate_place(card_img_hsv, limit1, limit2, color)if yl == yh and xl == xr:continueneed_accurate = Falseif yl >= yh:yl = 0yh = row_numneed_accurate = Trueif xl >= xr:xl = 0xr = col_numneed_accurate = Truecard_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr]if need_accurate:#可能x或y方向未縮小,需要再試一次card_img = card_imgs[card_index]card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)xl, xr, yh, yl = self.accurate_place(card_img_hsv, limit1, limit2, color)if yl == yh and xl == xr:continueif yl >= yh:yl = 0yh = row_numif xl >= xr:xl = 0xr = col_numcard_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr]#以上為車牌定位#以下為識(shí)別車牌中的字符predict_result = []roi = Nonecard_color = Nonefor i, color in enumerate(colors):if color in ("blue", "yello", "green"):card_img = card_imgs[i]gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY)#黃、綠車牌字符比背景暗、與藍(lán)車牌剛好相反,所以黃、綠車牌需要反向if color == "green" or color == "yello":gray_img = cv2.bitwise_not(gray_img)ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)#查找水平直方圖波峰x_histogram = np.sum(gray_img, axis=1)x_min = np.min(x_histogram)x_average = np.sum(x_histogram)/x_histogram.shape[0]x_threshold = (x_min + x_average)/2wave_peaks = find_waves(x_threshold, x_histogram)if len(wave_peaks) == 0:print("peak less 0:")continue#認(rèn)為水平方向,最大的波峰為車牌區(qū)域wave = max(wave_peaks, key=lambda x:x[1]-x[0])gray_img = gray_img[wave[0]:wave[1]]#查找垂直直方圖波峰row_num, col_num= gray_img.shape[:2]#去掉車牌上下邊緣1個(gè)像素,避免白邊影響閾值判斷gray_img = gray_img[1:row_num-1]# cv2.imshow("gray_img", gray_img)#二值化# cv2.waitKey(0)y_histogram = np.sum(gray_img, axis=0)y_min = np.min(y_histogram)y_average = np.sum(y_histogram)/y_histogram.shape[0]y_threshold = (y_min + y_average)/5#U和0要求閾值偏小,否則U和0會(huì)被分成兩半wave_peaks = find_waves(y_threshold, y_histogram)#車牌字符數(shù)應(yīng)大于6if len(wave_peaks) <= 6:print("peak less 1:", len(wave_peaks))continuewave = max(wave_peaks, key=lambda x:x[1]-x[0])max_wave_dis = wave[1] - wave[0]#判斷是否是左側(cè)車牌邊緣if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis/3 and wave_peaks[0][0] == 0:wave_peaks.pop(0)#組合分離漢字cur_dis = 0for i,wave in enumerate(wave_peaks):if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6:breakelse:cur_dis += wave[1] - wave[0]if i > 0:wave = (wave_peaks[0][0], wave_peaks[i][1])wave_peaks = wave_peaks[i+1:]wave_peaks.insert(0, wave)#去除車牌上的分隔點(diǎn)point = wave_peaks[2]if point[1] - point[0] < max_wave_dis/3:point_img = gray_img[:,point[0]:point[1]]if np.mean(point_img) < 255/5:wave_peaks.pop(2)if len(wave_peaks) <= 6:print("peak less 2:", len(wave_peaks))continuepart_cards = seperate_card(gray_img, wave_peaks)for i, part_card in enumerate(part_cards):#可能是固定車牌的鉚釘if np.mean(part_card) < 255/5:print("a point")continuepart_card_old = part_cardw = part_card.shape[1] // 3part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value = [0,0,0])part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA)cv2.destroyAllWindows()part_card = train.preprocess_hog([part_card])#preprocess_hog([part_card])if i == 0:resp = self.modelchinese.predict(part_card)#第一個(gè)字符調(diào)用中文svm模型charactor = train.provinces[int(resp[0]) - PROVINCE_START]else:resp = self.model.predict(part_card)#其他字符調(diào)用字母數(shù)字svm模型charactor = chr(resp[0])#判斷最后一個(gè)數(shù)是否是車牌邊緣,假設(shè)車牌邊緣被認(rèn)為是1if charactor == "1" and i == len(part_cards)-1:if part_card_old.shape[0]/part_card_old.shape[1] >= 8:#1太細(xì),認(rèn)為是邊緣print(part_card_old.shape)continuepredict_result.append(charactor)roi = card_imgcard_color = colorbreakreturn predict_result, roi, card_color#識(shí)別到的字符、定位的車牌圖像、車牌顏色if __name__ == '__main__':c = CardPredictor()c.load_svm()#加載訓(xùn)練好的模型img = cv2.imread("test\\car20.jpg")img = cv2.resize(img, (1000, 1000), interpolation=cv2.INTER_AREA)r, roi, color = c.predict(img)print(r)
運(yùn)行結(jié)果:
車牌顏色: blue
['津', 'N', 'A', 'V', '8', '8', '8']
從結(jié)果看比上一節(jié)的準(zhǔn)確多了。