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

Posted 一只老番茄

tags:

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

1.背景

由于一些需求,根据查阅的资料做了这方面的一点工作和研究,也是对在其他人的基础上做了点适合自己需求的改进。

如果有人看到有什么错误的地方请帮忙指正下,谢谢大佬们~

2.基本工具

开发工具:pycharm
框架和包:python3.6,tensorflow,PIL,numpy,flask等

3.基本流程

整个基本流程就是:

  • 1 . 数据清洗获得质量比较高的训练数据

  • 2 . 构建CNN进行特征提取生成模型

  • 3 . 模型测试

  • 4 . 构建http服务

4.数据集

数据集可以去自己生成,有对应的python包可以很快生成验证码,也可以用其他方式去生成。我这里是根据需求爬的一些验证码,筛选大多数做训练集,小部分做测试集。

验证码图片如下:

数据集中验证码对应的真实数字,作为图片的名字来存放:

# 获取验证码图片和名字
def get_name_and_image(dir):  
   all_image=os.listdir(dir)  
   random_file=random.randint(0,3000)  
   base=os.path.basename(dir+all_image[random_file])  
   name=os.path.splitext(base)[0]  
   image=Image.open(dir+all_image[random_file])  
   image=np.array(image)    return name, image

5.数据清洗

在这里数据清洗简单分为两步来做

(1)降维

降维即将数据维度从高维变成低维,这样方便之后的训练。对于验证码识别工作来说,验证码图片的颜色并没有任何意义,只会增加计算复杂度。因此,这里我们首先对图片进行二置化。

# 把彩色图像转为灰度图像(色彩对识别验证码没有什么用)
def convert2gray(img):    if len(img.shape) > 2:        gray = np.mean(img, -1)        
       # 上面的转法较快,正规转法如下        # r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]        # gray = 0.2989 * r + 0.5870 * g + 0.1140 * b        return gray    
   else:        
       return img

当然还要其他很多方法

(2)去噪

去噪即去除图片中干扰目标的那些数据。对于验证码来说,其中的干扰线,边框之类的就属于干扰因子,因此需要想办法去干扰线,去边框。

# input: gray iamgedef depoint(img):
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(2)
    img = img.convert('1')

    pixdata = img.load()
    w,h = img.size    
   # 图片周边2像素为边框,直接用255替代(灰度图像中像素值范围是0~255,其中255是白色,0是黑色)    for y in range(0, h):        
       for x in range(0, w):            
           if y<2 or y>h-2:                img.putpixel((x, y), 255)            
           if x<2 or x>w-2:                img.putpixel((x, y), 255)    
           # 对于图片中任意不为白色像素点周围像素为白色个数大于2个的,认为该点为干扰点    for y in range(1,h-1):        
       for x in range(1,w-1):            
           count = 0            if pixdata[x,y-1] > 245:                
               count = count + 1            if pixdata[x,y+1] > 245:                
               count = count + 1            if pixdata[x-1,y] > 245:                
               count = count + 1            if pixdata[x+1,y] > 245:                
               count = count + 1            if count > 2:                pixdata[x,y] = 255    
   return
img

当然这些去噪的方法都是根据训练数据来制定的,不同情况方法不会完全一样

根据验证码中各个字符的关联程度可以选择切分还是不切分,如果字符连接程度比较高的话,就将验证码图片作为一个整体去训练,如果没有连接,那么可以将验证码切分成一个个字符去训练,这样的话训练的速度和效果理论上应该更好一点。

(3)其他

cnn在图像大小是2的倍数时性能最高, 如果你用的图像大小不是2的倍数,可以在图像边缘补无用像素。

 # 图像上补2行,下补3行,左补2行,右补2
np.pad(image,((2,3),(2,2)), 'constant', constant_values=(255,))  

清洗过的图片如下:

6.构建CNN训练模型

完成数据清洗的工作之后,需要开始构建CNN来训练模型,这也分为几步:

(1)将验证码的名字与向量进行互转

这样做的目的同样是为了方便矩阵计算

# 验证码中的字符,这里只有大写和数字number = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
ALPHABET = ['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']
           
MAX_CAPTCHA=5 # 验证码字符个数

har_set = number + ALPHABET + ['_']  # 如果验证码长度小于4, '_'用来补齐

CHAR_SET_LEN = len(char_set)# 文本转向量,通过字符的ASCII值进行计算,向量(大小MAX_CAPTCHA*CHAR_SET_LEN)用0,1编码 63个编码一个字符,这样数字有,字母也有

