文章目录

  • 0、前言
  • 1、车牌定位
  • 1.1、训练分类器
  • 1.2、车牌粗定位
  • 1.3、倾斜矫正
  • 1.3.1、计算车牌的倾斜角
  • 1.3.2、旋转
  • 1.4、车牌精定位
  • 2、CNN训练
  • 2.2、构建CNN网络
  • 2.3、执行训练
  • 3、车牌识别
  • 3.1、读取1.4中精定位获取的图片
  • 3.2、使用CNN训练模型对精定位图片进行识别
  • 4、Mysql存储


0、前言

之前写过一篇基于字符切割的车牌识别博客,当时也成功做出来了,但是识别率不高,而且对图片要求苛刻,例如不能大角度倾斜,不能反光,不能太模糊等。有很多原因导致当时的方法不能有效且准确的识别车牌。为了提高识别率,有在网上参考了很多前辈的算法及实现过程。期间也化了不少时间,不过最终的识别效果还是挺高的。

本项目使用的资源如下

云服务器:VAST

数据集:CCPD数据集,正样本约20w张,负样本约3w张

https://github.com/detectRecog/CCPD

语言:python3

IDE:PyCharm

python依赖包:

  • Numpy
  • OpenCV
  • PIL
  • Tensorflow

1、车牌定位

1.1、训练分类器

其实在最开始准备正样本阶段,要做的第一步是标记车牌,就是从一张图片里边crop出车牌的位置。由于机器学习模型训练都需要大量的训练集,所以对于标记车牌,要不手动,要不掏钱。不过好的一点是找到了一个已经标记好的数据集。该数据集中图片的文件名包含了很多图片信息,在此就不介绍了,github上已经说明清楚。

第一步:将CCPD中的车牌从数据集中crop出来

import cv2
import hashlib
import os,sys

provinces = ["皖", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "京", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "警", "学", "O"]
alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'O']
ads = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X','Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'O']

if not os.path.exists("output"):
    os.mkdir("output")

h = 40
w = 250

path = r"F:\ccpd_dataset\ccpd_base" # 根据自己的实际情况
fi = open("label.txt","w",encoding="utf-8")
for img_name in os.listdir(path):
    
    # 读取图片的完整名字
    image = cv2.imread(path + "/" + img_name)
    
    # 以 - 为分隔符,将图片名切分,其中iname[4]为车牌字符,iname[2]为车牌坐标
    iname = img_name.rsplit('/', 1)[-1].rsplit('.', 1)[0].split('-')
    tempName = iname[4].split("_")
    name = provinces[int(tempName[0])] + alphabets[int(tempName[1])] + ads[int(tempName[2])] \
           + ads[int(tempName[3])] + ads[int(tempName[4])] + ads[int(tempName[5])] + ads[int(tempName[6])]
           
    # crop车牌的左上角和右下角坐标
    [leftUp, rightDown] = [[int(eel) for eel in el.split('&')] for el in iname[2].split('_')]
    
    # crop图片
    img = image[leftUp[1]:rightDown[1],leftUp[0]:rightDown[0]]
    height, width, depth = img.shape
    
    # 要将图片压缩成40*250,计算压缩比
    imgScale = h/height
    deltaD = int((w/imgScale-width)/2)   # (目标宽-实际宽)/2,因为要分别向左、右拓宽,所有需要除以2
    leftUp[0] = leftUp[0] - deltaD       # 切割宽度向左平移,保证补够250
    rightDown[0] = rightDown[0] + deltaD # 切割宽度向右平移,保证补够250

    if(leftUp[0] < 0):                   # 如果向左平移为负,坐标为0
        rightDown[0] = rightDown[0] - leftUp[0]
        leftUp[0] = 0;
    # 按照   高/宽 = 40 / 250 的比例切割,注意切的结果不是40和250
    img = image[leftUp[1]:rightDown[1],leftUp[0]:rightDown[0]]
    # resize成40*250
    newimg = cv2.resize(img,(w,h))

    cv2.imwrite("output/" + img_name, newimg)
    fi.write(img_name + ":" + name +"\r\n")

fi.close()

第二步:接下来就是把正负样本放置在对应的文件夹中,正样本要求图片大小一致,负样本可以随心

