一,基本思路

生成数据(验证码样本)

1.验证码类型

我们这里生成的验证码是当前最常见的验证码即由26位大小写英文字母和0到9十个数字组成的字符型验证码。

2.生成方式

我们可以选择两种方式来生成我们的训练数据。一种是一次性生成几万张图(保存到本地),另一种是定义一个数据生成器(数据未被保存)。两种方式各有千秋,第一种方式的好处是训练的时候显卡利用率高,如果你需要经常调参,可以一次生成,多次使用;第二种方式的好处是你不需要生成大量数据,训练过程中可以利用 CPU 生成数据,而且还有一个好处是你可以无限生成数据。我们这里采用第二种方式来生成数据。

3.验证码图片如下

CNN使用代码 cnn code_数据

 

处理数据

1.色彩在验证码中并不重要,我们将彩色验证码图片转为黑白,3维转1维,减少干扰数据。

2.将黑白验证码图片及其文本内容转化为数值数据。

3.设置验证码图片组,以便让图片数据分批次进行训练。

 

创建模型

这里用到了 5 层网络,前 3 层为卷积层,第 4、5 层为全连接层。对 4 层隐藏层都进行 dropout。网络结构如下所示: input——>conv——>pool——>dropout——>conv——>pool——>dropout——>conv——>pool——>dropout——>fully connected layer——>dropout——>fully connected layer——>output

 

训练数据

这里选择交叉熵损失函数。sigmod_cross适用于每个类别相互独立但不互斥,如图中可以有字母和数字。每批次采用 64 个训练样本,每训练100次测试一次样本识别的准确度,当准确度大于 95% 时保存模型,当准确度大于99%时训练结束。我们这里采用CPU来训练模型,训练大概需要7个小时左右才能达到95%的准确度,什么时候能达到99%呢?抱歉,看到过了0.95就激动了,我没能等到0.99。

 

测试模型

生成验证码——>调用保存的模型——>识别验证码——>输出识别结果。

 

二,主要工具

Anaconda

Anaconda指的是一个开源的Python发行版本,其包含了conda、Python等180多个科学包及其依赖项。

 

Python3.6

Python 是一个有条理的和强大的面向对象的程序设计语言,类似于Perl, Ruby, Scheme, Java.

 

TensorFlow

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,是一种通用深度学习框架,可被用于语音识别图像识别等多项机器学习和深度学习领域。

 

Captcha

captcha 是用 python 写的生成验证码的库,它支持图片验证码和语音验证码,我们使用的是它生成图片验证码的功能。

 

PyCharm(其他IDE当然也行)

PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具。

 

三,准备工作

安装Anaconda

1.从官方网站下载Anaconda:

https://www.anaconda.com/download/

CNN使用代码 cnn code_数据_02

2.进行软件安装(这个和普通的没什么特别区别):

注意一点:

CNN使用代码 cnn code_CNN使用代码_03

3.安装完成Anaconda之后进行环境变量的检测:  进入Anaconda Prompt,输入 conda info --envs

CNN使用代码 cnn code_CNN使用代码_04

4.检测anaconda环境是否安装成功:进入Anaconda Prompt,输入 conda --version

CNN使用代码 cnn code_CNN使用代码_05

 

安装TensorFlow(Python3.6)

1.检测目前安装了哪些环境变量:打开Anaconda Prompt,输入  conda info --envs

CNN使用代码 cnn code_数据_06

2.接着在Anaconda中安装一个内置的python版本解析器(其实就是python的版本),安装python版本:输入 conda create --name tensorflow python=3.6

3.检测tensflow的环境是否添加到了Anaconda里面:输入 conda info --envs

CNN使用代码 cnn code_数据_07

4.启动tensorflow环境:在Anaconda Prompt中,输入  activate   tensorflow 

CNN使用代码 cnn code_CNN使用代码_08

5.正式安装tensorflow:在tensorflow环境下,输入  pip install --upgrade --ignore-installed tensorflow

6.使用时注意:创建新python项目时,python解析器必须选择我们之前安装tensorflow的目录下的解析器,否则的话,我们之后使用不了tensorflow模块的内容。

CNN使用代码 cnn code_数据_09

7.测试是否安装成功:在tensorflow环境下,调出python,运行一个简单程序。

import tensorflow as

hello = tf.constant('Hello, TensorFlow!')

sess = tf.Session() print(sess.run(hello))

CNN使用代码 cnn code_CNN使用代码_10

这里运行程序时它会跳出一句话(不解决的话以后也一直有)说我们下载TensorFlow的版本不支持cpu的AVX2编译,一般没啥影响,不用管他。当然解决办法也有,请参考 。

8.注意:tensorflow环境下没有的模块,需要在tensorflow环境下安装。如下面的captcha等python库。

 

安装Captcha

