用4层神经网络识别教务处验证码

2018-03-12  本文已影响0人  Birdy潇

这次弄一个图片验证码识别的东东

先来看看教务处网站的验证码

要做的就是识别出这个图片验证码里包含的字符,包括数字和大小写字母,总计62个。这其实涉及的是 PhotoOCR 的内容,图像文本检测与识别。识别完成后就可以写个爬虫 post 账户密码和验证码内容来完成登陆。

初级方案

惯例,写个爬虫,把教务处的验证码图片先给爬下来,总计有500张图片:


1.图像预处理

选取一张图片观察


将其放大后的效果:


观察原图可以发现字符与背景有较大的差别,那么直接用灰度化把三通道的原图降维成一个单通道矩阵



用维纳滤波进行降躁。然后选取好阈值后进行二值化操作:


2.图像分割

一幅图片里面有四个字符,只能一个字符一个字符的识别,所以可以将这个图片进行等间距的分割,做一个segmentation,取出单个字符,再打上相应的标签。
一幅图像的大小为 20 x 60,我们做分割的时候可以将其隔15个像素做一次分割,分成四个字符,最终分割成 500 x 4=2000 张图片:


划分效果还不错,每一个segment都包含了该字符。因为这个教务处的验证码字符黏连情况没有那么严重,所以进行这样的分割很方便。

#-*-coding:UTF-8-*-
import matplotlib.pyplot as plt
from PIL import Image,ImageEnhance
import numpy as np
import xlrd
import tensorflow as tf

#灰度变换
def RGB2GRAY(RGB):
    return np.dot(RGB[...,:3],[0.299, 0.587, 0.114])

#二值化
def Threshold(image,threshold=50):
    shape=image.shape
    if len(shape)==3:
        image=RGB2GRAY(image)
    return (image>threshold)*255

#图像分割
def Img_segment(image):
    result=[]
    for i in range(4):
        roi=image[0:20,i*15:(i+1)*15]
        result.append(roi)
    return result

3.构建Training set 和Dev set

对每一张图片手动输入相应的字符码,总计500个,花了一个小时全部打上标签

数据集的划分按0.8的比例划分,400张构成Training set,100张构成Dev set。

# 对标签one_hot encode
def readdata(filepath):
    chars = [
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        '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',
        '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',
    ]
    # 构建labels字典
    dic={}
    num=0
    for char in chars:
        dic[char]=num
        num+=1
    # 读取标签
    workbook=xlrd.open_workbook(filepath)
    table=workbook.sheets()[0]
    list_Str=[]
    for i in range(table.nrows):
        list_Str.append(table.row_values(i)[0])
    data=''.join(list_Str)

    # 字符标签映射成数字标签
    labels=np.zeros((len(chars),len(data)))
    for i in range(len(data)):
        labels[dic[data[i]],i]=1
    return labels

# 批处理图像
def batch_process():
    pic_num = 500
    count = 0
    data = np.zeros((20*15,1))
    for i in range(pic_num):
        origi_img = np.array(Image.open("Pictures\\" + str(i) + ".jpg"))
        gray_img = RGB2GRAY(origi_img)
        thres_img = Threshold(gray_img, threshold=120)
        segments = Img_segment(thres_img)
        for item in segments:
            #image = Image.fromarray(item)
            #image = image.convert('RGB')
            #image.save("Pictures2\\" + str(count) + '.jpg')
            item_flatten = item.reshape((20*15,1))
            data = np.hstack((data,item_flatten))
            count += 1
    return  data[:,1:]

4.搭建Neural Network Structure

输入特征是20*15=300维的向量,所以input layer 有300个神经元,hidden layer我设计了三层,最后一层是62分类的softmax层
结构如下:

这是张结构示意图,神经元个数远比这个多得多

