验证码识别基本流程

Posted 程序员养成记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了验证码识别基本流程相关的知识,希望对你有一定的参考价值。

图像数据处理

验证码数据处理流程:

图像转numpy数组存储 --> 将图像灰度处理 --> 归一化数据存储 --> 适配keras图像格式

numpy存储rgb图

1import numpy as np
2from PIL import Image
3
4# 打开一个图像文件并识别出来变成字符流
5image = Image.open('*.jpg')
6
7# 转换成numpy数组(<type 'numpy.ndarray'>),分别为(weight, hight, 通道)
8# 要识别的验证码的shape为(60, 200, 3)
9np.array(image)

验证码数据可视化一下:

验证码识别基本流程

rgb图灰度处理

其实在上一节有提numpy数组中存储的rgb图像shape为(60,200,3),3是三通道

灰度处理也就是三通道成单通道也就是1

常用转灰度的公式为:

1Y' = 0.299 R + 0.587 G + 0.114 B

适合numpy转灰度这样写:

1np.dot(img[...,:3][0.299, 0.587, 0.114])

此时图像的shape为(60, 200), 完整的应该是(60, 200, 1),此处1被省略掉了

灰度图片可视化一下:

规范化数据

在numpy数组中,一张图片的存储是这样的

1[[235.852 235.852 235.852 ... 235.852 235.852 235.852]
2 [235.852 235.852 235.852 ... 235.852 235.852 235.852]
3 [235.852 235.852 235.852 ... 235.852 235.852 235.852]
4 ...
5 [235.852 235.852 235.852 ... 235.852 235.852 235.852]
6 [235.852 235.852 235.852 ... 235.852 235.852 235.852]
7 [235.852 235.852 235.852 ... 235.852 235.852 235.852]]

为了加快了梯度下降求最优解的速度、同时也为了提高准确度。此处需要对数据进行归一化处理

如果不进行归一化,那么由于特征向量中不同特征的取值相差较大,会导致目标函数变“扁”。这样在进行梯度下降的时候,梯度的方向就会偏离最小值的方向,走很多弯路,即训练时间过长。如果进行归一化以后,目标函数会呈现比较“圆”,这样训练速度大大加快,少走很多弯路。

此处的数据归一化方式很简单 ,除255即可

归一化后的数据:

1[[0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]
2 [0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]
3 [0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]
4 ...
5 [0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]
6 [0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]
7 [0.9249098 0.9249098 0.9249098 ... 0.9249098 0.9249098 0.9249098]]

图像数据格式

前两节提过图像的shape,也就是(60,200,3)其中60是图像高,200是图像宽,3则是指RGB3通道,如果是灰度图则会是1。

但是keras、caffe、tf等神经网络库接收图像的格式可能是个不同的,有可能是(60,200,1)这种通道在后的格式,就是"channels_last"格式,也有可能是(1,60,200)这种通道在前的格式,就是"channels_last"格式

此处使用keras,所以要适配一下keras的数据格式

1def fit_keras_channels(batch, rows=CAPTCHA_HEIGHT, cols=CAPTCHA_WIGTH):
2    if K.image_data_format() == 'channels_first':
3        batch = batch.reshape(batch.shape[0], 1, rows, cols)
4        input_shape = (1, rows, cols)
5    else:
6        batch = batch.reshape(batch.shape[0], rows, cols, 1)
7        input_shape = (rows, cols, 1)
8
9    return batch, input_shape

验证码数据处理

One-hot编码:验证码转向量

one hot编码是将类别变量转换为机器学习算法易于利用的一种形式的过程

验证码所有字符组合,共62种,如下:

1NUMBER = ['0''1''2''3''4''5''6''7''8''9']
2LOWERCASE = ['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']
3UPPERCASE = ['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']
4
5CAPTCHA_CHARSET = NUMBER+LOWERCASE+UPPERCASE
6
7CAPTCHA_LEN = 6

要识别的验证码是6位的验证码:如验证码"z67j66" 按one-hot编码则会变成如下的格式:

 1array([0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
2       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
3       0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
4       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
5       1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
6       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
7       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
8       0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.,
9       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
10       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
11       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
12       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
13       0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
14       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
15       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.,
16       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
17       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
18       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
19       0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.,
20       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
21       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.,
22       0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.])

解释:最终每个验证码都会转成62*6也就是372位的数组,每62位为一个单位也就代表一个字符,'z'字符在32位中排第36位,所以第36位为1,其余5个字符同理类推。

用one hot编码器对类别进行“二进制化”操作,然后将其作为模型训练的特征

one-hot将验证码编码的代码如下:

 1def text2vec(text, length=CAPTCHA_LEN, charset=CAPTCHA_CHARSET):
2    text_len = len(text)
3
4    if text_len != length:
5        raise ValseError('Error:length of captcha should be {}, but got {}'.format(length, text_len))
6
7    vec = np.zeros(length * len(charset))
8    for i in range(length):
9        vec[charset.index(text[i]) + i*len(charset)] = 1
10    return vec

