1. 程式人生 > 實用技巧 >CNN實現驗證碼識別

CNN實現驗證碼識別

前面學過數字識別(單任務),這裡基於CNN來實現驗證碼(多工的識別)

全部程式碼見:github(Tensorflow/10多工學習)

一:驗證碼圖片生成

from captcha.image import ImageCaptcha  #生產驗證碼圖片
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import random
import sys

(一)定義全域性資訊

numbers = ['0','1','2','3','4','5','6','7','8','9'] # a ..  z  A  .. Z
max_captcha 
= 4 char_set_len = 10 image_height = 60 image_width = 160

(二)獲取驗證碼數字序列

def random_captcha_text(charSet=numbers,charLen=4):
    """
    charSet:候選字串
    charLen:驗證碼長度
    """
    cs = ""
    for i in range(charLen):
        cs+=random.choice(charSet)
    return cs

(三)生成驗證碼

def gen_captcha_image():
    image 
= ImageCaptcha() captcha_text = random_captcha_text() captcha = image.generate(captcha_text) #print(captcha) #<_io.BytesIO object at 0x7f87d2a343b8> #image.write(captcha_text,"./1.png") captcha_image = Image.open(captcha) #獲取圖片資料 #print(captcha_image.convert("L")) #轉灰度 captcha_image
= np.array(captcha_image) return captcha_text,captcha_image

(四)轉灰度影象(3維轉1維)

#圖片轉灰度圖
def convert2gray(img):
    if len(img.shape)>2:
        r,b,g=img[:,:,0],img[:,:,1],img[:,:,2]
        gray = 0.2989*r+0.5870*g+0.1140*b
        return gray
    return img

(五)標籤轉one_hot向量格式

#處理標籤,轉40維one_hot
def text2vec(text):
    vec = np.zeros(max_captcha*char_set_len)
    for i,ch in enumerate(text):
        idx = i*char_set_len+int(ch)
        vec[idx] = 1
    return vec

(六)按批次生成資料

#按批次生成資料
def get_next_batch(batch_size=128):
    batch_x = np.zeros((batch_size,image_height*image_width))
    batch_y = np.zeros((batch_size,max_captcha*char_set_len))
    
    def correct_gen_captcha_text_image():
        while True:
            text,image = gen_captcha_image()
            if image.shape == (60,160,3):
                return text,image
    
    for i in range(batch_size):
        text,image = correct_gen_captcha_text_image()
        image = convert2gray(image)
        batch_x[i,:] = image.flatten()/255
        batch_y[i,:] = text2vec(text)
        
    return batch_x,batch_y

二:構建CNN結構

(一)實現公共函式

#初始化權值
def weight_variable(shape):
    init = tf.truncated_normal(shape,stddev=0.1)
    return tf.Variable(init)

#初始化偏執單元對應權值
def bias_variable(shape):
    init = tf.constant(0.1,shape=shape)
    return tf.Variable(init)

#實現卷積層
def conv2d(x,W):
    #input : 輸入的要做卷積的圖片,要求為一個張量,shape為 [ batch, in_height, in_weight, in_channel ],其中batch為圖片的數量,in_height 為圖片高度,in_weight 為圖片寬度,in_channel 為圖片的通道數,灰度圖該值為1,彩色圖為3。(也可以用其它值,但是具體含義不是很理解)
    #filter: 卷積核,要求也是一個張量,shape為 [ filter_height, filter_weight, in_channel, out_channels ],其中 filter_height 為卷積核高度,filter_weight 為卷積核寬度,in_channel 是影象通道數 ,和 input 的 in_channel 要保持一致,out_channel 是卷積核數量。
    #strides: 卷積時在影象每一維的步長,這是一個一維的向量,[ 1, strides, strides, 1],第一位和最後一位固定必須是1
    #padding: string型別,值為“SAME” 和 “VALID”,表示的是卷積的形式,是否考慮邊界。"SAME"是考慮邊界,不足的時候用0去填充周圍,"VALID"則不考慮
    #use_cudnn_on_gpu: bool型別,是否使用cudnn加速,預設為true
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")

#實現池化層
def max_pool_2x2(x):
    #value : 需要池化的輸入,一般池化層接在卷積層後面,所以輸入通常是feature map,依然是[batch_size, height, width, channels]這樣的shape
    #k_size : 池化視窗的大小,取一個四維向量,一般是[1, height, width, 1],因為我們不想在batch和channels上做池化,所以這兩個維度設為了1
    #strides : 視窗在每一個維度上滑動的步長,一般也是[1, stride,stride, 1]
    #padding: 填充的方法,SAME或VALID
    return tf.nn.max_pool(x,[1,2,2,1],strides=[1,2,2,1],padding="SAME")

(二)定義變數佔位符

X = tf.placeholder(tf.float32,[None,image_height*image_width])
Y = tf.placeholder(tf.float32,[None,char_set_len*max_captcha])
keep_drop = tf.placeholder(tf.float32)

(三)定義CNN結構,訓練結構---重點

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

#初始化的一個卷基層的權值和偏執
W_conv1 = weight_variable([3,3,1,32]) #3*3的取樣視窗,1表示輸入通道為1,黑白。32表示輸出的通道數--表示使用了32個卷積核(最終會獲得32個特徵平面)
b_conv1 = bias_variable([32]) #每一個卷積核都需要一個偏執,所以我們這裡要32個偏置值

