TensorFlow – 验证码识别(使用CNN)

效果预览

enter image description here

enter image description here

综合准确率在90%以上。

环境和数据准备

我们先保存几个教务处的验证码,通过观察这些验证码,我们发现了以下特点:

  1. 背景有一些彩色的椒盐噪声(可用滤波或点降噪去除)
  2. 验证码主体只有一种颜色 RGB:(0,0,153)(无法通过颜色的区别来分割字符)
  3. 进行了随机角度的旋转(可以想到切割后进行倾斜度矫正)
  4. 字符之间存在粘连,不易切割(主要是w、m等)

enter image description here

因此很容易可以想到以下两种识别方式:

  • 对验证码切割后使用SoftMax回归训练识别。(因为验证码存在粘连,准确率可能不是太高)
  • 直接对整张图片使用CNN(卷积神经网络)识别。(因为有4位字符,可能需要大量带标记样本才能达到不错的效果)

在本文中我们采用第二种方式,在不进行字符切割的情况下直接对验证码进行端到端的识别。

带标记数据获取

3000张带标签的验证码

数据预处理

格式转换

直接获取到的验证码是gif编码的,由于cv2不能处理gif,附一个转换成png格式的小脚本(当然也可以选择用其他的图形库替代cv2)。

import os
import Image

samples = os.listdir('train')
for i in range(0, len(samples)):
    path = os.path.join('train', samples[i])
    savepath = path[:-3] + 'png'

    im = Image.open(path)
    def iter_frames(im):
        try:
            i = 0
            while 1:
                im.seek(i)
                imframe = im.copy()
                if i == 0:
                    palette = imframe.getpalette()
                else:
                    imframe.putpalette(palette)
                yield imframe
                i += 1
        except EOFError:
            pass
    for i, frame in enumerate(iter_frames(im)):
        frame.save(savepath, **frame.info)
去除边框、降噪、二值化

其实对于CNN这种级别的武器来说,不经过降噪直接识别也有不错的准确率,但是为了排除验证码带来的干扰,仍然进行降噪处理。

在输入的图像中虽然有较多的噪声点,但是经观察发现,验证码主体转成灰度图后只有3个灰度(17、62和68),因此在灰度图上先去除除了这三个灰度的所有点,再进行滤波。

读取灰度图

enter image description here enter image description here enter image description here enter image description here

只保留3个灰度(代码)
# 去掉边框后为 70*25
crop = img[1:26, 1:71]
for h in range(25):
    for w in range(70):
        if crop[h,w]!=17 and crop[h,w]!=62 and crop[h,w]!=68:
            crop[h, w] =255
只保留3个灰度(效果)

enter image description here enter image description here enter image description here enter image description here

去除噪点(代码)
# 去除噪点
for h in range(1, 25 - 1):
    for w in range(1, 70 - 1):
        count = 0
        if crop[h, w - 1] > 245:
            count = count + 1
        if crop[h, w + 1] > 245:
            count = count + 1
        if crop[h - 1, w] > 245:
            count = count + 1
        if crop[h + 1, w] > 245:
            count = count + 1
        if count > 2:
            crop[h, w] = 255
去除噪点(效果)

enter image description here enter image description here enter image description here enter image description here

输入和输出转化

由于在TensorFlow中,只支持向量的输入输出。所以我们将输入转成70*25维的向量,每个值代表一个像素(仅取0和1)。将输出转成36*4维的向量。其中4个字符,每个字符取值为数字0-9或小写字母a-z,分别对应0-35。例如:‘123p’对应的向量为:

[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

训练

由于我们只有3000个样本,但是对于卷积神经网络来说,至少2-3k次的训练才开始有效果。我们取64个样本为一个batch,样本量远远满足不了训练的需求。因此我们采用滑动窗口的方式,第1次训练样本1-64,第2次训练样本2-65。。以此类推,当所有的样本都被训练完后,再回到第一个样本开始重复训练。

# 定义CNN
def crack_captcha_cnn(w_alpha=0.01, b_alpha=0.1):
    x_image = tf.reshape(x, shape=[-1, IMAGE_HEIGHT, IMAGE_WIDTH, 1])

    # 3 个卷积层
    w_c1 = tf.Variable(w_alpha * tf.random_normal([3, 3, 1, 32]))
    b_c1 = tf.Variable(b_alpha * tf.random_normal([32]))
    conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x_image, 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')
    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)

    # 全连接层
    #w_d = tf.Variable(w_alpha * tf.random_normal([8 * 20 * 64, 1024]))
    w_d = tf.Variable(w_alpha * tf.random_normal([6 * 4 * 64, 1024]))

    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, 4 * 36]))
    b_out = tf.Variable(b_alpha * tf.random_normal([4 * 36]))
    out = tf.add(tf.matmul(dense, w_out), b_out)
    # out = tf.nn.softmax(out)
    return out

