0 前言
🔥 優質競賽項目系列,今天要分享的是
🚩 基于機器學習的車牌識別系統
🥇學長這里給一個題目綜合評分(每項滿分5分)
- 難度系數:4分
- 工作量:4分
- 創新點:3分
該項目較為新穎,適合作為競賽課題方向,學長非常推薦!
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate
1 課題介紹
1.1 系統簡介
車牌識別這個系統,雖然傳統,古老,卻是包含了所有這四個特偵的一個大數據技術的縮影.
在車牌識別中,你需要處理的數據是圖像中海量的像素單元;你處理的數據不再是傳統的結構化數據,而是圖像這種復雜的數據;如果不能在很短的時間內識別出車牌,那么系統就缺少意義;雖然一副圖像中有很多的信息,但可能僅僅只有那一小塊的信息(車牌)以及車身的顏色是你關心,而且這些信息都蘊含著巨大的價值。也就是說,車牌識別系統事實上就是現在火熱的大數據技術在某個領域的一個聚焦,通過了解車牌識別系統,可以很好的幫助你理解大數據技術的內涵,也能清楚的認識到大數據的價值。
1.2 系統要求
- 它基于openCV這個開源庫,這意味著所有它的代碼都可以輕易的獲取。
- 它能夠識別中文,例如車牌為蘇EUK722的圖片,它可以準確地輸出std:string類型的"蘇EUK722"的結果。
- 它的識別率較高。目前情況下,字符識別已經可以達到90%以上的精度。
1.3 系統架構
整體包含兩個系統:
- 車牌檢測
- 車牌字體識別(中文 + 數字 + 英文)
整體架構如下:
2 實現方式
2.1 車牌檢測技術
車牌檢測(Plate Detection):
對一個包含車牌的圖像進行分析,最終截取出只包含車牌的一個圖塊。這個步驟的主要目的是降低了在車牌識別過程中的計算量。如果直接對原始的圖像進行車牌識別,會非常的慢,因此需要檢測的過程。在本系統中,我們使用SVM(支持向量機)這個機器學習算法去判別截取的圖塊是否是真的“車牌”。
車牌檢測這里不詳細說明, 只貼出opencv圖像處理流程, 需要代碼的可以留下郵箱
使用到的圖像處理算法
- 高斯模糊
- 灰度化處理
- Sobel算子(邊緣檢測)
- 開操作
- 閉操作
- 仿射變換
- 霍姆線性檢測
- 角度矯正
2.2 車牌識別技術
字符識別(Chars Recognition):
有的書上也叫Plate
Recognition,我為了與整個系統的名稱做區分,所以改為此名字。這個步驟的主要目的就是從上一個車牌檢測步驟中獲取到的車牌圖像,進行光學字符識別(OCR)這個過程。其中用到的機器學習算法是著名的人工神經網絡(ANN)中的多層感知機(MLP)模型。最近一段時間非常火的“深度學習”其實就是多隱層的人工神經網絡,與其有非常緊密的聯系。通過了解光學字符識別(OCR)這個過程,也可以知曉深度學習所基于的人工神經網路技術的一些內容。
我們這里使用深度學習的方式來對車牌字符進行識別, 為什么不用傳統的機器學習進行識別呢, 看圖就知道了:
圖2 深度學習(右)與PCA技術(左)的對比
可以看出深度學習對于數據的分類能力的優勢。
這里博主使用生成對抗網絡進行字符識別訓練, 效果相當不錯, 識別精度達到了98%
2.3 SVM識別字符
定義
class SVM(StatModel):def __init__(self, C = 1, gamma = 0.5):self.model = cv2.ml.SVM_create()self.model.setGamma(gamma)self.model.setC(C)self.model.setKernel(cv2.ml.SVM_RBF)self.model.setType(cv2.ml.SVM_C_SVC)#訓練svmdef train(self, samples, responses):self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)
?
調用方法,喂數據
def train_svm(self):#識別英文字母和數字self.model = SVM(C=1, gamma=0.5)#識別中文self.modelchinese = SVM(C=1, gamma=0.5)if os.path.exists("svm.dat"):self.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))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)self.model.train(chars_train, chars_label)?
車牌字符數據集如下
這些是字母的訓練數據,同樣的還有我們車牌的省份簡寫:
核心代碼
?
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)#黃、綠車牌字符比背景暗、與藍車牌剛好相反,所以黃、綠車牌需要反向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#認為水平方向,最大的波峰為車牌區域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個像素,避免白邊影響閾值判斷gray_img = gray_img[1:row_num-1]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會被分成兩半wave_peaks = find_waves(y_threshold, y_histogram)#for wave in wave_peaks:# cv2.line(card_img, pt1=(wave[0], 5), pt2=(wave[1], 5), color=(0, 0, 255), thickness=2) #車牌字符數應大于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]#判斷是否是左側車牌邊緣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)#去除車牌上的分隔點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 = abs(part_card.shape[1] - SZ)//2part_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)#part_card = deskew(part_card)part_card = preprocess_hog([part_card])if i == 0:resp = self.modelchinese.predict(part_card)charactor = provinces[int(resp[0]) - PROVINCE_START]else:resp = self.model.predict(part_card)charactor = chr(resp[0])#判斷最后一個數是否是車牌邊緣,假設車牌邊緣被認為是1if charactor == "1" and i == len(part_cards)-1:if part_card_old.shape[0]/part_card_old.shape[1] >= 7:#1太細,認為是邊緣continuepredict_result.append(charactor)roi = card_imgcard_color = colorbreakreturn predict_result, roi, card_color#識別到的字符、定位的車牌圖像、車牌顏色?
?
2.4 最終效果
最后算法部分可以和你想要的任何UI配置到一起:
可以這樣 :
也可以這樣:
甚至更加復雜一點:
最后
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate