卷积神经网络CNN微信验证码识别

舆情采集部门需要采集微信评论,需要用到识别验证码的技术。
早先学习了tensorflow和keras,跑了许多深度学习得demo却没有在实际场景应用过,现在刚好公司有需要,就使用keras来做一个验证码识别的API。
识别验证码现在网上有大量的案例,因为python可以使用
from captcha.image import ImageCaptcha来生成验证码。
生成的验证码样例如下:


验证码的噪声简单且训练集可以无限次生成的情况下,识别验证码就非常容易,github和博客中都有大量的例子,通常是使用tensorflow,Caffee,keras等等,只要愿意花时间训练都可以达到90%的识别率。

可是这样的识别方法,要用到实际场景中就很困难,因为实际环境中的验证码非常难以自己生成。有些公司会去相关网站上爬取验证码,然后自己手工标注,如果找外包付费标注倒是现实一些,自己手工标注数十万的验证码,通常不太可能。
因为公司任务,所以在网上到处寻找微信验证码,碰巧之前在一篇博客提供的网盘链接中找到了带标签的微信的验证码训练集,链接如下:https://pan.baidu.com/s/1yZeWYHcK47lqzXD8OY8hKQ 密码: hjph

url: https://mp.weixin.qq.com/cgi-bin/verifycode?username=info8@ersinfotech.com&r=1520386543758

微信验证码

方法还是使用常规验证码识别方法,使用keras中预训练好的模型fine tune就可以,但是数据集换成微信场景下的数据集。

因为验证码只有字母而且不区分大小写,那么只需要对4个字母每个字母做26分类就可以了。
keras_weight中Alex Net,google net,VGG16,VGG19,ResNet50,Xception,InceptionV3。都是由ImageNet训练而来。
模型权重可以直接去官网下载,如果速度不够快,这里提供网盘链接。
keras 预训练好的模型权重链接:https://pan.baidu.com/s/1r8y0w3vVKpbSkYrGtI93TA 密码:p00h

这里使用Xception模型

Xception模型

keras.applications.xception.Xception(include_top=True,   
                                    weights='imagenet',
                                    input_tensor=None, input_shape=None,
                                    pooling=None, classes=1000)

Xception V1 模型, 权重由ImageNet训练而来
在ImageNet上,该模型取得了验证集top1 0.790和top5 0.945的正确率

注意,该模型目前仅能以TensorFlow为后端使用,由于它依赖于"SeparableConvolution"层,目前该模型只支持channels_last的维度顺序(width, height, channels)

默认输入图片大小为299x299

参数:
include_top:是否保留顶层的3个全连接网络
weights:None代表随机初始化,即不加载预训练权重。'imagenet'代表加载预训练权重
input_tensor:可填入Keras tensor作为模型的图像输出tensor
input_shape:可选,仅当include_top=False有效,应为长为3的tuple,指明输入图片的shape,图片的宽高必须大于71,如(150,150,3)
pooling:当include_top=False时,该参数指定了池化方式。None代表不池化,最后一个卷积层的输出为4D张量。‘avg’代表全局平均池化,‘max’代表全局最大值池化。
classes:可选,图片分类的类别数,仅当include_top=True并且不加载预训练权重时可用。

训练和测试代码:

十万样本,9万训练,1万验证和批量测试,100张从微信url上抓取下来的图片做单张测试。

import numpy as np
import matplotlib.pyplot as plt # plt 用于显示图片
import matplotlib.image as mpimg # mpimg 用于读取图片
import glob
import h5py 
from keras.models import model_from_json  
import os
os.chdir(r'/home/wongyao/验证码/test')
#获取指定目录下的所有图片
samples = glob.glob(r'/home/wongyao/验证码/sample/*.jpg')
np.random.shuffle(samples) 

nb_train = 90000 #共有10万+样本,9万用于训练,1万+用于验证
train_samples = samples[:nb_train]
test_samples = samples[nb_train:]

letter_list = [chr(i) for i in range(97,123)]
#letter_list = ['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']

from keras.applications.xception import Xception,preprocess_input
from keras.layers import Input,Dense,Dropout
from keras.models import Model

img_size = (50, 120) 
#CNN适合在高宽都是偶数的情况,否则需要在边缘补齐,
#那么我们也可以把全体图片都resize成这个尺寸(高,宽,通道)
input_image = Input(shape=(img_size[0],img_size[1],3))

#具体方案是:直接将验证码输入,做几个卷积层提取特征,
#然后把这些提出来的特征连接几个分类器(26分类,因为不区分大小写),
#如果需要加入数字就是36分类,微信验证码里没有数字。