总计有:(500 x 300+300 x 500+100 x 300+62 x 100) + (500+300+100+62)=337162个参数。每一层的activation function我用的都是RelU,线性修正单元,这个函数不像tanh和sigmoid,它具有固定的导数,同时也能提供非线性,在用backprop时,能减少计算量,训练的更快。Backprop训练时,我用的是Adam算法,它是梯度下降结合Momentum和RMSprop的一个变种优化算法,本质上还是梯度下降,改变的是梯度下降的方向和步长。我设计的是四层网络,是一个浅层网络,但为了避免出现梯度消失和梯度爆炸的问题,依旧采用泽维尔的weights初始化方式,梯度消失和梯度爆炸现在还没有一个很好的解决方法,不过何凯明创建的152层的ResNet构建了残差网络,利用跳跃链接可以创建更深层次的网络。

对于2000张图片用batch方式训练,迭代3500个epoch,learning rate为0.0002,训练了将近3分钟

def Initialize_parameters():
    # initialize parameters
    W1 = tf.get_variable("W1", [500, 300], initializer=tf.contrib.layers.xavier_initializer())
    b1 = tf.get_variable("b1", [500, 1], initializer=tf.zeros_initializer())
    W2 = tf.get_variable("W2", [300, 500], initializer=tf.contrib.layers.xavier_initializer())
    b2 = tf.get_variable("b2", [300, 1], initializer=tf.zeros_initializer())
    W3 = tf.get_variable("W3", [100, 300], initializer=tf.contrib.layers.xavier_initializer())
    b3 = tf.get_variable("b3", [100, 1], initializer=tf.zeros_initializer())
    W4 = tf.get_variable("W4", [62, 100], initializer=tf.contrib.layers.xavier_initializer())
    b4 = tf.get_variable("b4", [62, 1], initializer=tf.zeros_initializer())

    parameters={"W1":W1,"b1":b1,"W2":W2,"b2":b2,"W3":W3,"b3":b3,"W4":W4,"b4":b4}

    return parameters

def forward_propagation(X,parameters):
    # fetch the parameters
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    W3 = parameters["W3"]
    b3 = parameters["b3"]
    W4 = parameters["W4"]
    b4 = parameters["b4"]

    # compute forward propagation
    Z1 = tf.add(tf.matmul(W1, X), b1)
    A1 = tf.nn.relu(Z1)
    # add the dropout to reduce variance
    A1_drop = tf.nn.dropout(A1, keep_prob=0.9)
    Z2 = tf.add(tf.matmul(W2, A1_drop), b2)
    A2 = tf.nn.relu(Z2)
    A2_drop = tf.nn.dropout(A2, keep_prob=0.8)
    Z3 = tf.add(tf.matmul(W3, A2_drop), b3)
    A3 = tf.nn.relu(Z3)
    A3_drop = tf.nn.dropout(A3, keep_prob=1)
    Z4 = tf.add(tf.matmul(W4, A3_drop), b4)

    return Z4

# compute the cost
def compute_cost(Z4,Y):
    # compute cost
    logits = tf.transpose(Z4)
    labels = tf.transpose(Y)
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels))
    return cost

# predict
def predict(X,parameters):
    # convert to tensor
    W1 = tf.convert_to_tensor(parameters["W1"])
    b1 = tf.convert_to_tensor(parameters["b1"])
    W2 = tf.convert_to_tensor(parameters["W2"])
    b2 = tf.convert_to_tensor(parameters["b2"])
    W3 = tf.convert_to_tensor(parameters["W3"])
    b3 = tf.convert_to_tensor(parameters["b3"])
    W4 = tf.convert_to_tensor(parameters["W4"])
    b4 = tf.convert_to_tensor(parameters["b4"])

    x = tf.placeholder(tf.float32,[300,1])

    Z1 = tf.add(tf.matmul(W1, x), b1)
    A1 = tf.nn.relu(Z1)
    Z2 = tf.add(tf.matmul(W2, A1), b2)
    A2 = tf.nn.relu(Z2)
    Z3 = tf.add(tf.matmul(W3, A2), b3)
    A3 = tf.nn.relu(Z3)
    Z4 = tf.add(tf.matmul(W4, A3), b4)
    p = tf.argmax(Z4)
    sess = tf.Session()
    prediction = sess.run(p,feed_dict={x:X})[0]

    # creat labels dict
    chars = [
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        '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',
        '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',
    ]
    dict={}
    count=0
    for i in range(62):
        dict[str(i)]=chars[i]

    return dict[str(prediction)]