def text2vec(text):    vector = np.zeros(MAX_CAPTCHA * CHAR_SET_LEN)  
   def char2pos(c):       if c == '_':           k = 62           return k       k = ord(c) - 48       if k > 9:           k = ord(c) - 55           if k > 35:               k = ord(c) - 61               if k > 61:                  
                  raise ValueError('No Map')      
       return k  
     
     for i, c in enumerate(text):           idx = i * CHAR_SET_LEN + char2pos(c)           vector[idx] = 1   return vector   # 向量转回文本
def vec2text(vec):   char_pos = vec.nonzero()[0]   text = []  
  for i, c in enumerate(char_pos):       char_at_pos = i  # c/63       char_idx = c % CHAR_SET_LEN      
      if char_idx < 10:           char_code = char_idx + ord('0')      
      elif char_idx < 36:           char_code = char_idx - 10 + ord('A')      
      elif char_idx < 62:           char_code = char_idx - 36 + ord('a')      
      elif char_idx == 62:           char_code = ord('_')      
      else:          
          raise ValueError('error')       text.append(chr(char_code))  
          return "".join(text)

(2)生成训练batch

卷积神经网络中一般有卷积层,全连接层和池化层。这里就是为了生成CNN络中的卷积层,我觉得也就是为了将一些相关的特征聚合起来,这些可以参考CNN的一些相关资料。

# 图像大小,这里你要事先了解
IMAGE_HEIGHT = 25
IMAGE_WIDTH = 90
# 生成一个训练batch,默认一次采集128张验证码作为一次训练
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])   for i in range(batch_size):      
       text, image = gen_captcha_text_and_image() # 获取训练数据        batch_x[i, :] = image.flatten()      
       batch_y[i, :] = text2vec(text)   return batch_x, batch_y

(3)定义CNN结构

定义CNN结构中,采用3个卷积层加1个全连接层的结构,在每个卷积层中都选用2* 2的最大池化层和dropout层,卷积核尺寸选择3* 3。需要注意的是在全连接层中,我们的图片25* 90已经经过了3层池化层,也就是长宽都压缩了8倍,得到4* 12大小。

# 定义CNN
def crack_captcha_cnn(w_alpha=0.01, b_alpha=0.1):   
  x = tf.reshape(X, shape=[-1, IMAGE_HEIGHT, IMAGE_WIDTH, 1])   # 3 conv layer   w_c1 = tf.Variable(w_alpha * tf.random_normal([3, 3, 1, 32])) # 3*3的卷积核尺寸   b_c1 = tf.Variable(b_alpha * tf.random_normal([32]))   conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x, w_c1, strides=[1, 1, 1, 1], padding='SAME'), b_c1))   conv1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')  # 2*2池化层   conv1 = tf.nn.dropout(conv1, keep_prob)   w_c2 = tf.Variable(w_alpha * tf.random_normal([3, 3, 32, 64]))   b_c2 = tf.Variable(b_alpha * tf.random_normal([64]))   conv2 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv1, w_c2, strides=[1, 1, 1, 1], padding='SAME'), b_c2))   conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')   conv2 = tf.nn.dropout(conv2, keep_prob)   w_c3 = tf.Variable(w_alpha * tf.random_normal([3, 3, 64, 64]))   b_c3 = tf.Variable(b_alpha * tf.random_normal([64]))   conv3 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv2, w_c3, strides=[1, 1, 1, 1], padding='SAME'), b_c3))   conv3 = tf.nn.max_pool(conv3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')   conv3 = tf.nn.dropout(conv3, keep_prob)   # Fully connected layer   w_d = tf.Variable(w_alpha * tf.random_normal([4 * 12 * 64, 1024])) # 4*12d的图片尺寸   b_d = tf.Variable(b_alpha * tf.random_normal([1024]))   dense = tf.reshape(conv3, [-1, w_d.get_shape().as_list()[0]])   dense = tf.nn.relu(tf.add(tf.matmul(dense, w_d), b_d))   dense = tf.nn.dropout(dense, keep_prob)   w_out = tf.Variable(w_alpha * tf.random_normal([1024, MAX_CAPTCHA * CHAR_SET_LEN]))   b_out = tf.Variable(b_alpha * tf.random_normal([MAX_CAPTCHA * CHAR_SET_LEN]))   out = tf.add(tf.matmul(dense, w_out), b_out)  
    return out

(4)训练模型

这里损失函数使用的是tf中的sigmoid_cross_entropy_with_logits()方法。
keep_prob = 0.45,这个参数控制着过拟合,当机器学习速度过快的时候,可以减小该值,让机器遗忘的多一点。