One-hot解码:向量转验证码

验证码"z67j66"模型预测输出的值是如下的格式:

 1[[2.44564273e-16 2.31532883e-16 7.05901482e-16 9.69627720e-08
2  1.90615249e-11 1.13110076e-13 9.86182663e-12 5.86264605e-07
3  8.27818425e-10 1.12511597e-11 2.95084703e-12 1.06110916e-08
4  8.10248483e-12 6.00182334e-11 8.03801697e-07 9.24991163e-18
5  2.73106763e-11 5.08062288e-14 3.57526297e-15 9.37030740e-14
6  1.66095523e-12 2.50196330e-09 3.78523330e-13 2.04370034e-15
7  1.15636201e-12 1.87122914e-10 7.01577400e-14 2.13903960e-12
8  1.66599522e-14 1.69228503e-11 4.07353188e-12 6.42220245e-14
9  1.22493107e-12 7.45806802e-12 3.04900332e-12 9.99998450e-01
10  9.95180201e-17 2.65097543e-16 3.03590531e-16 2.40453225e-16
11  4.13751481e-16 4.07195673e-15 1.03881889e-15 2.98967055e-16
12  1.55898717e-16 2.84909829e-16 1.63157312e-18 3.49257360e-16
13  5.56924904e-16 3.13443907e-16 2.06972342e-16 1.24157759e-16
14  1.35119829e-18 8.28667420e-16 1.06030175e-16 1.99753112e-15
15  2.48584318e-16 2.49156097e-14 9.08180372e-18 2.66752959e-17
16  4.80507868e-16 3.56970071e-16  ...
17  ...
18  ...
19  ...                            2.46898023e-11 2.24925009e-11
20  1.67140520e-11 1.90873006e-09 1.23573176e-04 4.87467977e-09
21  9.99285281e-01 1.90138834e-07 2.15840439e-04 8.90224692e-05
22  1.66158983e-07 1.60308190e-11 5.79694206e-05 1.10788515e-05
23  1.01717042e-05 5.67441873e-07 1.77367729e-05 6.35295976e-08
24  2.53118598e-12 1.51632734e-10 3.31958915e-07 8.18048581e-15
25  2.34718798e-12 2.08676374e-05 1.44573960e-06 1.24188591e-04
26  6.26552865e-10 5.33972343e-06 2.58398882e-06 8.66098686e-12
27  3.31359552e-05 2.33374351e-07 9.15339679e-11 8.83077877e-09
28  3.38219036e-11 4.80769868e-09 1.51213643e-14 4.39860474e-11
29  3.56461527e-10 7.17846449e-10 4.51461432e-13 1.09047892e-11
30  1.03767982e-12 5.20220238e-12 3.60051871e-07 9.94699589e-14
31  4.04294341e-11 2.17001677e-11 6.38204333e-13 2.79468636e-11
32  7.74395061e-14 2.28902366e-11 6.51043552e-10 9.35049038e-10
33  1.57869984e-09 6.15493700e-09 2.10308385e-11 2.22924058e-11
34  7.48997919e-10 6.44263948e-11 1.20270960e-10 4.88545142e-12]]

本来是62*6,也就是372位。因为太长所以只展示了前62位和后62位。前62位中发现第36位的值最大,而在验证码字符数组中第36位正好是"z"。后62位中最大的值是第7位,而在验证码字符数组中第7位正好是"6"。验证码中间四个字符也是同理得

将向量转验证码的代码:

1def vec2text(vector):
2    if not isinstance(vector, np.ndarray):
3        vector = np.asarray(vector)
4    vector = np.reshape(vector, [CAPTCHA_LEN, -1])
5    text = ''
6    for item in vector:
7        text += CAPTCHA_CHARSET[np.argmax(item)]
8
9    return text

模型设计

特征提取与分类

特征提取:

卷积神经网络三剑客conv、relu 、pooling

  • conv卷积对原始图像进行特征提取

  • relu激活 用来加入非线性因素

  • pooling池化 把一个模块里最大的激活值提取出来作为这个模块的激活值 减少计算量

然后进行dropout操作防止过拟合

分类:

六个全连接层对应六个分类器,分别都接收特征提取出来的大的向量,输出长为62的向量。作用就是对六个字符进行分类。

优化器使用自适应的adam,损失函数使用binary_crossentropy

模型结构代码部分:

 1inputs = Input(shape = input_shape, name = "inputs")
2
3conv1 = Conv2D(32, (33), name = "conv1")(inputs)
4relu1 = Activation('relu', name="relu1")(conv1)
5
6conv2 = Conv2D(32, (33), name = "conv2")(relu1)
7relu2 = Activation('relu', name="relu2")(conv2)
8pool2 = MaxPooling2D(pool_size=(2,2), padding='same', name="pool2")(relu2)
9
10conv3 = Conv2D(64, (33), name = "conv3")(pool2)
11relu3 = Activation('relu', name="relu3")(conv3)
12pool3 = MaxPooling2D(pool_size=(2,2), padding='same', name="pool3")(relu3)
13
14x = Flatten()(pool3)
15
16x = Dropout(0.25)(x)
17
18x = [Dense(62, activation='softmax', name='fc%d'%(i+1))(x) for i in range(6)]
19
20outs = Concatenate()(x)
21
22model = Model(inputs=inputs, outputs=outs)
23
24model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

