CNN实现车牌识别

引 言
车牌自动识别是一项利用车辆的动态视频或静态图像进行牌照号码、牌照颜色自动识别的模式识别技术。其硬件设备一般包括触发设备(监测车辆是否进入视野)、摄像设备、照明设备、图像采集设备、识别车牌号码的处理机(如计算机)等,其软件核心包括车牌定位算法、车牌字符分割算法和光学字符识别算法等。某些车牌识别系统还具有通过视频图像判断是否有车的功能称之为视频车辆检测。一个完整的车牌识别系统应包括车辆检测、图像采集、车牌识别等几部分。当车辆检测部分检测到车辆到达时触发图像采集单元,采集当前的视频图像。车牌识别单元对图像进行处理,定位出牌照位置,再将牌照中的字符分割出来进行识别,然后组成牌照号码输出。而本文所使用的是最为常见的CNN,由此可见CNN应用的广泛,以及其图像提取能力的先进
1 数据集准备
1.1 特征提取
车牌的组成是由汉字、字母与数字组成,所以我们需要将一些车牌的图片中每一个汉字和、字母和数字进行分开分别保存在不同的文件夹下,比如提取的江苏缩写汉字“苏”保存在“zh_su”文件夹下,这部分的提取可以建立OpenCV代码批量处理即可,具体不细讲,和本文无关。
1.2 图像处理
将定位的图片特征进行OpenCV的图像处理,其中包括二值化、中值化、腐化、膨胀、中心化模糊、边缘检测、形态学梯度等等,其基本目的是在于方便和CNN提取的图像进行分类识别。处理的图像如下
CNN实现车牌识别_第1张图片
CNN实现车牌识别_第2张图片

Fig.1 file condition
2 建模外代码处理
2.1 训练前代码
首先需要导入所需要使用的python库,其中包括sys库、os库、numpy库、cv2库、TensorFlow框架、sklearn库。接着建立三个数字用来识别输出内容。分别是数字、字母、汉字组成,即为车牌的组成。建立未知量由三者加和而成,用以识别分类。定义图像尺寸、一次训练的图像数量以及输入输出的占位变量,代码如下:

import sys
import os
import numpy as np
import cv2
import tensorflow as tf
from sklearn.model_selection import train_test_split

numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
alphbets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                 'U', 'V', 'W', 'X', 'Y', 'Z']
chinese = ['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']



dataset = numbers + alphbets + chinese
dataset_len = len(dataset)
img_size = 20
y_size = len(dataset)
batch_size = 10
x_place = tf.placeholder(dtype=tf.float32, shape=[None, img_size, img_size], name='x_place')
y_place = tf.placeholder(dtype=tf.float32, shape=[None, y_size], name='y_place')
keep_place = tf.placeholder(dtype=tf.float32, name='keep_place')

2.2 训练部分代码
定义函数为train函数,其中要包括对数据集的初始化,即为输入输出的图像路径,以便于找到训练数据集。接着使用softmax函数对经过神经网络输出的结果进行分类识别,接着使用梯度下降的方法去训练模型,其中的损失函数就是softmax函数,模型训练的目的在于通过改变其中的每一个神经连接的参数去缩小损失函数的值。下面建立tf会话,初始化模型目的在于将W和B参数清零,启动模型保存器,然后每隔10步保存一次模型,然后输出精确度。代码如下:

def train(data_dir,save_model_path):
        print('ready load train dataset')
        X, y = init_data(data_dir)
        print('success load' + str(len(y)) + 'datas')
        train_x, test_x, train_y, test_y = train_test_split(X, y, test_size=0.2, random_state=0)

        out_put = cnn_construct()
        predicts = tf.nn.softmax(out_put)
        predicts = tf.argmax(predicts, axis=1)
        actual_y = tf.argmax(y_place, axis=1)
        accuracy = tf.reduce_mean(tf.cast(tf.equal(predicts, actual_y), dtype=tf.float32))
        cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=out_put, labels=y_place))
        opt = tf.train.AdamOptimizer(learning_rate=0.001)
        train_step = opt.minimize(cost)

        with tf.Session() as sess:
            init = tf.global_variables_initializer()
            sess.run(init)
            step = 0
            saver = tf.train.Saver()
            while True:
                train_index = np.random.choice(len(train_x), batch_size, replace=False)
                train_randx = train_x[train_index]
                train_randy = train_y[train_index]
                _, loss = sess.run([train_step, cost],
                                   feed_dict={x_place:train_randx,y_place:train_randy,keep_place:0.75})
                step += 1

                if step % 10 == 0:
                    test_index = np.random.choice(len(test_x), batch_size, replace=False)
                    test_randx = test_x[test_index]
                    test_randy = test_y[test_index]
                    acc = sess.run(accuracy,feed_dict={x_place : test_randx, y_place : test_randy,
                                                       keep_place : 1.0})
                    print(step, loss)
                    if step % 50 == 0:
                        print('accuracy:' + str(acc))
                    if step % 500 == 0:
                        saver.save(sess, save_model_path, global_step=step)
                    if acc > 0.99 and step > 500:
                        saver.save(sess, save_model_path, global_step=step)
                        break