第三步:在当前目录下我们还得到了pos_image.txt文件和neg_image.txt文件。其中pos_image.txt文件内容格式

基于深度学习的汽车分类项目界面opencv_深度学习


imag/pic00xxxx.jpg表示图片名字,1 0 0 分别表示图片中车牌的数量、起始坐标(x,y),图片宽高(w,h)负样本neg_image.txt格式

基于深度学习的汽车分类项目界面opencv_Tensorflow_02


第三步:使用opencv的opencv_createsamples.exe生成正样本vec文件

!opencv\build\x64\vc14\bin\opencv_createsamples.exe -vec pos.vec -info pos_image.txt -bg neg_image.txt -w 250 -h 40 -num 199998

用opencv_traincascade.exe训练级联分类器

opencv_traincascade.exe  -data xml -vec  pos.vec  -bg  neg_image.txt -numPos  16000  -numNeg  3036  -numStages  12  -precalcValBufSize  5000  -precalcIdxBufSize  5000  -w 250  -h  40  -maxWeakCount  200  -mode ALL  -minHitRate  0.990  -maxFalseAlarmRate 0.20

训练完成后会在xml目录下生成一个cascade.xml文件,该文件就是训练的的级联分类器数据,我们利用它就可以识别出车牌。

1.2、车牌粗定位

使用上一目训练出的分类器进行车牌粗定位

image = cv.imread('1.png')                  ## 读取图片
image = cv.GaussianBlur(image , (3,3) , 0)  ## 高斯去噪
resize_h = image.shape[0]                   
resize_w = int( image.shape[1] / image.shape[0] * resize_h)
image = cv.resize(image , (resize_w,resize_h)) ## 将原图resize
image_gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY) ## 转换成灰度图
clf = cv.CascadeClassifier('./cascade.xml')       ## 导入分类器
area = clf.detectMultiScale(image_gray)           ## 检测车牌,可能不止一张

image2 = image.copy()
for (x,y,w,h) in area:
	x -= w * 0.14
	w += w * 0.28
	y -= h * 0.15
	h += h * 0.3
	cropped_images = image2[int(x):int(x+w), int(y):int(y+h)]
return cropped_images
1.3、倾斜矫正

如果车牌倾斜没有矫正,那么水平投影和垂直投影,甚至铆钉都无法正常处理。所以,当车辆信息中获取车牌的第一步,应该是检查倾斜角度,做倾斜矫正。

1.3.1、计算车牌的倾斜角

使用基于方向场的算法检测车牌的倾角

def skew_detection(image_gray):
    h, w = image_gray.shape[:2]
    ## cornerEigenValsAndVecs()计算图像块的特征值和特征向量用于角点检测
    eigen = cv2.cornerEigenValsAndVecs(image_gray,12, 5)
    angle_sur = np.zeros(180,np.uint);
    eigen = eigen.reshape(h, w, 3, 2)
    flow = eigen[:,:,2]
    vis = image_gray.copy()
    vis[:] = (192 + np.uint32(vis)) / 2
    d = 12
    points =  np.dstack( np.mgrid[d/2:w:d, d/2:h:d] ).reshape(-1, 2)
    for x, y in points:
        x = int(x)
        y = int(y)
        vx, vy = np.int32(flow[y, x]*d)
        # cv2.line(rgb, (x-vx, y-vy), (x+vx, y+vy), (0, 355, 0), 1, cv2.LINE_AA)
        ang = angle(vx,vy);
        angle_sur[(ang+180)%180] +=1;
    # torr_bin = 30
    angle_sur = angle_sur.astype(np.float)
    angle_sur = (angle_sur-angle_sur.min())/(angle_sur.max()-angle_sur.min())
    angle_sur = filters.gaussian_filter1d(angle_sur,5)
    skew_v_val =  angle_sur[20:180-20].max();
    skew_v = angle_sur[30:180-30].argmax() + 30;
    skew_h_A = angle_sur[0:30].max()
    skew_h_B = angle_sur[150:180].max()
    skew_h = 0;
    if (skew_h_A > skew_v_val*0.3 or skew_h_B > skew_v_val*0.3):
        if skew_h_A>=skew_h_B:
            skew_h = angle_sur[0:20].argmax()
        else:
            skew_h = - angle_sur[160:180].argmax()
    return skew_h,skew_v
