HyperLRP是一个开源的、基于深度学习高性能中文车牌识别库,由北京智云视图科技有限公司开发,支持PHP、C/C++、Python语言,Windows/Mac/Linux/Android/IOS平台。与较为流行的开源的EasyPR相比,它的检测速度和鲁棒性和多场景的适应性都要好于目前开源的EasyPR,HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等。
- TODO
支持多种车牌以及双层
支持大角度车牌
轻量级识别模型- 特性
速度快 720p,单核 Intel 2.2G CPU (MaBook Pro 2015)平均识别时间低于100ms
基于端到端的车牌识别无需进行字符分割
识别率高,卡口场景准确率在95%-97%左右
轻量,总代码量不超1k行
1、在Anaconda所建环境的命令行中输入
pip3 install hyperlpr -i https://mirrors.aliyun.com/pypi/simple/
2、下载整个开源库文件
https://gitee.com/zeusees/HyperLPR/tree/master/
把hyperlpr_py3并改名为hyperlpr复制到Anaconda3安装路径\envs\环境名\lib\python3.6\site-packages,与原目录下的hyperlpr合并
3、新建开发文件
将开源库中的Font、model、HyperLprGUI.py、HyperLprLite.py、demo.py拷到此目录中,创建一个Images的目录,放置待识别车牌的车辆照片,命名为plate1.jpg、plate2.jpg、plate3.jpg。
4、测试代码demo.py(单张图片检测部分)
from hyperlpr.pipline import drawRectBox
import HyperLPRLite as pr
import cv2
import numpy as np
grr = cv2.imread("./Images/plate3.png")
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、在命令行里输入sudo pip3 install hyperlpr
2、将上面的hyperlpr_py3并改名为hyperlpr拷贝到树莓派/home/pi/.local/lib/python3.7/site-packages
demo中总的流程分为:
1)利用cascade进行车牌定位
2)对粗定位的车牌进行左右边界回归,去除车牌两边多余的部分
3)将精定位的车牌送入CRNN网络进行字符识别
from hyperlpr.pipline import drawRectBox
import HyperLPRLite as pr
import cv2
Github : https://github.com/icepoint666/HyperLPR
Forked from zeusees/HyperLPR 略加改动
只需要三个代码文件:
- multi_demo.py
- demo.py
- HyperLPRLite.py
grr = cv2.imread("./Images/plate3.png")
opencv2的imread函数导入图片, 返回的是Mat类型。
model = pr.LPR("model/cascade.xml","model/model12.h5","model/ocr_plate_all_gru.h5")
HyperLPRLiite.py中的LPR类构造函数导入model, 参数就是训练好的三个模型文件,名字分别是:
- model/cascade.xml cascade模型
- model/model12.h5 左右边界回归模型
- model/ocr_plate_all_gru.h5 字符识别模型
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)
参数 model_detection 就是文件 model/cascade.xml 用到了opencv2的CascadeClassifier()函数
参数输入.xml或者.yaml文件,表示加载模型,是一种基于Haar特征的级联分类器用于物体检测的模型
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
利用了keras网络模型:对车牌的左右边界进行回归,通过modelFineMapping.loadweights()函数加载模型文件并由modelFineMapping.predict输出网络结果
- 输入:16663 tensor
- 输出:长度为2的tensor
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
基于GRU的序列模型从OCR模型中修改的网络模型
model_sec_rec函数
model_path为模型weights文件路径 ocr部分的网络模型(keras模型) 输入层:164483的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"空"
]
网络结构是三层卷积神经网络(CNN),以及四层内控循环单元(GRU)组成
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)
输入为一个Mat类型的图片 输出为识别的车牌字符串,以及confidence可信度,
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
首先用到detectPlateRough()函数
- detectPlateRough函数是返回图像中所有车牌的边框在图片中的bbox,返回的是一个表示车牌区域坐标边框的list
对于每个车牌区域的for循环中,经过
- filemappingVertical()函数
处理后输入
- recognizeOne()函数
进行ocr识别
下面是SImpleRecognizePlateByE2E()函数中所用到的函数解析
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
利用多尺度检测detectMultiScale,得到可能的车牌,及其在原图中的rect位置
输入参数:
- image_gray: 一个rgb图像,Mat类型
- resize_h: 重新设定的图像大小
- top_bottom_padding_rate: 表示要裁剪掉图片的上下部占比
这个函数实现的处理:
- resize图像大小,cv2.resize函数,按照原来图像比例裁剪图片,根据输入的top_bottom_padding_rate如果是0.1,那么上面裁剪掉0.1height,下面也裁剪掉0.1height
- 将图像从rgb转化为灰度 cv2.cvtColor函数,cv2.COLOR_RGB2GRAY
- 根据前面的cv2.CascadeClassifier()物体检测模型,输入image_gray灰度图像,边框可识别的最小size,最大size,输出得到车牌在图像中的offset,也就是边框左上角坐标(x, y )以及边框高度( h )和宽度( w )
- 对得到的车牌边框的bbox进行扩大,也就是宽度左右各扩大0.14倍,高度上下各扩大0.15倍。
- 返回图片中所有识别出来的车牌边框bbox,这个list作为返回结果。
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
输入参数: 裁剪的车牌区域图像(Mat类型),rect也是裁剪的车牌部分的图像(Mat类型)
实现处理:
1.将原来车牌图像resize大小:66163
2.将原来灰度图颜色通道[0, 255]转化为float类型[0,1]
3.将输入66*16(float),输入进模型进行测试self.modelFineMapping.predict
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.将前面的(136, 36)图像resize成(164, 48)
2.将图像转置,输入
#打上boundingbox和标签
def drawRectBox(image,rect,addText):
cv2.rectangle(image, (int(rect[0]), int(rect[1])), (int(rect[0] + rect[2]), int(rect[1] + rect[3])), (0,0, 255), 2,cv2.LINE_AA)
cv2.rectangle(image, (int(rect[0]-1), int(rect[1])-16), (int(rect[0] + 115), int(rect[1])), (0, 0, 255), -1,
cv2.LINE_AA)
img = Image.fromarray(image)
draw = ImageDraw.Draw(img)
draw.text((int(rect[0]+1), int(rect[1]-16)), addText.decode("utf-8"), (255, 255, 255), font=fontC)
imagex = np.array(img)
return imagex
参考教程
HyperLPR车牌识别代码解读
hyperlpr学习笔记——demo学习