1.启动tensorflow环境:在Anaconda Prompt中,直接输入  activate   tensorflow
2.在tensorflow环境下安装captcha:输入pip install captcha

 

安装其他python

random,numpy ,matplotlib,os,datetime ——同上,从略

 

四,详细代码

代码分成4个部分,分别是captcha_create.py,captcha_process.py,cnn_train.py,cnn_test.py,将它们全部放到一个名为captchaCnn的python packge下。

CNN使用代码 cnn code_数据_11

 

captcha_create.py(生成数据)

import random

import numpy as np

from PIL import Image

import matplotlib.pyplot as plt

from captcha.image import ImageCaptcha



# 验证码基本信息

NUMBER = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

LOW_CASE = ['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']

UP_CASE = ['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']

CAPTCHA_LIST = NUMBER + LOW_CASE + UP_CASE

CAPTCHA_LEN = 4

CAPTCHA_HEIGHT = 60

CAPTCHA_WIDTH = 160



# 随机生成验证码文本

def random_captcha_text(char_set=CAPTCHA_LIST, captcha_size=CAPTCHA_LEN):

    '''

    :param char_set:

    :param captcha_size:

    :return:

    '''

    captcha_text = [random.choice(char_set) for _ in range(captcha_size)]

    return ''.join(captcha_text)



# 生成随机验证码

def gen_captcha_text_and_image(width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT,save=None):

    '''

    :param width:

    :param height:

    :param save:

    :return: np数组

    '''

    image = ImageCaptcha(width=width, height=height)

    # 验证码文本

    captcha_text = random_captcha_text()

    captcha = image.generate(captcha_text)

    # 保存

    if save: image.write(captcha_text, captcha_text + '.jpg')

    captcha_image = Image.open(captcha)

    # 转化为np数组

    captcha_image = np.array(captcha_image)

    return captcha_text, captcha_image



if __name__ == '__main__':

    a = gen_captcha_text_and_image(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, save=False)

    print(a[0])

    plt.imshow(a[1])

    plt.show()

CNN使用代码 cnn code_数据_12

 

captcha_process.py(处理数据)

import numpy as np

from captchaCnn.captcha_create import gen_captcha_text_and_image

from captchaCnn.captcha_create import CAPTCHA_LIST, CAPTCHA_LEN, CAPTCHA_HEIGHT, CAPTCHA_WIDTH



# 图片转为黑白,3维转1维

def convert2gray(img):

    '''

    :param img:

    :return:

    '''

    if len(img.shape) > 2:

        img = np.mean(img, -1)

    return img



# 验证码文本转为向量

def text2vec(text, captcha_len=CAPTCHA_LEN, captcha_list=CAPTCHA_LIST):

    '''

    :param text:

    :param captcha_len:

    :param captcha_list:

    :return:

    '''

    text_len = len(text)

    if text_len > captcha_len:

        raise ValueError('验证码最长4个字符')

    vector = np.zeros(captcha_len * len(captcha_list))

    for i in range(text_len): vector[captcha_list.index(text[i])+i*len(captcha_list)] = 1

    return vector



# 验证码向量转为文本

def vec2text(vec, captcha_list=CAPTCHA_LIST, size=CAPTCHA_LEN):

    '''

    :param vec:

    :param captcha_list:

    :param size:

    :return:

    '''

    vec_idx = vec

    text_list = [captcha_list[v] for v in vec_idx]

    return ''.join(text_list)



# 返回特定shape图片

def wrap_gen_captcha_text_and_image(shape=(CAPTCHA_HEIGHT, CAPTCHA_WIDTH, 3)):

    '''

    :param shape:

    :return:

    '''

    while True:

        t, im = gen_captcha_text_and_image()

        if im.shape == shape: return t, im



# 获取训练图片组

def next_batch(batch_count=60, width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT):

    '''

    :param batch_count:

    :param width:

    :param height:

    :return:

    '''

    batch_x = np.zeros([batch_count, width * height])

    batch_y = np.zeros([batch_count, CAPTCHA_LEN * len(CAPTCHA_LIST)])

    for i in range(batch_count):

        text, image = wrap_gen_captcha_text_and_image()

        image = convert2gray(image)

        # 将图片数组一维化 同时将文本也对应在两个二维组的同一行

        batch_x[i, :] = image.flatten() / 255

        batch_y[i, :] = text2vec(text)

    # 返回该训练批次

    return batch_x, batch_y



if __name__ == '__main__':

    x, y = next_batch(batch_count=1)

    print(x,'\n\n', y)

CNN使用代码 cnn code_验证码_13

 

cnn_train.py(创建模型,训练数据)

import os

import tensorflow as tf

from datetime import datetime

from captchaCnn.captcha_process import next_batch

from captchaCnn.captcha_create import CAPTCHA_HEIGHT, CAPTCHA_WIDTH, CAPTCHA_LEN, CAPTCHA_LIST