def train():
    output = crack_captcha_cnn()
    # loss
    # loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(output, Y))
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=output, labels=y_))
    # 最后一层用来分类的softmax和sigmoid有什么不同?
    # optimizer 为了加快训练 learning_rate应该开始大,然后慢慢衰
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

    predict = tf.reshape(output, [-1, 4, 36])
    max_idx_p = tf.argmax(predict, 2)
    max_idx_l = tf.argmax(tf.reshape(y_, [-1, 4, 36]), 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(step,64)
            _, loss_ = sess.run([optimizer, loss], feed_dict={x: batch_x, y_: batch_y, keep_prob: 0.75})
            print(step, loss_)

            # 每100 step计算一次准确率
            if step % 100 == 0:
                batch_x_test, batch_y_test = get_next_batch(step,100)
                acc = sess.run(accuracy, feed_dict={x: batch_x_test, y_: batch_y_test, keep_prob: 1.})
                print(step, acc)
                # 训练次数大于多少时保存并退出
                if step > 20000:
                    saver.save(sess, "checkpoint/test")
                    break

            step += 1


train()

可视化模型图

graph

(Powered by Tensorboard)

点击此处查看大图

测试

训练完成后,使用保存的模型(迭代20000次)对100个测试集样本进行测试,正确率为93%,即在100个验证码的400个字符中,有7个字符识别错误。

def ints2tag(ints):
    re = ''
    for i in ints:
        if i<10:
            re=re+str(i)
        else:
            re=re+chr(i-10+97)

    return re

def crack_captcha(captcha_image):
    from train import crack_captcha_cnn
    output = crack_captcha_cnn()

    saver = tf.train.Saver()
    with tf.Session() as sess:
        # 加载保存的模型
        saver.restore(sess, tf.train.latest_checkpoint('checkpoint'))
        predict = tf.argmax(tf.reshape(output, [-1, 4, 36]), 2)
        from train import keep_prob
        from train import x
        text_list = sess.run(predict, feed_dict={x: captcha_image, keep_prob: 1})

        for i in range(0,99):
            text = text_list[i].tolist(print ints2tag(text)

def recog():
    X2=[]
    Y2=[]
    samples = os.listdir('test')
    samples.sort()
    for i in range(1, 100):  # len(samples)
        if i % 10 == 0:
            print i
        path = os.path.join('test', '%d.png' %i)

        img = cv2.imread(path, 0)
        # 预处理
        mask = pre_handle(img)

        X2.append(image2vector(mask))

    crack_captcha(X2)

recog()

训练效果分析

识别错误的验证码

enter image description here enter image description here

原因分析

字符重合度较大时,难以识别出重合的两个字符。另一方面,由于训练样本较少,某些字符旋转到特定的角度时可能没有被训练到(如:向右倾斜的6)

loss和准确率曲线

enter image description here enter image description here

loss和准确率曲线在3000,6000,9000处存在跳变,这是因为总共3000个训练样本,训练完一遍重新从头开始训练的缘故(改进训练样本的获取方式可以让曲线更平滑)。

可以看出,CNN在训练次数很小(小于2000)时几乎起不到任何作用,训练数到达某处时(2000)突然就开始有了突飞猛进的进步(量变引起质变?),最后逐渐趋向于稳定。当然这个loss开始下降的阈值并不是固定的,它取决于学习任务的难度、每次feed的样本量、学习率等多方面因素。

项目代码

https://github.com/misads/capcha_recog

我的博客

如果这些内容对你有所帮助,可以关注我的个人博客哦~
http://www.xyu.ink/blog

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
验证码识别是一个常见的图像识别问题,卷积神经网络(CNN)是其中一种常用的解决方案。下面是一个使用 TensorFlow 2 实现的简单 CNN 模型来识别验证码的例子: 首先,导入必要的库: ```python import os import numpy as np import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout ``` 接着,定义一些超参数: ```python IMG_WIDTH = 60 IMG_HEIGHT = 20 BATCH_SIZE = 32 EPOCHS = 10 NUM_CLASSES = 4 ``` 其中,`IMG_WIDTH` 和 `IMG_HEIGHT` 分别代表输入图像的宽度和高度,`BATCH_SIZE` 是每次训练时使用的样本数量,`EPOCHS` 是训练迭代次数,`NUM_CLASSES` 是验证码字符集的大小。 然后,准备数据集。假设我们有一个包含 1000 张验证码图片的数据集,每张图片都是 60x20 的灰度图像,保存在 `data` 文件夹下,文件名为 `captcha_{i}.png`(`i` 从 1 到 1000)。我们需要将数据集分成训练集和测试集,并使用 `ImageDataGenerator` 类来对图像进行预处理: ```python train_datagen = ImageDataGenerator( rescale=1./255, shear_range=0.2, zoom_range=0.2, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, validation_split=0.2) train_generator = train_datagen.flow_from_directory( 'data', target_size=(IMG_HEIGHT, IMG_WIDTH), batch_size=BATCH_SIZE, color_mode='grayscale', class_mode='categorical', subset='training') test_generator = train_datagen.flow_from_directory( 'data', target_size=(IMG_HEIGHT, IMG_WIDTH), batch_size=BATCH_SIZE, color_mode='grayscale', class_mode='categorical', subset='validation') ``` 其中,`train_datagen` 定义了一系列图像增强操作,包括缩放、剪切、旋转、平移等。`train_generator` 和 `test_generator` 分别是训练集和测试集的生成器。 接下来,构建 CNN 模型: ```python model = Sequential([ Conv2D(32, (3, 3), activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH, 1)), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activation='relu'), MaxPooling2D((2, 2)), Conv2D(128, (3, 3), activation='relu'), MaxPooling2D((2, 2)), Flatten(), Dropout(0.5), Dense(512, activation='relu'), Dropout(0.5), Dense(NUM_CLASSES, activation='softmax') ]) ``` 该模型包含 3 个卷积层、3 个池化层和 2 个全连接层,其中每个卷积层后面都跟着一个最大池化层。最后一层是一个大小为 `NUM_CLASSES` 的 softmax 层,用于分类。 最后,编译模型并开始训练: ```python model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(train_generator, epochs=EPOCHS, validation_data=test_generator) ``` 训练完成后,我们可以使用模型来预测新的验证码图片: ```python def predict_captcha(filename): img = tf.keras.preprocessing.image.load_img(filename, color_mode='grayscale', target_size=(IMG_HEIGHT, IMG_WIDTH)) img = tf.keras.preprocessing.image.img_to_array(img) img = np.expand_dims(img, axis=0) img /= 255. prediction = model.predict(img) prediction = np.argmax(prediction, axis=1) return prediction[0] ``` 该函数接受一个验证码图片的文件名,返回模型预测出的验证码字符的标签。 以上就是使用 TensorFlow 2 实现验证码识别的一个简单例子。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值