卷積神經(jīng)網(wǎng)絡(luò)CNN微信驗證碼識別

輿情采集部門需要采集微信評論,需要用到識別驗證碼的技術(shù)。
早先學習了tensorflow和keras,跑了許多深度學習得demo卻沒有在實際場景應(yīng)用過,現(xiàn)在剛好公司有需要,就使用keras來做一個驗證碼識別的API。
識別驗證碼現(xiàn)在網(wǎng)上有大量的案例,因為python可以使用
from captcha.image import ImageCaptcha來生成驗證碼。
生成的驗證碼樣例如下:


驗證碼的噪聲簡單且訓練集可以無限次生成的情況下,識別驗證碼就非常容易,github和博客中都有大量的例子,通常是使用tensorflow,Caffee,keras等等,只要愿意花時間訓練都可以達到90%的識別率。

可是這樣的識別方法,要用到實際場景中就很困難,因為實際環(huán)境中的驗證碼非常難以自己生成。有些公司會去相關(guān)網(wǎng)站上爬取驗證碼,然后自己手工標注,如果找外包付費標注倒是現(xiàn)實一些,自己手工標注數(shù)十萬的驗證碼,通常不太可能。
因為公司任務(wù),所以在網(wǎng)上到處尋找微信驗證碼,碰巧之前在一篇博客提供的網(wǎng)盤鏈接中找到了帶標簽的微信的驗證碼訓練集,鏈接如下:https://pan.baidu.com/s/1yZeWYHcK47lqzXD8OY8hKQ 密碼: hjph

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

微信驗證碼

方法還是使用常規(guī)驗證碼識別方法,使用keras中預(yù)訓練好的模型fine tune就可以,但是數(shù)據(jù)集換成微信場景下的數(shù)據(jù)集。

因為驗證碼只有字母而且不區(qū)分大小寫,那么只需要對4個字母每個字母做26分類就可以了。
keras_weight中Alex Net,google net,VGG16,VGG19,ResNet50,Xception,InceptionV3。都是由ImageNet訓練而來。
模型權(quán)重可以直接去官網(wǎng)下載,如果速度不夠快,這里提供網(wǎng)盤鏈接。
keras 預(yù)訓練好的模型權(quán)重鏈接: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 模型, 權(quán)重由ImageNet訓練而來
在ImageNet上,該模型取得了驗證集top1 0.790和top5 0.945的正確率

注意,該模型目前僅能以TensorFlow為后端使用,由于它依賴于"SeparableConvolution"層,目前該模型只支持channels_last的維度順序(width, height, channels)

默認輸入圖片大小為299x299

參數(shù):
include_top:是否保留頂層的3個全連接網(wǎng)絡(luò)
weights:None代表隨機初始化,即不加載預(yù)訓練權(quán)重。'imagenet'代表加載預(yù)訓練權(quán)重
input_tensor:可填入Keras tensor作為模型的圖像輸出tensor
input_shape:可選,僅當include_top=False有效,應(yīng)為長為3的tuple,指明輸入圖片的shape,圖片的寬高必須大于71,如(150,150,3)
pooling:當include_top=False時,該參數(shù)指定了池化方式。None代表不池化,最后一個卷積層的輸出為4D張量?!產(chǎn)vg’代表全局平均池化,‘max’代表全局最大值池化。
classes:可選,圖片分類的類別數(shù),僅當include_top=True并且不加載預(yù)訓練權(quán)重時可用。

訓練和測試代碼:

十萬樣本,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適合在高寬都是偶數(shù)的情況,否則需要在邊緣補齊,
#那么我們也可以把全體圖片都resize成這個尺寸(高,寬,通道)
input_image = Input(shape=(img_size[0],img_size[1],3))

#具體方案是:直接將驗證碼輸入,做幾個卷積層提取特征,
#然后把這些提出來的特征連接幾個分類器(26分類,因為不區(qū)分大小寫),
#如果需要加入數(shù)字就是36分類,微信驗證碼里沒有數(shù)字。