1.3.2、旋转

首先计算出目标图像上四个顶点的坐标,再使用OpenCV的getPerspectiveTransform()计算透视变换矩阵,最后使用OpenCV的warpPerspective()进行透视变换

## v:垂直方向旋转
def v_rot(img,angel,shape,max_angel):

    ## shape[0]高   shape[1]宽
    size_o = [shape[1],shape[0]]
	## size = (宽 + 高*cos(最大角),高)
    size = (shape[1] + int(shape[0]*np.cos((float(max_angel )/180) * 3.14)),shape[0])
    ## interval = |sin(倾角)*高|
    interval = abs( int( np.sin((float(angel) /180) * 3.14)* shape[0]));
    ## 原图像的坐标
    pts1 = np.float32([[0,0]         ,[0,size_o[1]],[size_o[0],0],[size_o[0],size_o[1]]])
    if(angel>0):
        pts2 = np.float32([[interval,0],[0,size[1]  ],[size[0],0  ],[size[0]-interval,size_o[1]]])
    else:
        pts2 = np.float32([[0,0],[interval,size[1]  ],[size[0]-interval,0  ],[size[0],size_o[1]]])
    ## getPerspectiveTransform 用于计算透视变换矩阵
    ## getPerspectiveTransform( // 返回3x3透视变换矩阵
	## const cv::Point2f* src,  // 源图像四个顶点坐标(点数组)
	## const cv::Point2f* dst   // 目标图像上四个顶点的坐标(点数组)
	## )
    M  = cv2.getPerspectiveTransform(pts1,pts2);
    ## 透视变换
    dst = cv2.warpPerspective(img,M,size);

    return dst,M
1.4、车牌精定位
def fitLine_ransac(pts,zero_add = 0 ):
    if len(pts)>=2:
        [vx, vy, x, y] = cv2.fitLine(pts, cv2.DIST_HUBER, 0, 0.01, 0.01)
        lefty = int((-x * vy / vx) + y)
        righty = int(((136- x) * vy / vx) + y)
        return lefty+30+zero_add,righty+30+zero_add
    return 0,0
    