# 随机生成权重

def weight_variable(shape, w_alpha=0.01):

    '''

    :param shape:

    :param w_alpha:

    :return:

    '''

    initial = w_alpha * tf.random_normal(shape)

    return tf.Variable(initial)



# 随机生成偏置项

def bias_variable(shape, b_alpha=0.1):

    '''

    :param shape:

    :param b_alpha:

    :return:

    '''

    initial = b_alpha * tf.random_normal(shape)

    return tf.Variable(initial)



# 局部变量线性组合,步长为1,模式‘SAME’代表卷积后图片尺寸不变,即零边距

def conv2d(x, w):

    '''

    :param x:

    :param w:

    :return:

    '''

    return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')



# max pooling,取出区域内最大值为代表特征, 2x2pool,图片尺寸变为1/2

def max_pool_2x2(x):

    '''

    :param x:

    :return:

    '''

    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')



# 三层卷积神经网络计算图

def cnn_graph(x, keep_prob, size, captcha_list=CAPTCHA_LIST, captcha_len=CAPTCHA_LEN):

    '''

    :param x:

    :param keep_prob:

    :param size:

    :param captcha_list:

    :param captcha_len:

    :return:

    '''

    # 图片reshape为4维向量

    image_height, image_width = size

    x_image = tf.reshape(x, shape=[-1, image_height, image_width, 1])



    # 第一层

    # filter定义为3x3x1, 输出32个特征, 即32个filter

    w_conv1 = weight_variable([3, 3, 1, 32])

    b_conv1 = bias_variable([32])

    # rulu激活函数

    h_conv1 = tf.nn.relu(tf.nn.bias_add(conv2d(x_image, w_conv1), b_conv1))

    # 池化

    h_pool1 = max_pool_2x2(h_conv1)

    # dropout防止过拟合

    h_drop1 = tf.nn.dropout(h_pool1, keep_prob)



    # 第二层

    w_conv2 = weight_variable([3, 3, 32, 64])

    b_conv2 = bias_variable([64])

    h_conv2 = tf.nn.relu(tf.nn.bias_add(conv2d(h_drop1, w_conv2), b_conv2))

    h_pool2 = max_pool_2x2(h_conv2)

    h_drop2 = tf.nn.dropout(h_pool2, keep_prob)



    # 第三层

    w_conv3 = weight_variable([3, 3, 64, 64])

    b_conv3 = bias_variable([64])

    h_conv3 = tf.nn.relu(tf.nn.bias_add(conv2d(h_drop2, w_conv3), b_conv3))

    h_pool3 = max_pool_2x2(h_conv3)

    h_drop3 = tf.nn.dropout(h_pool3, keep_prob)



    # 全连接层

    image_height = int(h_drop3.shape[1])

    image_width = int(h_drop3.shape[2])

    w_fc = weight_variable([image_height*image_width*64, 1024])

    b_fc = bias_variable([1024])

    h_drop3_re = tf.reshape(h_drop3, [-1, image_height*image_width*64])

    h_fc = tf.nn.relu(tf.add(tf.matmul(h_drop3_re, w_fc), b_fc))

    h_drop_fc = tf.nn.dropout(h_fc, keep_prob)



    # 全连接层(输出层)

    w_out = weight_variable([1024, len(captcha_list)*captcha_len])

    b_out = bias_variable([len(captcha_list)*captcha_len])

    y_conv = tf.add(tf.matmul(h_drop_fc, w_out), b_out)

    return y_conv



# 最小化loss

def optimize_graph(y, y_conv):

    '''

    优化计算图

    :param y:

    :param y_conv:

    :return:

    '''

    # 交叉熵计算loss

    # sigmod_cross适用于每个类别相互独立但不互斥,如图中可以有字母和数字

    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=y_conv, labels=y))

    # 最小化loss优化

    optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

    return optimizer



# 偏差计算

def accuracy_graph(y, y_conv, width=len(CAPTCHA_LIST), height=CAPTCHA_LEN):

    '''

    :param y:

    :param y_conv:

    :param width:

    :param height:

    :return:

    '''

    # 预测值

    predict = tf.reshape(y_conv, [-1, height, width])

    max_predict_idx = tf.argmax(predict, 2)

    # 标签

    label = tf.reshape(y, [-1, height, width])

    max_label_idx = tf.argmax(label, 2)

    correct_p = tf.equal(max_predict_idx, max_label_idx)

    accuracy = tf.reduce_mean(tf.cast(correct_p, tf.float32))

    return accuracy



# 训练cnn