#輸入圖片
#用預(yù)訓練的Xception提取特征,采用平均池化
base_model = Xception(input_tensor=input_image, weights='imagenet', include_top=False, pooling='avg')

#用全連接層把圖片特征接上softmax然后26分類,dropout為0.5,激活使用softmax因為是多分類問題。
#ReLU - 用于隱層神經(jīng)元輸出
#Sigmoid - 用于隱層神經(jīng)元輸出
#Softmax - 用于多分類神經(jīng)網(wǎng)絡(luò)輸出
#Linear - 用于回歸神經(jīng)網(wǎng)絡(luò)輸出(或二分類問題)
#對四個字母做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:優(yōu)化器的選擇可以參考這篇博客http://m.itdecent.cn/p/d99b83f4c1a6
#loss:損失函數(shù),這里選稀疏多類對數(shù)損失
#metrics:列表,包含評估模型在訓練和測試時的性能的指標,典型用法是metrics=['accuracy']如果要在多輸出模型中為不同的輸出指定不同的指標,可像該參數(shù)傳遞一個字典,例如metrics={'ouput_a': 'accuracy'}
#sample_weight_mode:如果你需要按時間步為樣本賦權(quán)(2D權(quán)矩陣),將該值設(shè)為“temporal”。默認為“None”,代表按樣本賦權(quán)(1D權(quán))。如果模型有多個輸出,可以向該參數(shù)傳入指定sample_weight_mode的字典或列表。在下面fit函數(shù)的解釋中有相關(guān)的參考內(nèi)容。
#weighted_metrics: metrics列表,在訓練和測試過程中,這些metrics將由sample_weight或clss_weight計算并賦權(quán)
#target_tensors: 默認情況下,Keras將為模型的目標創(chuàng)建一個占位符,該占位符在訓練過程中將被目標數(shù)據(jù)代替。如果你想使用自己的目標張量(相應(yīng)的,Keras將不會在訓練時期望為這些目標張量載入外部的numpy數(shù)據(jù)),你可以通過該參數(shù)手動指定。目標張量可以是一個單獨的張量(對應(yīng)于單輸出模型),也可以是一個張量列表,或者一個name->tensor的張量字典。
#model.summary()

from scipy import misc
#misc.imread把圖片轉(zhuǎn)化成矩陣,
#misc.imresize重塑圖片尺寸misc.imresize(misc.imread(img), img_size)  img_size是自己設(shè)定的尺寸
#ord()函數(shù)主要用來返回對應(yīng)字符的ascii碼,
#chr()主要用來表示ascii碼對應(yīng)的字符他的輸入時數(shù)字,可以用十進制,也可以用十六進制。

def data_generator(data, batch_size): #樣本生成器,節(jié)省內(nèi)存
    while True:
        #np.random.choice(x,y)生成一個從x中抽取的隨機數(shù),維度為y的向量,y為抽取次數(shù)
        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(tǒng)列表,ord(i)-ord('a')把對應(yīng)字母轉(zhuǎn)化為數(shù)字a=0,b=1……z=26
        x = preprocess_input(np.array(x).astype(float))
#原先是dtype=uint8轉(zhuǎn)成一個純數(shù)字的array
        y = np.array(y)
        yield x,[y[:,i] for i in range(4)]