## 精定位算法
def findContoursAndDrawBoundingBox(image_rgb):
    line_upper  = [];
    line_lower = [];

    line_experiment = []
    grouped_rects = []
    gray_image = cv2.cvtColor(image_rgb,cv2.COLOR_BGR2GRAY)
    
    ## 15级自适应二值化
    for k in np.linspace(-50, 0, 16):
        binary_niblack = cv2.adaptiveThreshold(gray_image,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,17,k)
        
        ## 找出轮廓
        contours, hierarchy = cv2.findContours(binary_niblack.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
        for contour in contours:
            ## boundingRect()返回外部矩形边界
            bdbox = cv2.boundingRect(contour)
            if ((bdbox[3]/float(bdbox[2])>0.7 and bdbox[3]*bdbox[2]>100 and bdbox[3]*bdbox[2]<1200) or (bdbox[3]/float(bdbox[2])>3 and bdbox[3]*bdbox[2]<100))  :
                line_upper.append([bdbox[0],bdbox[1]])
                line_lower.append([bdbox[0]+bdbox[2],bdbox[1]+bdbox[3]])

                line_experiment.append([bdbox[0],bdbox[1]])
                line_experiment.append([bdbox[0]+bdbox[2],bdbox[1]+bdbox[3]])

    rgb = cv2.copyMakeBorder(image_rgb,30,30,0,0,cv2.BORDER_REPLICATE)
    leftyA, rightyA = fitLine_ransac(np.array(line_lower),3)
    rows,cols = rgb.shape[:2]
    
    leftyB, rightyB = fitLine_ransac(np.array(line_upper),-3)
    rows,cols = rgb.shape[:2]
    pts_map1  = np.float32([[cols - 1, rightyA], [0, leftyA],[cols - 1, rightyB], [0, leftyB]])
    pts_map2 = np.float32([[136,36],[0,36],[136,0],[0,0]])
    mat = cv2.getPerspectiveTransform(pts_map1,pts_map2)
    image = cv2.warpPerspective(rgb,mat,(136,36),flags=cv2.INTER_CUBIC)
    image,M = fastDeskew(image)
    return image

2、CNN训练

2.1、获取batch_size张图片用于训练

train_data_dir = r'F:\train' # 根据实际情况替换
test_data_dir  = r'F:\test'

train_file_name_list = os.listdir(train_data_dir)

## 把彩色图像转为灰度图像(色彩对识别验证码没有什么用)
def convert2gray(img):
    if len(img.shape) > 2:
        gray = np.mean(img, -1)
        return gray
    else:
        return img
 
 def gen_train_data(batch_size=32):
    '''
    生成训练数据
    '''
    train_image_list = random.sample(train_file_name_list, batch_size)
    x_data = []
    y_data = []
    for train_image in train_image_list:
        if train_image.endswith('.gif'):
            image    = Image.open(os.path.join(train_data_dir, train_image))
            image_np = np.array(image)
            image_np = convert2gray(image_np)
            assert image_np.shape == (60, 160)
            image_np = np.expand_dims(image_np, 2)
            x_data.append(image_np)
            y_data.append(np.array(list(train_image.split('.')[0])).astype(np.int32))
    x_data = np.array(x_data).astype(np.float) ## x_data.shape = (64, 60, 160, 1)
    y_data = np.array(y_data)                  ## y_data.shape = (64, 7)
    return x_data, y_data
2.2、构建CNN网络
X = tf.placeholder(tf.float32, name="input") ## 亦即 X = x_data , so X.shape = (batch_size, 60, 160, 1)
Y = tf.placeholder(tf.int32)                 ## 亦即 Y = y_data , so Y.shape = (batch_size, 7)
keep_prob = tf.placeholder(tf.float32)
y_one_hot = tf.one_hot(Y, 65, 1, 0)          ## y_one_hot.shape = (batch_size , 7 , 10)
y_one_hot = tf.cast(y_one_hot, tf.float32)   ## tf.cast()类型转换

# keep_prob = 1.0
def net(w_alpha=0.01, b_alpha=0.1):
    '''
    网络部分,三层卷积层,一个全连接层
    :param w_alpha:
    :param b_alpha:
    :return: 网络输出,Tensor格式
    '''
    conv2d_size = 3      ## 卷积核大小
    featuremap_num1 = 32 ## 卷积层1输出的featuremap的数量
    featuremap_num2 = 64 ## 卷积层2输出的featuremap的数量
    featuremap_num3 = 64 ## 卷积层3输出的featuremap的数量
    
    strides_conv2d1 = [1, 1, 1, 1] ##卷积层1的卷积步长
    strides_conv2d2 = [1, 1, 1, 1] ##卷积层2的卷积步长
    strides_conv2d3 = [1, 1, 1, 1] ##卷积层3的卷积步长
    
    strides_pool1   = [1, 2, 2, 1] ## 卷积层1的池化步长
    strides_pool2   = [1, 2, 2, 1] ## 卷积层2的池化步长
    strides_pool3   = [1, 2, 2, 1] ## 卷积层3的池化步长
    
    ksize_pool1     = [1, 2, 2, 1] ## 卷积层1的池化size
    ksize_pool2     = [1, 2, 2, 1] ## 卷积层2的池化size
    ksize_pool3     = [1, 2, 2, 1] ## 卷积层3的池化size
    
    neuron_num      = 1024         ## 神经元数量
    
    FC_dim1         = float(img_height)/(strides_pool1[1]*strides_pool2[1]*strides_pool3[1])
    FC_dim2         = float(img_width) /(strides_pool1[2]*strides_pool2[2]*strides_pool3[2])
    FC_dim          = int(round(FC_dim1) * round(FC_dim2) * featuremap_num3)
    
    ## -1代表先不考虑输入的图片有多少张,1是channel的数量 
    x_reshape = tf.reshape(X,shape = [-1, img_height, img_width, 1])
    
    ## 构建卷积层1 
    ## tf.Variable()定义变量
    ## tf.random_normal()生成N维服从正太分布的数据
    w_c1 = tf.Variable(w_alpha * tf.random_normal([conv2d_size, conv2d_size, 1, featuremap_num1])) # 卷积核3*3,1个channel,16个卷积核,形成16个featuremap
    b_c1 = tf.Variable(b_alpha * tf.random_normal([featuremap_num1])) # 16个featuremap的偏置
    ## tf.nn.relu()激活函数,激活函数是用来加入非线性因素的,提高神经网络对模型的表达能力,解决线性模型所不能解决的问题。
    ## tf.nn.bias_add(value, bias, data_format=None, name=None),将偏差项bias加到values上
    ## tf.nn.conv2d()卷积计算的核心函数,讲解请参考
    ## padding参数中SAME代表给边界加上Padding让卷积的输出和输入保持相同的尺寸
    conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x_reshape, w_c1, strides=strides_conv2d1, padding='SAME'), b_c1))
    ## tf.nn.max_pool()最大值池化
    ## 经过tf.nn.max_pool(strides=[1, 2, 2, 1])后,feature_map在各个维度上都变为一半
    conv1 = tf.nn.max_pool(conv1, ksize=ksize_pool1, strides=strides_pool1, padding='SAME')
    ## tf.nn.dropout()此函数是为了防止在训练中过拟合的操作,将训练输出按一定规则进行变换
    conv1 = tf.nn.dropout(conv1, keep_prob)

    ## 构建卷积层2
    w_c2 = tf.Variable(w_alpha * tf.random_normal([conv2d_size, conv2d_size, featuremap_num1, featuremap_num2])) # 注意这里channel值是16
    b_c2 = tf.Variable(b_alpha * tf.random_normal([featuremap_num2]))
    conv2 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv1, w_c2, strides=strides_conv2d1, padding='SAME'), b_c2))
    conv2 = tf.nn.max_pool(conv2, ksize=ksize_pool2, strides=strides_pool2, padding='SAME')
    conv2 = tf.nn.dropout(conv2, keep_prob)
    
    ## 构建卷积层3
    w_c3 = tf.Variable(w_alpha * tf.random_normal([conv2d_size, conv2d_size, featuremap_num2, featuremap_num3]))
    b_c3 = tf.Variable(b_alpha * tf.random_normal([featuremap_num3]))
    conv3 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv2, w_c3, strides=strides_conv2d1, padding='SAME'), b_c3))
    conv3 = tf.nn.max_pool(conv3, ksize=ksize_pool3, strides=strides_pool3, padding='SAME')
    conv3 = tf.nn.dropout(conv3, keep_prob)

    ## 构建全连接层,这个全连接层的输出才是最后要提取的特征
    # Fully connected layer
    # 随机生成权重
    ## https://stackoverflow.com/questions/43010339/python-tensorflow-input-to-reshape-is-a-tensor-with-92416-values-but-the-re
    # w_d = tf.Variable(w_alpha * tf.random_normal([3 * 8 * 64, 128]))
    # w_d = tf.Variable(w_alpha * tf.random_normal([163840, 128]))   # 128个神经元
    w_d = tf.Variable(w_alpha * tf.random_normal([FC_dim, neuron_num]))   # 1024个神经元
    # 随机生成偏置
    b_d = tf.Variable(b_alpha * tf.random_normal([neuron_num]))
    dense = tf.reshape(conv3, [-1, w_d.get_shape().as_list()[0]])
    ## 最后要提取的特征
    dense = tf.nn.relu(tf.add(tf.matmul(dense, w_d), b_d))

    ## 输出层
    w_out = tf.Variable(w_alpha * tf.random_normal([neuron_num, 65]))  # 40个神经元
    b_out = tf.Variable(b_alpha * tf.random_normal([65]))
    out = tf.add(tf.matmul(dense, w_out), b_out) ## out.shape = (64,65)
    return out