#獲取第一層卷積之後的啟用值,以及池化層處理以後的隱藏層資訊
h_conv1 = tf.nn.relu(conv2d(x,W_conv1)+b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
h_pool1_drop = tf.nn.dropout(h_pool1,keep_drop)

#----conv (60,160,32)----max_pool (30,80,32)

#第二層卷積池化
W_conv2 = weight_variable([3,3,32,64]) #3*3的取樣視窗,1表示輸入通道為1,黑白。64表示輸出的通道數--表示使用了64個卷積核(最終會獲得64個特徵平面)
b_conv2 = bias_variable([64]) #每一個卷積核都需要一個偏執,所以我們這裡要64個偏置值

#獲取第二層卷積之後的啟用值,以及池化層處理以後的隱藏層資訊
h_conv2 = tf.nn.relu(conv2d(h_pool1_drop,W_conv2)+b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
h_pool2_drop = tf.nn.dropout(h_pool2,keep_drop)

#----conv (30,80,64)----max_pool (15,40,64)

#第三層卷積池化
W_conv3 = weight_variable([3,3,64,128]) #3*3的取樣視窗,1表示輸入通道為1,黑白。128表示輸出的通道數--表示使用了128個卷積核(最終會獲得128個特徵平面)
b_conv3 = bias_variable([128]) #每一個卷積核都需要一個偏執,所以我們這裡要128個偏置值

#獲取第二層卷積之後的啟用值,以及池化層處理以後的隱藏層資訊
h_conv3 = tf.nn.relu(conv2d(h_pool2_drop,W_conv3)+b_conv3)
h_pool3 = max_pool_2x2(h_conv3)
h_pool3_drop = tf.nn.dropout(h_pool3,keep_drop)

#----conv (15,40,128)----max_pool (8,20,128)

#開始全連結層

#先將第3池化層處理後的資料扁平化
h_pool3_flat = tf.reshape(h_pool3_drop,[-1,8*20*128])

#初始化第一個全連結層權值
W_fc1 = weight_variable([8*20*128,1024]) #設定第一個全連結層單元數量為1024
b_fc1 = bias_variable([1024]) #共有1024個目標單元

#求得第一個全連結層的輸出
h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat,W_fc1)+b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1,keep_drop)

#初始化第二個全連結層權值
W_fc2 = weight_variable([1024,max_captcha*char_set_len]) #開始要進行輸出了
b_fc2 = bias_variable([max_captcha*char_set_len])

#prediction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
#prediction = tf.nn.sigmoid(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
prediction = tf.matmul(h_fc1_drop,W_fc2)+b_fc2

#交叉熵處理獲取損失
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=prediction,labels=Y))

#根據損失函式進行優化
train_step = tf.train.AdamOptimizer(1e-3).minimize(loss)

#轉換結果格式,開始對比
pred = tf.reshape(prediction,[-1,max_captcha,char_set_len])
max_pred_idx = tf.argmax(pred,2)
max_lable_idx = tf.arg_max(tf.reshape(Y,[-1,max_captcha,char_set_len]),2)
#返回預測結果到bool列表 correct_pred
= tf.equal(max_pred_idx,max_lable_idx)
#返回準確率tf.cast強制轉換 accuracy
= tf.reduce_mean(tf.cast(correct_pred,tf.float32))

(四)模型訓練、儲存

saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    step = 0
    while True:
        batch_x,batch_y = get_next_batch()
        #hpd = sess.run(h_pool2_drop,feed_dict={X:batch_x,Y:batch_y,keep_drop:0.75})
        #print(hpd)
        _,loss_ = sess.run([train_step,loss],feed_dict={X:batch_x,Y:batch_y,keep_drop:0.75})
        print(step,loss_)
        if step % 10 == 0:
            batch_x,batch_y = get_next_batch()
            acc = sess.run(accuracy,feed_dict={X:batch_x,Y:batch_y,keep_drop:1})
            print(step,acc)
            if acc > 0.99:
                saver.save(sess, './captcha/models/cnn_pic_captcha.model',global_step=step)
                break
        step += 1

注意:第二個7070是準確率,第一個是損失值

補充:

1.在前面幾十次迭代中,誤差下降較快,但是準確率低

2.在進入誤差0.3+範圍時,迭代了2000+,誤差幾乎不變,準確率一直在0.1左右

3.2000次迭代後發生變化,準確率達到0.5

4.3000+迭代後準確率達到0.9

5.4000+迭代以後達到0.99的準確率

6.7000+迭代後

三:模型呼叫

CNN網路結構不變

(一)模型呼叫

saver = tf.train.Saver()

with tf.Session() as sess:
    saver.restore(sess,"./captcha/models/cnn_pic_captcha.model-7070")
    
    text,image = gen_captcha_image()
    plt.figure()
    plt.imshow(image)
    plt.axis("off")
    plt.show()
    
    image = convert2gray(image)
    image = image.flatten()/255
    
    idx = sess.run(max_pred_idx,feed_dict={X:[image],keep_drop:1})
    print(idx)