def nn_model(X_train,Y_train,X_test,Y_test,epoch_nums=1000,learning_rate=0.01,):

    # creat placeholders
    X=tf.placeholder(tf.float32,[300,None])
    Y=tf.placeholder(tf.float32,[62,None])
    costs=[]

    # initialize parameters
    parameters=Initialize_parameters()

    # forward propagation
    Z4=forward_propagation(X,parameters)

    # compute cost
    cost=compute_cost(Z4,Y)

    # define the tensorflow optimizer,Use AdamOptimizer
    optimizer=tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

    # initialize all variables
    init=tf.global_variables_initializer()
    saver=tf.train.Saver()

    # Start the Session to run the tensorflow graph
    with tf.Session() as sess:
        # Run the initialization
        sess.run(init)

        # Do the training loop
        for epoch in range(epoch_nums):
            _,epoch_cost=sess.run([optimizer,cost],feed_dict={X:X_train,Y:Y_train})
            # print the cost
            if epoch % 100==0:
                print("Cost after %i epoch:%f"%(epoch,epoch_cost))
            if epoch % 5==0:
                costs.append(epoch_cost)
        # save the model
        saver.save(sess,"SaveModel/model1.ckpt")

        # plot the cost
        plt.plot(np.squeeze(costs)[20:])
        plt.ylabel('cost')
        plt.xlabel('iterations (per fives)')
        plt.title("Learning rate =" + str(learning_rate))
        plt.show()

        # Save the parameters
        parameters=sess.run(parameters)
        print("Parameters have been trained!")

        # caculate the accuracy
        correct_prediction=tf.equal(tf.argmax(Z4),tf.argmax(Y))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
        print("Train Accuracy:", sess.run(accuracy,feed_dict={X:X_train,Y:Y_train}))
        print("Test Accuracy:",sess.run(accuracy,feed_dict={X:X_test,Y:Y_test}))

        return parameters

if __name__=='__main__':
    X=batch_process()
    Y=readdata("labels.xlsx")
    X_train=X[:,0:400*4]
    Y_train=Y[:,0:400*4]
    X_test=X[:,400*4:]
    Y_test=Y[:,400*4:]
    parameters=nn_model(X_train,Y_train,X_test,Y_test,epoch_nums=5000,learning_rate=0.0002)
    X_single=X_train[:,987].reshape((300,1))
    prediction=predict(X_single,parameters)
    print("The Prediction of this picture is:",prediction)
    plt.imshow(X_single.reshape((20,15)),cmap='Greys_r')
    plt.show()

可以看到每次迭代都能保证Cost是降低的,保证了梯度沿正确的方向下降,把Cost曲线画出来

曲线沿下降趋势,最后收敛了。训练结束,看看分类的准确度


在训练集上达到了0.99的精度,在测试集上达到了0.9425的精度,训练结果还是很不错的。另外,数据集只有2000张图片,为了防止overfitting,采用dropout来减少神经元对整个网络的贡献,dropout是逆向随机失活,是按一定的概率让神经元失活,使其output为0,在该网络中失活概率分别是0.1,0.2,和 0。此外,L2正则也是常用的减弱overfitting的方法。

现在选取一张图片来做个测试,这是字符Y
将其输入到我们的训练好的网络中,output: 不错,能成功识别,先在随机挑选一张完整的验证码图片:

加上一个loop循环处理

for i in range(4):
    X_feed=X[i].reshape((20*15,1))
    prediction = sess.run(p, feed_dict={x:X_feed})[0]
    result.append(dict[str(prediction)])
    print("the prediction is",''.join(result))

得到的结果:


完美!成功识别。
0.95的准确率基本上能完成大部分的验证码识别,后续可以写个爬虫登陆教务系统将账户密码和识别出的验证码一起发送,实现自动登陆
上一篇 下一篇

猜你喜欢

热点阅读