#输入图片
#用预训练的Xception提取特征,采用平均池化
base_model = Xception(input_tensor=input_image, weights='imagenet', include_top=False, pooling='avg')

#用全连接层把图片特征接上softmax然后26分类,dropout为0.5,激活使用softmax因为是多分类问题。
#ReLU - 用于隐层神经元输出
#Sigmoid - 用于隐层神经元输出
#Softmax - 用于多分类神经网络输出
#Linear - 用于回归神经网络输出(或二分类问题)
#对四个字母做26分类
predicts = [Dense(26, activation='softmax')(Dropout(0.5)(base_model.output)) for i in range(4)]

model = Model(inputs=input_image, outputs=predicts)
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

#optimizer:优化器的选择可以参考这篇博客https://www.jianshu.com/p/d99b83f4c1a6
#loss:损失函数,这里选稀疏多类对数损失
#metrics:列表,包含评估模型在训练和测试时的性能的指标,典型用法是metrics=['accuracy']如果要在多输出模型中为不同的输出指定不同的指标,可像该参数传递一个字典,例如metrics={'ouput_a': 'accuracy'}
#sample_weight_mode:如果你需要按时间步为样本赋权(2D权矩阵),将该值设为“temporal”。默认为“None”,代表按样本赋权(1D权)。如果模型有多个输出,可以向该参数传入指定sample_weight_mode的字典或列表。在下面fit函数的解释中有相关的参考内容。
#weighted_metrics: metrics列表,在训练和测试过程中,这些metrics将由sample_weight或clss_weight计算并赋权
#target_tensors: 默认情况下,Keras将为模型的目标创建一个占位符,该占位符在训练过程中将被目标数据代替。如果你想使用自己的目标张量(相应的,Keras将不会在训练时期望为这些目标张量载入外部的numpy数据),你可以通过该参数手动指定。目标张量可以是一个单独的张量(对应于单输出模型),也可以是一个张量列表,或者一个name->tensor的张量字典。
#model.summary()

from scipy import misc
#misc.imread把图片转化成矩阵,
#misc.imresize重塑图片尺寸misc.imresize(misc.imread(img), img_size)  img_size是自己设定的尺寸
#ord()函数主要用来返回对应字符的ascii码,
#chr()主要用来表示ascii码对应的字符他的输入时数字,可以用十进制,也可以用十六进制。

def data_generator(data, batch_size): #样本生成器,节省内存
    while True:
        #np.random.choice(x,y)生成一个从x中抽取的随机数,维度为y的向量,y为抽取次数
        batch = np.random.choice(data, batch_size)
        x,y = [],[]
        for img in batch:
            x.append(misc.imresize(misc.imread(img), img_size))
#读取resize图片,再存进x列表
            y.append([ord(i)-ord('a') for i in img[-8:-4]]) 
#把验证码标签添加到y列表,ord(i)-ord('a')把对应字母转化为数字a=0,b=1……z=26
        x = preprocess_input(np.array(x).astype(float))
#原先是dtype=uint8转成一个纯数字的array
        y = np.array(y)
        yield x,[y[:,i] for i in range(4)]
#输出:图片array和四个转化成数字的字母 例如:[array([6]), array([0]), array([3]), array([24])])

from keras.utils.vis_utils import plot_model
plot_model(model, to_file="model.png", show_shapes=True)

model.fit_generator(data_generator(train_samples, 100), steps_per_epoch=1000, epochs=10, validation_data=data_generator(test_samples, 100), validation_steps=100) 
#参数:generator生成器函数,
#samples_per_epoch,每个epoch以经过模型的样本数达到samples_per_epoch时,记一个epoch结束
#step_per_epoch:整数,当生成器返回step_per_epoch次数据是记一个epoch结束,执行下一个epoch
#epochs:整数,数据迭代的轮数
#validation_data三种形式之一,生成器,类(inputs,targets)的元组,或者(inputs,targets,sample_weights)的元祖
#若validation_data为生成器,validation_steps参数代表验证集生成器返回次数
#class_weight:规定类别权重的字典,将类别映射为权重,常用于处理样本不均衡问题。
#sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。
#workers:最大进程数
#max_q_size:生成器队列的最大容量
#pickle_safe: 若为真,则使用基于进程的线程。由于该实现依赖多进程,不能传递non picklable(无法被pickle序列化)的参数到生成器中,因为无法轻易将它们传入子进程中。
#initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。

#保存模型
model.save('CaptchaForWechat.h5')