# 训练
def train_crack_captcha_cnn():   output = crack_captcha_cnn()   # loss   loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=output, labels=Y))     # optimizer 为了加快训练 learning_rate应该开始大,然后慢慢衰   optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)   predict = tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN])   max_idx_p = tf.argmax(predict, 2)   max_idx_l = tf.argmax(tf.reshape(Y, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)   correct_pred = tf.equal(max_idx_p, max_idx_l)   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(64)           _, loss_ = sess.run([optimizer, loss], feed_dict={X: batch_x, Y: batch_y, keep_prob: 0.45})          
          print(step, loss_)           # 100 step计算一次准确率           if step % 100 == 0:               batch_x_test, batch_y_test = get_next_batch(100)               acc = sess.run(accuracy, feed_dict={X: batch_x_test, Y: batch_y_test, keep_prob: 1.})              
              print(step, acc)               # 如果准确率大于98%,保存模型,完成训练               if acc > 0.98:                   saver.save(sess, dir+"crack_capcha.model", global_step=step)                   break          
           step += 1

使用CNN训练模型最好使用GPU机器来跑快一点,我用GPU机器跑几千个训练集,大概也就10来分钟

生成的模型如下:

7.模型测试

使用CNN生成的模型跑测试集看模型的效果

def crack_captcha_while():   
  output = crack_captcha_cnn()   saver = tf.train.Saver()   with tf.Session() as sess:       saver.restore(sess, tf.train.latest_checkpoint(dir)) #加载模型       num = 1       correctnum = 0       while(num<518):           textCorrect, imageCorrect = get_name_and_image_test() #加载测试集           captcha_image = imageCorrect.flatten()           predict = tf.argmax(tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)           text_list = sess.run(predict, feed_dict={X: [captcha_image], keep_prob: 1})           text = text_list[0].tolist()          
          vector = np.zeros(MAX_CAPTCHA * CHAR_SET_LEN)           i = 0           for n in text:              
              vector[i * CHAR_SET_LEN + n] = 1               i += 1           if textCorrect==vec2text(vector):               correctnum+=1           else:              
              print("correct: {}  predit: {}".format(textCorrect, vec2text(vector)))           num+=1       print("correctnum: {} num: {}".format(correctnum, num))

8.部署使用

做这个是为了要用的,对于其他语言或者系统来调用这个python脚本比较低效,因此构建一个简单的http服务是相对比较快捷高效的方式。这里使用flask框架构建了一个简单的http服务以便使用。

# CNN的定义和模型的加载作为全局变量,以保证除第一次进行这些操作外,之后都只是使用模型,因为tf构建CNN以及模型加载相对很耗时output = crack_captcha_cnn()
saver = tf.train.Saver()
sess = tf.Session()
saver.restore(sess, tf.train.latest_checkpoint(dir))
predict = tf.argmax(tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)

def predict_valid_code(image):
   captcha_image = image.flatten()

   text_list = sess.run(predict, feed_dict={X: [captcha_image], keep_prob: 1})

   text = text_list[0].tolist()
   vector = np.zeros(MAX_CAPTCHA * CHAR_SET_LEN)
   i = 0
   for n in text:
       vector[i * CHAR_SET_LEN + n] = 1
       i += 1

   return vec2text(vector)
   
@app.route('/predictValidCode/', methods=['GET','POST'])
def predictValidCode():
   start = time.time()
   # if not request.json or 'sessionId' not in request.json:
   #     abort(400)

   image = request.json['image']

   predict_result = predict_valid_code(image)   
  end = time.time()  
  print('predict_result:',predict_result,';the time of all process:',end-start)  
  return jsonify({'result': predict_result})
 
if __name__ == '__main__':   # host设置为0.0.0.0,则外网用户也可以访问到这个服务   app.run(host="0.0.0.0", port=8383, debug=False, threaded=False)

9.总结

其实大部分代码都是从网上找的,这里只是根据需要和特定需求进行相应的拼凑改进。另外感觉即使是应用机器学习的算法也是需要对算法有比较深的理解的,不然怎么调参都不知道。
另外在服务器上离线搭tf环境也是蛮痛苦的zzzz~

10.参考

  • 使用Python+Tensorflow的CNN技术快速识别验证码


以上是关于CNN进行简单验证码识别小记的主要内容,如果未能解决你的问题,请参考以下文章

用“活着的”CNN 进行验证码识别

字符型图片验证码,使用tensorflow实现卷积神经网络,进行验证码识别CNN

验证码是怎么被机器识别的?Keras+CNN模型验证码识别详解

验证码识别ctc_loss

CNN验证码识别项目

基于keras的cnn定长验证码识别