2.3、执行训练
def train():
    
    batch_size_train = 64  ## 一个训练batch的数量
    batch_size_test  = 100 ## 一个测试batch的数量
    
    cnn = net()
    # loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=cnn, labels=y_one_hot))
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=cnn, labels=y_one_hot))
    # optimizer 为了加快训练 learning_rate应该开始大,然后慢慢衰减
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)
    
    print('开始执行训练')

    predict = net()
    max_idx_p = tf.argmax(predict, 2)  ## 通过argmax返回的index可得出识别的图片的字符值
    max_idx_l = tf.argmax(tf.reshape(y_one_hot, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)
    correct_pred = tf.equal(max_idx_p, max_idx_l)
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

    saver = tf.train.Saver()
    with tf.Session() as sess:
        step = 0
        tf.global_variables_initializer().run()
        while True:
            x_data, y_data = gen_one_batch(batch_size_train)
            loss_, cnn_, y_one_hot_, optimizer_ = sess.run([loss, cnn, y_one_hot, optimizer],
                                                           feed_dict={Y: y_data, X: x_data, keep_prob: 0.75})
            print('step: %4d, loss: %.4f' % (step, loss_))
            
            ## 每运行1w步,保存一次
            if 0 == (step % 10000):
                saver.save(sess, "tmp10000/", global_step=step)
                
            # 每100 step计算一次准确率
            if 0 == (step % 100):
                x_data, y_data = gen_one_batch(batch_size_test)
                acc = sess.run(accuracy, feed_dict={X: x_data, Y: y_data, keep_prob: 1.0})
                print('准确率计算:step: %4d, accuracy: %.4f' % (step, acc))
                if acc > 0.98:
                    saver.save(sess, "tmp/", global_step=step)
                    print("训练完成,模型保存成功!")
                    break
            step += 1

3、车牌识别

3.1、读取1.4中精定位获取的图片
def gen_test_data():
    x_data = []
    y_data = []
    for parent, dirnames, filenames in os.walk(test_data_dir, followlinks=True):
        for filename in filenames:
            gif_file_path = os.path.join(parent, filename)
            if gif_file_path.endswith('.gif'):
                captcha_image = Image.open(gif_file_path)
                captcha_image_np = np.array(captcha_image)
                assert captcha_image_np.shape == (60, 160)
                captcha_image_np = np.expand_dims(captcha_image_np, 2).astype(np.float32)
                x_data.append(captcha_image_np)
                y_data.append(filename.split('.')[0])
    return x_data, y_data
3.2、使用CNN训练模型对精定位图片进行识别
def test():
    if not os.path.exists(test_data_dir):
        raise RuntimeError('测试数据目录不存在,请检查"%s"参数' % 'test_data_dir')
    if tf.train.latest_checkpoint('tmp/') is None:
        raise RuntimeError('未找到模型文件,请先执行训练!')
    print('%s' % '开始执行测试')
    x, y = gen_test_data()
    print('测试目录文件数量:%d' % len(x))
    saver = tf.train.Saver()
    sum = 0
    correct = 0
    error = 0
    with tf.Session() as sess:
        saver.restore(sess, tf.train.latest_checkpoint('tmp/'))
        for i, image in enumerate(x):
            answer = y[i]
            image = image.reshape((1, img_height , img_width, 1))
            cnn_out = sess.run(cnn, feed_dict={X: image, keep_prob: 1})
            # print(cnn_out)
            cnn_out = cnn_out[0]
            predict_vector = np.argmax(cnn_out, 1)
            predict = ''
            for c in predict_vector:
                predict += str(c)
            print('预测:%s,答案:%s,判定:%s' % (predict, answer, "√" if predict == answer else "×"))
            sum += 1
            if predict == answer:
                correct += 1
            else:
                error += 1
    print("总数:%d,正确:%d,错误:%d" % (sum, correct, error))
if __name__=='__main__':
    # 测试
    test()

4、Mysql存储

将识别到的车牌保存到Mysql中

import pymysql
import datetime
import Ipynb_importer
from car_recognise import Record2Mysql
from time import sleep


db = pymysql.connect(host='localhost',port=3306,user='root',password='root',database='car_record',charset='utf8')
cursor = db.cursor()

time_in = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
sleep(2)
time_out = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
path_org,path_cut,predict_result = Record2Mysql()

sql = """INSERT INTO car_info(img_org,img_cut, predict, times, time_in,time_out)VALUES ('"""+ \
        path_org + "','" + path_cut + "','" +  predict_result + "','" + "21'" + ",'" + time_in + "','" + time_out + "')"
cursor.execute(sql)
db.commit()
db.close()

5、总结

此识别过程中,最棘手的问题就是倾斜矫正。由于不知道透视变换的使用方法,一旦遇到倾斜的车牌,基本不能正确识别。经过透视变换后,可有效切除铆钉的影响,从而正确识别到车牌字符。识别率可达95.8%

例如这张车牌,其识别结果总是粤RH469Z

基于深度学习的汽车分类项目界面opencv_CNN_03