#评价模型的全对率(批量预测,num为预测样本总量)
def predict1(num):
    from tqdm import tqdm
    total = 0.
    right = 0.
    step = 0
    for x,y in tqdm(data_generator(test_samples, num)):
        z = model.predict(x)
        print (z)
        z = np.array([i.argmax(axis=1) for i in z]).T
        #print (z)
        #i.argmax(axis = 1)返回每行中最大数的索引,i.argmax(axis = 0)返回每列中最大数的索引
        #26分类,索引为(0-25)对应(a-z),取出概率最大的索引找出对应的字母即可
        y = np.array(y).T #原先的正确结果
        total += len(x) #样本数量
        right += ((z == y).sum(axis=1) == 4).sum()#四个都对就默认对了一个
        if step < 100:
            step += 1
        else:
            break
    result = u'模型全对率:%s'%(right/total)
    return result

test_samples1 = glob.glob(r'/home/wongyao/验证码/test/test_sample/*.jpg')
test_list = [i for i in range(len(test_samples1))]
def data_generator_test1(data, n): #样本生成器,节省内存
    while True:
        batch = np.array([data[n]])
        x,y = [],[]
        for img in batch:
            x.append(misc.imresize(misc.imread(img), img_size)) #读取resize图片,再存进x列表
            y.append([ord(i)-ord('a') for i in img[-8:-4]]) #把验证码标签添加到y列表,ord(i)-ord('a')把对应字母转化为数字a=0,b=1……z=26
        x = preprocess_input(np.array(x).astype(float)) #原先是dtype=uint8转成一个纯数字的array
        y = np.array(y)
        yield x,[y[:,i] for i in range(4)]        

#单张图片测试    
def predict2(n):
    x,y = next(data_generator_test1(test_samples1, n))
    z = model.predict(x)
    z = np.array([i.argmax(axis=1) for i in z]).T
    result = z.tolist() 
    v = []
    for i in range(len(result)):
        for j in result[i]:
            v.append(letter_list[j])
    image = mpimg.imread(test_samples1[n])
    plt.axis('off')
    plt.imshow(image)
    plt.show()
    #输出测试结果
    str = ''
    for i in v:
        str += i
    return (str)

单张测试结果如下:







四个字母全部识别对即成功识别了一张验证码,批量测试结果识别率在70%以上,选出10张验证码做单张测试识别对了5张,如果去除三张人眼也很难辨别的图片,那么8张图仅仅识别错了两张。计算机识别的速度要远远超过人眼,识别率超过10%就可以满足需求,已经可以认为模型具有破解验证码的能力。

如果有更多训练集、更精细的调参再加上比对多种不同的CNN模型择优和一定会有更好的效果,另外传统验证码识别中,一般需要先对验证码二值化(因为识别字符不需要颜色这个特征),再把字母单独分割出来再识别,CNN中如果这样预处理也很可能会提高模型的效果,具体优化的想法只能有时间以后再继续完成了。

接下来我们把训练好的模型封装成支持graphql形式的API,以供调用。
这里选取 opencv来读取和转换图片,速度会快一点点。

# -*- coding: utf-8 -*-
"""
Created on Mon May  7 15:11:35 2018

@author: wongyao
"""

import numpy as np
from keras.applications.xception import preprocess_input
#from skimage import transform
import cv2 
from scipy import misc
import time
from keras.models import load_model
import requests
import imageio
import os
dir = os.path.dirname(__file__)
h5_file = os.path.join(dir, 'CaptchaForWechat.h5')
model = load_model(h5_file) 

url="https://mp.weixin.qq.com/cgi-bin/verifycode?username=info@ersinfotech.com&r=521346561100"
img_size = (50,120)
letter_list = [chr(i) for i in range(97,123)]


def captcha_predict(url):
    """
    :params:url
    :return:captcha(string)
    """
    x = []
    t1 = time.time()
    try:
        img = requests.get(url).content
        x.append(misc.imresize(misc.imread(img), img_size))
        x = preprocess_input(np.array(x).astype(float))
        z = model.predict(x)
        z = np.array([i.argmax(axis=1) for i in z]).T
        result = z.tolist()
        v = ''.join([letter_list[i] for i in result[0]])
    except Exception as e:
        v = "error"
        print(e)
    t2 = time.time()
    print("總耗時: {:.2f} s".format(t2-t1))
    data = {
        "captcha":v,
        "runtime": t2-t1
    }
    return data

References

keras 2.0中文文档
https://www.jiqizhixin.com/articles/2017-12-14-2
https://spaces.ac.cn/archives/4503
https://zhuanlan.zhihu.com/p/26078299

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容