#輸出:圖片array和四個轉(zhuǎn)化成數(shù)字的字母 例如:[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) 
#參數(shù):generator生成器函數(shù),
#samples_per_epoch,每個epoch以經(jīng)過模型的樣本數(shù)達到samples_per_epoch時,記一個epoch結(jié)束
#step_per_epoch:整數(shù),當生成器返回step_per_epoch次數(shù)據(jù)是記一個epoch結(jié)束,執(zhí)行下一個epoch
#epochs:整數(shù),數(shù)據(jù)迭代的輪數(shù)
#validation_data三種形式之一,生成器,類(inputs,targets)的元組,或者(inputs,targets,sample_weights)的元祖
#若validation_data為生成器,validation_steps參數(shù)代表驗證集生成器返回次數(shù)
#class_weight:規(guī)定類別權(quán)重的字典,將類別映射為權(quán)重,常用于處理樣本不均衡問題。
#sample_weight:權(quán)值的numpy array,用于在訓練時調(diào)整損失函數(shù)(僅用于訓練)??梢詡鬟f一個1D的與樣本等長的向量用于對樣本進行1對1的加權(quán),或者在面對時序數(shù)據(jù)時,傳遞一個的形式為(samples,sequence_length)的矩陣來為每個時間步上的樣本賦不同的權(quán)。這種情況下請確定在編譯模型時添加了sample_weight_mode='temporal'。
#workers:最大進程數(shù)
#max_q_size:生成器隊列的最大容量
#pickle_safe: 若為真,則使用基于進程的線程。由于該實現(xiàn)依賴多進程,不能傳遞non picklable(無法被pickle序列化)的參數(shù)到生成器中,因為無法輕易將它們傳入子進程中。
#initial_epoch: 從該參數(shù)指定的epoch開始訓練,在繼續(xù)之前的訓練時有用。

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

#評價模型的全對率(批量預(yù)測,num為預(yù)測樣本總量)
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)返回每行中最大數(shù)的索引,i.argmax(axis = 0)返回每列中最大數(shù)的索引
        #26分類,索引為(0-25)對應(yīng)(a-z),取出概率最大的索引找出對應(yīng)的字母即可
        y = np.array(y).T #原先的正確結(jié)果
        total += len(x) #樣本數(shù)量
        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): #樣本生成器,節(jié)省內(nèi)存
    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(tǒng)列表,ord(i)-ord('a')把對應(yīng)字母轉(zhuǎn)化為數(shù)字a=0,b=1……z=26
        x = preprocess_input(np.array(x).astype(float)) #原先是dtype=uint8轉(zhuǎn)成一個純數(shù)字的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()
    #輸出測試結(jié)果
    str = ''
    for i in v:
        str += i
    return (str)

單張測試結(jié)果如下:







四個字母全部識別對即成功識別了一張驗證碼,批量測試結(jié)果識別率在70%以上,選出10張驗證碼做單張測試識別對了5張,如果去除三張人眼也很難辨別的圖片,那么8張圖僅僅識別錯了兩張。計算機識別的速度要遠遠超過人眼,識別率超過10%就可以滿足需求,已經(jīng)可以認為模型具有破解驗證碼的能力。

如果有更多訓練集、更精細的調(diào)參再加上比對多種不同的CNN模型擇優(yōu)和一定會有更好的效果,另外傳統(tǒng)驗證碼識別中,一般需要先對驗證碼二值化(因為識別字符不需要顏色這個特征),再把字母單獨分割出來再識別,CNN中如果這樣預(yù)處理也很可能會提高模型的效果,具體優(yōu)化的想法只能有時間以后再繼續(xù)完成了。

接下來我們把訓練好的模型封裝成支持graphql形式的API,以供調(diào)用。
這里選取 opencv來讀取和轉(zhuǎn)換圖片,速度會快一點點。

# -*- 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 轉(zhuǎn)載鏈接 注:本文轉(zhuǎn)載知乎上的回答 作者:初雪 鏈接:https://www.zhihu.com/question...
    pengshuangta閱讀 29,343評論 9 295
  • 公司有一個業(yè)務(wù)需要抓取某網(wǎng)站數(shù)據(jù),登錄需要識別驗證碼,類似下面這種,這應(yīng)該是很多網(wǎng)站使用的驗證碼類型。 首先由于驗...
    洋子該昵稱已被占用閱讀 6,566評論 0 1
  • 年紀大了也許偶爾會做夢。小漁早上打算睡覺的時候,突然就被夢嚇醒了。好累!夢里的小漁一直告訴自己,自己是在做夢,自己...
    張漁閱讀 255評論 0 0
  • 從第一周的《破除習慣養(yǎng)成誤區(qū)》課到今天剛好是六天,明天是第二周的習慣養(yǎng)成訓練營的課。因為自己很多事耽擱拖延到了現(xiàn)在...
    7515b237f6ce閱讀 463評論 3 1

友情鏈接更多精彩內(nèi)容