1. 程式人生 > 實用技巧 >HyperLPR車牌識別專案程式碼解讀

HyperLPR車牌識別專案程式碼解讀

HyperLPR車牌識別專案程式碼解讀

High Accuracy Chinese Plate Recognition Framework, This research aims at simply developping plate recognition project based on deep learning methods, with low complexity and high speed. This project has been used by some commercial corporations. Free and open source, deploying by Zeusee.

Github :https://github.com/icepoint666/HyperLPR

Forked from zeusees/HyperLPR 略加改動

只需要三個程式碼檔案:
- multi_demo.py
- demo.py
- HyperLPRLite.py

1. 引數說明

識別單一圖片中的車牌

在專案路徑 /HyperLPR 中執行下面指令
例如:

$ python demo.py --detect_path dataset/1.jpg \
> --save_result_flag True \
> --plot_result_flag True \
> --save_path /home/icepoint/Pictures/
  • 1
  • 2
  • 3
  • 4
 - detect_path: 被檢測圖片的路徑,                                                         
            default = None
 - cascade_model_path: 用於object detection的模型檔案路徑           
            default = model/cascade.xml
 - mapping_vertical_model_path: 用左右邊界迴歸模型檔案路徑        
            default = model/model12.h5
 - ocr_plate_model_path: 用於檢測車牌中的文字                                   
            default = model/ocr_plate_all_gru.h5
 - save_result_flag: 是否儲存識別結果圖片
            default = True
 - plot_result_flag: 是否輸出識別結果圖片
            default = True
 - save_path: 識別結果圖片儲存路徑folder (None表示不儲存)  
            default = None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

識別某路徑下的所有圖片中的車牌

在專案路徑 /HyperLPR 中執行下面指令
示例:

$ python multi_demo.py --detect_parent_path dataset/ \
> --save_result_flag True \
> --plot_result_flag True \
> --save_path /home/icepoint/Pictures/
  • 1
  • 2
  • 3
  • 4

引數:

 - detect_parent_path: 被檢測圖片目錄folder                                    
            default = None
 - cascade_model_path: 用於object detection的模型檔案路徑           
            default = model/cascade.xml
 - mapping_vertical_model_path: 用左右邊界迴歸模型檔案路徑        
            default = model/model12.h5
 - ocr_plate_model_path: 用於檢測車牌中的文字                                   
            default = model/ocr_plate_all_gru.h5
 - save_result_flag: 是否儲存識別結果圖片
            default = True
 - plot_result_flag: 是否輸出識別結果圖片
            default = True
 - save_path: 識別結果圖片儲存路徑folder (None表示不儲存)  
            default = None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2. 實現原理

入口檔案 demo.py(部分):

import HyperLPRLite as pr
import cv2
import numpy as np
grr = cv2.imread("images_rec/2_.jpg")
model = pr.LPR("model/cascade.xml","model/model12.h5","model/ocr_plate_all_gru.h5")
for pstr,confidence,rect in model.SimpleRecognizePlateByE2E(grr):
        if confidence>0.7:
            image = drawRectBox(grr, rect, pstr+" "+str(round(confidence,3)))
            print "plate_str:"
            print pstr
            print "plate_confidence"
            print confidence