def train(height=CAPTCHA_HEIGHT, width=CAPTCHA_WIDTH, y_size=len(CAPTCHA_LIST)*CAPTCHA_LEN):

    '''

    :param height:

    :param width:

    :param y_size:

    :return:

    '''

    acc_rate = 0.95

    # 按照图片大小申请占位符

    x = tf.placeholder(tf.float32, [None, height * width])

    y = tf.placeholder(tf.float32, [None, y_size])

    # 防止过拟合 训练时启用 测试时不启用

    keep_prob = tf.placeholder(tf.float32)

    # cnn模型

    y_conv = cnn_graph(x, keep_prob, (height, width))

    # 最优化

    optimizer = optimize_graph(y, y_conv)

    # 偏差

    accuracy = accuracy_graph(y, y_conv)

    # 启动会话.开始训练

    saver = tf.train.Saver()

    sess = tf.Session()

    sess.run(tf.global_variables_initializer())

    step = 0

    while 1:

        # 每批次64个样本

        batch_x, batch_y = next_batch(64)

        sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: 0.75})

        # 每训练一百次测试一次

        if step % 100 == 0:

            batch_x_test, batch_y_test = next_batch(100)

            acc = sess.run(accuracy, feed_dict={x: batch_x_test, y: batch_y_test, keep_prob: 1.0})

            print(datetime.now().strftime('%c'), ' step:', step, ' accuracy:', acc)

            # 偏差满足要求,保存模型

            if acc > acc_rate:

                model_path = os.getcwd() + os.sep + str(acc_rate) + "captcha.model"

                saver.save(sess, model_path, global_step=step)

                acc_rate += 0.01

                if acc_rate > 0.99: break

        step += 1

    sess.close()



if __name__ == '__main__':

    train()

CNN使用代码 cnn code_CNN使用代码_14

刚开始准确度很低,但不能就此断定程序写错了,要运行久一点才能说明问题。前3000样本的准确度都比较低也基本上变化不大,3000样本以后就有点起色了,准确度在缓缓上升。到13000个样本左右时,准确度达到0.9左右。到20000个样本左右时,准确度达到0.95左右。

我这里用CPU花了7个小时训练了20600个样本才达到95%的准确率。保存的模型包括4个文件,如下图。

CNN使用代码 cnn code_CNN使用代码_15

 

cnn_test.py(测试模型)

import tensorflow as tf

from captchaCnn.cnn_train import cnn_graph

from captchaCnn.captcha_create import gen_captcha_text_and_image

from captchaCnn.captcha_process import vec2text, convert2gray

from captchaCnn.captcha_process import CAPTCHA_LIST, CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LEN



# 验证码图片转化为文本

def captcha2text(image_list, height=CAPTCHA_HEIGHT, width=CAPTCHA_WIDTH):

    '''

    :param image_list:

    :param height:

    :param width:

    :return:

    '''

    x = tf.placeholder(tf.float32, [None, height * width])

    keep_prob = tf.placeholder(tf.float32)

    y_conv = cnn_graph(x, keep_prob, (height, width))

    saver = tf.train.Saver()

    with tf.Session() as sess:

        saver.restore(sess, tf.train.latest_checkpoint('.'))

        predict = tf.argmax(tf.reshape(y_conv, [-1, CAPTCHA_LEN, len(CAPTCHA_LIST)]), 2)

        vector_list = sess.run(predict, feed_dict={x: image_list, keep_prob: 1})

        vector_list = vector_list.tolist()

        text_list = [vec2text(vector) for vector in vector_list]

        return text_list



if __name__ == '__main__':

    text, image = gen_captcha_text_and_image()

    image = convert2gray(image)

    image = image.flatten() / 255

    pre_text = captcha2text([image])

    print('Label:', text, ' Predict:', pre_text)

CNN使用代码 cnn code_数据_16

 

五,总结

CNN识别验证码的优点

1.模型高效

经过不算特别长的时间的训练可以达到很高的识别准确度。

2.模型存粹

不需要额外的OCR软件或验证码识别API等。

 

CNN识别验证码的缺点

1.样本获取难

若想将模型实际应用,那么训练样本的获得就是个问题。要么找不到已知验证码的生成器,要么找到的验证码没有文本标签,需要通过人工打码或打码平台等获得验证码标签。

2.灵活性不高

CNN识别验证码不同于将图片进行分割等处理的识别方法,而直接对整个图片进行学习。因此只要验证码的风格与原样本不同时,识别准确率就比较低。

在CNN识别验证码中,卷积层对于图像是没有尺寸限制要求的。卷积仅于自身的卷积核大小,维度有关,输入向量大小对其无影响。但全连接层的输入是固定大小的,如果输入向量的维数不固定,那么全连接的权值参数的量也是不固定的,就会造成网络的动态变化,无法实现参数训练的目的。因此即使是同样的验证码如果改变了图片的大小,程序就会报错。不过这个问题貌似是可以解决的,具体请参见 。

 

六,写在最后

本人第一次做这个,知识精力有限,如有疏漏之处还望各位大佬指正。