模型网络结构

模型概要:

 1__________________________________________________________________________________________________
2Layer (type)                    Output Shape         Param #     Connected to                     
3==================================================================================================

4inputs (InputLayer)             (None, 60, 200, 1)   0                                            
5__________________________________________________________________________________________________
6conv1 (Conv2D)                  (None, 58, 198, 32)  320         inputs[0][0]                     
7__________________________________________________________________________________________________
8relu1 (Activation)              (None, 58, 198, 32)  0           conv1[0][0]                      
9__________________________________________________________________________________________________
10conv2 (Conv2D)                  (None, 56, 196, 32)  9248        relu1[0][0]                      
11__________________________________________________________________________________________________
12relu2 (Activation)              (None, 56, 196, 32)  0           conv2[0][0]                      
13__________________________________________________________________________________________________
14pool2 (MaxPooling2D)            (None, 28, 98, 32)   0           relu2[0][0]                      
15__________________________________________________________________________________________________
16conv3 (Conv2D)                  (None, 26, 96, 64)   18496       pool2[0][0]                      
17__________________________________________________________________________________________________
18relu3 (Activation)              (None, 26, 96, 64)   0           conv3[0][0]                      
19__________________________________________________________________________________________________
20pool3 (MaxPooling2D)            (None, 13, 48, 64)   0           relu3[0][0]                      
21__________________________________________________________________________________________________
22flatten_1 (Flatten)             (None, 39936)        0           pool3[0][0]                      
23__________________________________________________________________________________________________
24dropout_1 (Dropout)             (None, 39936)        0           flatten_1[0][0]                  
25__________________________________________________________________________________________________
26fc1 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
27__________________________________________________________________________________________________
28fc2 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
29__________________________________________________________________________________________________
30fc3 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
31__________________________________________________________________________________________________
32fc4 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
33__________________________________________________________________________________________________
34fc5 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
35__________________________________________________________________________________________________
36fc6 (Dense)                     (None, 62)           2476094     dropout_1[0][0]                  
37__________________________________________________________________________________________________
38concatenate_1 (Concatenate)     (None, 372)          0           fc1[0][0]                        
39                                                                 fc2[0][0]                        
40                                                                 fc3[0][0]                        
41                                                                 fc4[0][0]                        
42                                                                 fc5[0][0]                        
43                                                                 fc6[0][0]                        
44==================================================================================================

45Total params: 14,884,628
46Trainable params: 14,884,628
47Non-trainable params: 0
48__________________________

网络结构:


模型训练

1history = model.fit(X_train, 
2                    Y_train, 
3                    batch_size=100, 
4                    epochs=10, verbose=2,  
5                    validation_data=(X_test, Y_test))
 1Train on 5000 samples, validate on 1000 samples
2Epoch 1/10
3 - 95s - loss: 0.0678 - acc: 0.9839 - val_loss: 0.0544 - val_acc: 0.9842
4Epoch 2/10
5 - 90s - loss: 0.0321 - acc: 0.9907 - val_loss: 0.0270 - val_acc: 0.9930
6Epoch 3/10
7 - 92s - loss: 0.0141 - acc: 0.9963 - val_loss: 0.0225 - val_acc: 0.9944
8Epoch 4/10
9 - 91s - loss: 0.0069 - acc: 0.9982 - val_loss: 0.0234 - val_acc: 0.9941
10Epoch 5/10
11 - 92s - loss: 0.0032 - acc: 0.9991 - val_loss: 0.0251 - val_acc: 0.9942
12Epoch 6/10
13 - 92s - loss: 0.0015 - acc: 0.9995 - val_loss: 0.0266 - val_acc: 0.9940
14Epoch 7/10
15 - 92s - loss: 8.9256e-04 - acc: 0.9997 - val_loss: 0.0273 - val_acc: 0.9942
16Epoch 8/10
17 - 91s - loss: 7.4161e-04 - acc: 0.9998 - val_loss: 0.0297 - val_acc: 0.9940
18Epoch 9/10
19 - 91s - loss: 5.5599e-04 - acc: 0.9998 - val_loss: 0.0286 - val_acc: 0.9941
20Epoch 10/10
21 - 91s - loss: 4.5862e-04 - acc: 0.9999 - val_loss: 0.0297 - val_acc: 0.9942


以上是关于验证码识别基本流程的主要内容,如果未能解决你的问题,请参考以下文章

CNN进行简单验证码识别小记

验证码识别之模板匹配方法

字符型图片验证码识别完整过程及Python实现

Python爬虫之网站验证码识别

爬虫遇到头疼的验证码?Python实战讲解弹窗处理和验证码识别

验证码识别之模板匹配方法