cv2.imshow("image",image)
cv2.waitKey(0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(1)opencv2的imread函式匯入圖片, 返回的是Mat型別。

(2)HyperLPRLiite.py中的LPR類建構函式匯入model, 引數就是訓練好的三個模型檔案,名字分別是:
- model/cascade.xml
- model/model12.h5
- model/ocr_plate_all_gru.h5

HyperLPRLite.py(部分):

class LPR():
    def __init__(self,model_detection,model_finemapping,model_seq_rec):
        self.watch_cascade = cv2.CascadeClassifier(model_detection)
        self.modelFineMapping = self.model_finemapping()
        self.modelFineMapping.load_weights(model_finemapping)
        self.modelSeqRec = self.model_seq_rec(model_seq_rec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(3)引數 model_detection 就是檔案 model/cascade.xml
用到了 opencv2的CascadeClassifier()函式
cv2.CascadeClassifier()
引數輸入.xml或者.yaml檔案,表示載入模型
一種基於Haar特徵的級聯分類器用於物體檢測的模型

(4)model.SImpleRecognizePlateByE2E()函式

for pstr,confidence,rect in model.SimpleRecognizePlateByE2E(grr):
        if confidence>0.7:
            image = drawRectBox(grr, rect, pstr+" "+str(round(confidence,3)))
            print "plate_str:"
            print pstr
            print "plate_confidence"
            print confidence
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

輸入為一個Mat型別的圖片
輸出為識別的車牌字串,以及confidence可信度,
定義在 HyperLPRLite.py:

    def SimpleRecognizePlateByE2E(self,image):
        images = self.detectPlateRough(image,image.shape[0],top_bottom_padding_rate=0.1)
        res_set = []
        for j,plate in enumerate(images):
            plate, rect  =plate
            image_rgb,rect_refine = self.finemappingVertical(plate,rect)
            res,confidence = self.recognizeOne(image_rgb)
            res_set.append([res,confidence,rect_refine])
        return res_set
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中又用到detectPlateRough()函式

下面有詳細說明detectPlateRough函式(5)是返回影象中所有車牌的邊框在圖片中的bbox
返回的是一個表示車牌區域座標邊框的list

for迴圈中,對於每個識別出來的車牌用到filemappingVertical()函式(6)

(5)detectPlateRough函式

    def detectPlateRough(self,image_gray,resize_h = 720,en_scale =1.08 ,top_bottom_padding_rate = 0.05):
        if top_bottom_padding_rate>0.2:
            print("error:top_bottom_padding_rate > 0.2:",top_bottom_padding_rate)
            exit(1)
        height = image_gray.shape[0]
        padding =    int(height*top_bottom_padding_rate)
        scale = image_gray.shape[1]/float(image_gray.shape[0])
        image = cv2.resize(image_gray, (int(scale*resize_h), resize_h))
        image_color_cropped = image[padding:resize_h-padding,0:image_gray.shape[1]]
        image_gray = cv2.cvtColor(image_color_cropped,cv2.COLOR_RGB2GRAY)
        watches = self.watch_cascade.detectMultiScale(image_gray, en_scale, 2, minSize=(36, 9),maxSize=(36*40, 9*40))
        cropped_images = []
        for (x, y, w, h) in watches:
            x -= w * 0.14
            w += w * 0.28
            y -= h * 0.15
            h += h * 0.3
            cropped = self.cropImage(image_color_cropped, (int(x), int(y), int(w), int(h)))
            cropped_images.append([cropped,[x, y+padding, w, h]])
        return cropped_images
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

輸入引數:
image_gray: 一個rgb影象,Mat型別
resize_h: 重新設定的影象大小
top_bottom_padding_rate: 表示要裁剪掉圖片的上下部佔比

這個函式實現的處理:
1.resize影象大小,cv2.resize函式,按照原來影象比例
2.裁剪圖片,根據輸入的top_bottom_padding_rate如果是0.1,那麼上面裁剪掉0.1*height,下面也裁剪掉0.1*height
3.將影象從rgb轉化為灰度 cv2.cvtColor函式,cv2.COLOR_RGB2GRAY
4.根據前面的cv2.CascadeClassifier()物體檢測模型(3),輸入image_gray灰度影象,邊框可識別的最小size,最大size,輸出得到車牌在影象中的offset,也就是邊框左上角座標( x, y )以及邊框高度( h )和寬度( w )
5.對得到的車牌邊框的bbox進行擴大,也就是寬度左右各擴大0.14倍,高度上下各擴大0.15倍。
6.返回圖片中所有識別出來的車牌邊框bbox,這個list作為返回結果。

(6)filemappingVertical函式

    def finemappingVertical(self,image,rect):
        resized = cv2.resize(image,(66,16))
        resized = resized.astype(np.float)/255
        res_raw= (np.array([resized]))[0]
        res  =res_raw*image.shape[1]
        res = res.astype(np.int)
        H,T = res
        H-=3
        if H<0:
            H=0
        T+=2;
        if T>= image.shape[1]-1:
            T= image.shape[1]-1
        rect[2] -=  rect[2]*(1-res_raw[1] + res_raw[0])
        rect[0]+=res[0]
        image = image[:,H:T+2]
        image = cv2.resize(image, (int(136), int(36)))
        return image,rect
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

輸入引數:
裁剪的車牌區域影象(Mat型別),rect也是裁剪的車牌部分的影象(Mat型別)

實現處理:
1.將原來車牌影象resize大小:66*16*3
2.將原來灰度圖顏色通道[0, 255]轉化為float型別[0,1]
3.將輸入66*16(float),輸入進模型進行測試self.modelFineMapping.predict

(7)modelFineMapping模型

class LPR():
    def __init__(self,model_detection,model_finemapping,model_seq_rec):
        self.watch_cascade = cv2.CascadeClassifier(model_detection)
        self.modelFineMapping = self.model_finemapping()
        self.modelFineMapping.load_weights(model_finemapping)
        self.modelSeqRec = self.model_seq_rec(model_seq_rec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

model_finemapping()函式

    def model_finemapping(self):
        input = Input(shape=[16, 66, 3])  # change this shape to [None,None,3] to enable arbitraty shape input
        x = Conv2D(10, (3, 3), strides=1, padding='valid', name='conv1')(input)
        x = Activation("relu", name='relu1')(x)
        x = MaxPool2D(pool_size=2)(x)
        x = Conv2D(16, (3, 3), strides=1, padding='valid', name='conv2')(x)
        x = Activation("relu", name='relu2')(x)
        x = Conv2D(32, (3, 3), strides=1, padding='valid', name='conv3')(x)
        x = Activation("relu", name='relu3')(x)
        x = Flatten()(x)
        output = Dense(2,name = "dense")(x)
        output = Activation("relu", name='relu4')(output)
        model = Model([input], [output])
        return model
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

keras網路模型:對車牌的左右邊界進行迴歸
通過modelFineMapping.loadweights()函式載入模型檔案
通過modelFineMapping.predict輸出網路結果

輸入:16*66*3 tensor
輸出:長度為2的tensor

(8)recognizeOne函式
對於每個車牌區域的for迴圈中,經過fineMappingVertical處理後輸入到recognizeOne函式,進行ocr識別

        for j,plate in enumerate(images):
            plate, rect  =plate
            image_rgb,rect_refine = self.finemappingVertical(plate,rect)
            res,confidence = self.recognizeOne(image_rgb)
            res_set.append([res,confidence,rect_refine])
  • 1
  • 2
  • 3
  • 4
  • 5

recognizeOne()

    def recognizeOne(self,src):
        x_tempx = src
        x_temp = cv2.resize(x_tempx,( 164,48))
        x_temp = x_temp.transpose(1, 0, 2)
        y_pred = self.modelSeqRec.predict(np.array([x_temp]))
        y_pred = y_pred[:,2:,:]
        return self.fastdecode(y_pred)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1.將前面的(136, 36)影象resize成(164, 48)
2.將影象轉置,輸入

**(9)**modelSecRec模型
基於GRU的序列模型從OCR模型中修改的網路模型
model_sec_rec函式

    def model_seq_rec(self,model_path):
        width, height, n_len, n_class = 164, 48, 7, len(chars)+ 1
        rnn_size = 256
        input_tensor = Input((164, 48, 3))
        x = input_tensor
        base_conv = 32
        for i in range(3):
            x = Conv2D(base_conv * (2 ** (i)), (3, 3))(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = MaxPooling2D(pool_size=(2, 2))(x)
        conv_shape = x.get_shape()
        x = Reshape(target_shape=(int(conv_shape[1]), int(conv_shape[2] * conv_shape[3])))(x)
        x = Dense(32)(x)
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
        gru_1 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru1')(x)
        gru_1b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', name='gru1_b')(x)
        gru1_merged = add([gru_1, gru_1b])
        gru_2 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru2')(gru1_merged)
        gru_2b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', name='gru2_b')(gru1_merged)
        x = concatenate([gru_2, gru_2b])
        x = Dropout(0.25)(x)
        x = Dense(n_class, kernel_initializer='he_normal', activation='softmax')(x)
        base_model = Model(inputs=input_tensor, outputs=x)
        base_model.load_weights(model_path)
        return base_model
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

model_path為模型weights檔案路徑
ocr部分的網路模型(keras模型)
輸入層:164*48*3的tensor
輸出層:長度為7 的tensor,類別有len(chars)+1種

chars:

chars = [u"京", u"滬", u"津", u"渝", u"冀", u"晉", u"蒙", u"遼", u"吉", u"黑", u"蘇", u"浙", u"皖", u"閩", u"贛", u"魯", u"豫", u"鄂", u"湘", u"粵", u"桂",
             u"瓊", u"川", u"貴", u"雲", u"藏", u"陝", u"甘", u"青", u"寧", u"新", u"0", u"1", u"2", u"3", u"4", u"5", u"6", u"7", u"8", u"9", u"A",
             u"B", u"C", u"D", u"E", u"F", u"G", u"H", u"J", u"K", u"L", u"M", u"N", u"P", u"Q", u"R", u"S", u"T", u"U", u"V", u"W", u"X",
             u"Y", u"Z",u"港",u"學",u"使",u"警",u"澳",u"掛",u"軍",u"北",u"南",u"廣",u"沈",u"蘭",u"成",u"濟",u"海",u"民",u"航",u"空"
             ]
  • 1
  • 2
  • 3
  • 4
  • 5

網路結構是三層卷積神經網路(CNN),以及四層內控迴圈單元(GRU)組成