3 模型参数
神经网络层为3层卷积,3层全连接,最后输出。其中输入层,接着接入第一层卷积,卷积核为33,步长为1,32个卷积核,定义其为训练的参数w1,同时偏执项为b1和w个数相同,激活函数relu输出,再接池化层和降采样,目的在于降噪。同样每一层的卷积核都是为33,保持上层输入等于输出即可。全连接层直接是w加b即可,代码如下:

def cnn_construct():
        x_input = tf.reshape(x_place, shape=[-1, 20, 20, 1])

        cw1 = tf.Variable(tf.random_normal(shape=[3, 3, 1, 32], stddev=0.01), dtype=tf.float32)
        cb1 = tf.Variable(tf.random_normal(shape=[32]), dtype=tf.float32)
        conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x_input,filter=cw1,strides=[1,1,1,1],padding='SAME'),cb1))
        conv1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
        conv1 = tf.nn.dropout(conv1, keep_place)

        cw2 = tf.Variable(tf.random_normal(shape=[3, 3, 32, 64], stddev=0.01), dtype=tf.float32)
        cb2 = tf.Variable(tf.random_normal(shape=[64]), dtype=tf.float32)
        conv2 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv1,filter=cw2,strides=[1,1,1,1],padding='SAME'),cb2))
        conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
        conv2 = tf.nn.dropout(conv2, keep_place)

        cw3 = tf.Variable(tf.random_normal(shape=[3, 3, 64, 128], stddev=0.01), dtype=tf.float32)
        cb3 = tf.Variable(tf.random_normal(shape=[128]), dtype=tf.float32)
        conv3 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv2,filter=cw3,strides=[1,1,1,1],padding='SAME'),cb3))
        conv3 = tf.nn.max_pool(conv3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
        conv3 = tf.nn.dropout(conv3, keep_place)

        conv_out = tf.reshape(conv3, shape=[-1, 3 * 3 * 128])

        fw1 = tf.Variable(tf.random_normal(shape=[3 * 3 * 128, 1024], stddev=0.01), dtype=tf.float32)
        fb1 = tf.Variable(tf.random_normal(shape=[1024]), dtype=tf.float32)
        fully1 = tf.nn.relu(tf.add(tf.matmul(conv_out, fw1), fb1))
        fully1 = tf.nn.dropout(fully1, keep_place)

        fw2 = tf.Variable(tf.random_normal(shape=[1024, 1024], stddev=0.01), dtype=tf.float32)
        fb2 = tf.Variable(tf.random_normal(shape=[1024]), dtype=tf.float32)
        fully2 = tf.nn.relu(tf.add(tf.matmul(fully1, fw2), fb2))
        fully2 = tf.nn.dropout(fully2, keep_place)

        fw3 = tf.Variable(tf.random_normal(shape=[1024, dataset_len], stddev=0.01), dtype=tf.float32)
        fb3 = tf.Variable(tf.random_normal(shape=[dataset_len]), dtype=tf.float32)
        fully3 = tf.add(tf.matmul(fully2, fw3), fb3, name='out_put')

        return fully3

4 CNN特征提取
4.1 CNN相对于全连接优势
全连接神经网络相对于CNN,主要有以下几个方面的问题:
1、参数数量太多考虑一个输入10001000像素的图片(一百万像素),输入层有10001000=100万节点。假设第一个隐藏层有100个节点,那么仅这一层就有(1000*1000+1)*100=1亿参数,参数过于庞大!因此可见,图像只扩大一点,参数数量就会多很多,因此它的扩展性很差。
2、没有利用像素之间的位置信息 对于图像识别任务来说,每个像素和其周围像素的联系是比较紧密的,和离得很远的像素的联系可能就很小了。如果一个神经元和上一层所有神经元相连,那么就相当于对于一个像素来说,把图像的所有像素都等同看待,这不符合前面的假设。当我们完成每个连接权重的学习之后,最终可能会发现,有大量的权重,它们的值都是很小的(也就是这些连接其实无关紧要)。努力学习大量并不重要的权重,这样的学习必将是非常低效的。
3、网络层数限制 我们知道网络层数越多其表达能力越强,但是通过梯度下降方法训练深度全连接神经网络很困难,因为全连接神经网络的梯度很难传递超过3层。因此,我们不可能得到一个很深的全连接神经网络,也就限制了它的能力。
卷积神经网络优势思路:
1.利用局部连接,这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。
2、权值共享,一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。
3,、下采样,可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。
卷积网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式,只要用已知的模式对卷积网络加以训练,网络就具有输入输出对之间的映射能力。
CNN一个非常重要的特点就是头重脚轻(越往输入权值越小,越往输出权值越多),呈现出一个倒三角的形态,这就很好地避免了BP神经网络中反向传播的时候梯度损失得太快。
卷积神经网络CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。由于CNN的特征检测层通过训练数据进行学习,所以在使用CNN时,避免了显式的特征抽取,而隐式地从训练数据中进行学习;再者由于同一特征映射面上的神经元权值相同,所以网络可以并行学习,这也是卷积网络相对于神经元彼此相连网络的一大优势。卷积神经网络以其局部权值共享的特殊结构在语音识别和图像处理方面有着独特的优越性,其布局更接近于实际的生物神经网络,权值共享降低了网络的复杂性,特别是多维输入向量的图像可以直接输入网络这一特点避免了特征提取和分类过程中数据重建的复杂度
对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。
4.2 三维的层结构
卷积神经网络的层结构和全连接神经网络的层结构有很大不同,全连接神经网络每层的神经元是按照一维排列的,也就是排成一条线的样子;而卷积神经网络每层的神经元是按照三维排列的,也就是排成一个长方体的样子,有宽度、高度和深度。
其中输入层的宽度和高度对应于输入图像的宽度和高度,比如它的深度为1。其后可以接卷积层,第一个卷积层对这幅图像进行了卷积操作,可以得到了三个Feature Map,也就是三套参数,每个Filter都可以把原始输入图像卷积得到一个Feature Map,三个Filter就可以得到三个Feature Map。至于一个卷积层可以有多少个Filter,那是可以自由设定的。也就是说,卷积层的Filter个数也是一个超参数。我们可以把Feature Map可以看做是通过卷积变换提取到的图像特征,三个Filter就对原始图像提取出三组不同的特征,也就是得到了三个Feature Map,也称做三个通道(channel)。
在第一个卷积层之后,Pooling层对三个Feature Map做了下采样,得到了三个更小的Feature Map。接着,是第二个卷积层,它有5个Filter。每个Fitler都把前面下采样之后的3个Feature Map卷积在一起,得到一个新的Feature Map。这样,5个Filter就得到了5个Feature Map。接着,是第二个Pooling,继续对5个Feature Map进行下采样,得到了5个更小的Feature Map。
接着可以介入最后两层全连接层。第一个全连接层的每个神经元,和上一层5个Feature Map中的每个神经元相连,第二个全连接层(也就是输出层)的每个神经元,则和第一个全连接层的每个神经元相连,这样得到了整个网络的输出。
4.2 卷积神经网络的训练
和全连接神经网络相比,卷积神经网络的训练要复杂一些。但训练的原理是一样的:利用链式求导计算损失函数对每个权重的偏导数(梯度),然后根据梯度下降公式更新权重。训练算法依然是反向传播算法。
整个算法分为三个步骤:

前向计算每个神经元的输出值;
反向计算每个神经元的误差项,在有的文献中也叫做敏感度(sensitivity)。它实际上是网络的损失函数对神经元加权输入的偏导数;
计算每个神经元连接权重的梯度(表示从神经元连接到神经元的权重)。
最后,根据梯度下降法则更新每个权重即可。

对于卷积神经网络,由于涉及到局部连接、下采样的等操作,影响到了第二步误差项的具体计算方法,而权值共享影响了第三步权重的梯度的计算方法。
池化层的具体作用:

  1. 特征不变性,也就是我们在图像处理中经常提到的特征的尺度不变性,池化操作就是图像的resize,平时一张狗的图像被缩小了一倍我们还能认出这是一张狗的照片,这说明这张图像中仍保留着狗最重要的特征,我们一看就能判断图像中画的是一只狗,图像压缩时去掉的信息只是一些无关紧要的信息,而留下的信息则是具有尺度不变性的特征,是最能表达图像的特征。
  2. 特征降维,我们知道一幅图像含有的信息是很大的,特征也很多,但是有些信息对于我们做图像任务时没有太多用途或者有重复,我们可以把这类冗余信息去除,把最重要的特征抽取出来,这也是池化操作的一大作用。
  3. 在一定程度上防止过拟合,更方便优化

5 总体思路
首先是车牌定位,利用CNN建模找到精准的图像中车牌位置,并提取保存在文件夹中。
接着是车牌组成的汉字、字母、数字识别,利用数据集训练即可,参照图像识别思路
最后是识别部分代码,即对一张车的图片利用车牌定位提取车牌,接着利用OpenCV边缘检测,将其中的每一个汉字、数字、字母分割提取,接着利用第二步训练的模型识别,最终可得车牌号

欢迎大家关注公众号“人工智能实战及常见代码分享”,
代码获取后台回复“CNN实现车牌识别”
CNN实现车牌识别_第3张图片

你可能感兴趣的:(代码)