Tensorflow2.0+版本 实现图形验证码的自动识别

说明

代码只有两个文件,一个生成验证码,一个是训练和预测,最后面会把所有代码发出来。

二维码的制作

这里就大概过一下,使用的是PIL,生成验证码。

PIL:是python image library的缩写,图像处理的模块。

验证码制作,无非就是在一堆想要出现的数字字母里面,随机生成几个,但又为了不能轻意认识,加了干扰线干扰点。又觉得太单调了,好吧那就加上不同的颜色,稍微酷一点。

大概思路清楚,Just do it~~

准备全部数据

# 准备全部数据
# 这里先以数字识别试试
def getRandomChar():
    random_num = str(random.randint(0, 9))      # 数字 0~9
    random_lower = chr(random.randint(97, 122)) # 小写字母a~z
    random_upper = chr(random.randint(65, 90))  # 大写字母A~Z
    random_char = random.choice([random_num, random_upper])
    return random_num

生成随机颜色

这里is_light留意一下,是为了生成浅色和深色用。具体后面讲为啥。

def getRandomColor(is_light = True):
    """
    生成随机颜色
    :param is_light: 以127分界线,为了设置浅色和深色
    :return:  (r, g, b)
    """
    r = random.randint(0, 127) +int(is_light)* 128
    g = random.randint(0, 127) +int(is_light)* 128
    b = random.randint(0, 127) +int(is_light)* 128
    return (r, g, b)

画干扰线和干扰点

干扰线和干扰点数量自己设置,看你心情

def drawLine(draw):
    """
    随机生成4个干扰线,然后每个设置随机颜色
    """
    for i in range(4):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, y1, x2, y2), fill=getRandomColor(is_light=True))

def drawPoint(draw):
    """
    随机生成80个干扰点,然后每个设置随机颜色
    """
    for i in range(80):
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.point((x,y), fill=getRandomColor(is_light=True))

画出验证码,保存

def createImg():
    # 随机生成一个颜色
    bg_color = getRandomColor(is_light=True)

    # 创建一张随机背景色的图片
    img = Image.new(mode="RGB", size=(width, height), color=bg_color)

    # 获取图片画笔,用于描绘字
    draw = ImageDraw.Draw(img)

    # 修改字体
    font = ImageFont.truetype(font="arial.ttf", size=18)

    # 保存图片的名字
    file_name = ''

    # 这里生成4位数字,就循环4次
    for i in range(4):
        # 随机生成4种字符+4种颜色
        random_txt = getRandomChar()
        txt_color = getRandomColor(is_light=False)

        # 避免文字颜色和背景色一致重合
        while txt_color == bg_color:
            txt_color = getRandomColor(is_light=False)
        # 根据坐标填充文字
        draw.text((15 + 15 * i, 0), text=random_txt, fill=txt_color, font=font)
        file_name +=random_txt
    # 画干扰线和点
    drawLine(draw)
    drawPoint(draw)
    print(file_name)

    # 打开图片操作,并保存在train文件夹下
    with open("./train/{}.png".format(file_name), "wb") as f:

        img.save(f, format="png")

效果就是这样的了
在这里插入图片描述

tensorflow识别

处理图片生成数据集

简单就是读取图片数据,生成训练集合测试集

def gen_train_data(filePath):
    '''
       生成数据集
       :param filePath: 存filePath文件夹获取全部图片处理
       :return: x_data:图片数据,shape=(num, 20, 80),y_data:标签信息, shape=(num, 4)
       '''

    #返回指定的文件夹包含的文件或文件夹的名字的列表。
    train_file_name_list = os.listdir(filePath)
    # 返回值
    x_data = []
    y_data = []

    # 对每个图片单独处理
    for selected_train_file_name in train_file_name_list:
        if selected_train_file_name.endswith('.png'):

            # 获取图片对象
            captcha_image = Image.open(os.path.join(filePath, selected_train_file_name))

            # 对图片去噪,后面对这个方法单独说明
            captcha_image = denoising(captcha_image)
            # captcha_image = captcha_image.convert('L') # 对于简单的不用去噪,灰度反而更有利
            captcha_image_np = np.array(captcha_image)

            # 下面这两个是tensorflow获取图片信息,这里我们还是以上面为例
            # img = tf.io.read_file(os.path.join(filePath, selected_train_file_name))
            # img_np = tf.image.decode_jpeg(img, channels=0)

            img_np = np.array(captcha_image_np)
            # 把每个处理后的数据,塞进x_data,y_data
            x_data.append(img_np)
            y_data.append(np.array(list(selected_train_file_name.split('.')[0])).astype(np.int))

    x_data = np.array(x_data).astype(np.float)
    y_data = np.array(y_data)
    return x_data,y_data

# 生成训练集--train_data_dir(训练文件验证码路径)
(x,y) = gen_train_data(train_data_dir)
# 生成测试集--test_data_dir(测试文件验证码路径)
(x_test,y_test) = gen_train_data(test_data_dir)

打印一下x,y的shape可以看见
在这里插入图片描述

对图片优化

对图片去噪,去掉一些没有任何意义的东西比如干扰点干扰线,只看见我想要的值。处理完之后,把图片灰度。

可惜的是目前去噪算法并没有很好的解决方案,网上很多的去除噪点,切割验证码等等方法并没有很好的解决。我试了试不同的方法,很多都是这种样子
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以实在没法解决的话,直接灰度也可以。(目前NL-Means和BM3D可以说是目前效果最好的去噪算法,放在这有点大材小用)

这个时候还记得验证码生成随机颜色函数的这个参数is_light?利用浅色和深色进行过滤,把is_light=False的像素全部改成白色~~(只是提供一种想法,其实就算不去噪,只灰度也能很高效率的识别)

def denoising(image):
    """
    处理图片,方便更好识别,学习
    :param image:图片对象
    :return: 处理之后的图片
    """

    threshold = 128  # 通过设置阈值,去除不必要的干扰物
    for i in range(image.width):
        for j in range(image.height):
            r,g,b = image.getpixel((i,j))
            if (r > threshold or g >threshold or b > threshold):
                r=255
                g=255
                b=255
                image.putpixel((i,j),(r,g,b))
            else:
                r = 0
                g = 0
                b = 0
                image.putpixel((i, j), (r, g, b))

    # 灰度图片
    image = image.convert('L')
    return image

效果这样的。
在这里插入图片描述
接下来进行batch,对训练集每次10个来一次处理

def preprocess(x,y):
    """
    对x,y进行数据处理,转成tensor张量,小范围缩小在-1~1之间
    """
    x = 2*tf.cast(x,dtype=tf.float32)/255.-1
    x = tf.expand_dims(x,-1)
    y = tf.cast(y,dtype=tf.int32)
    return x,y
batch_size = 10
train_db = tf.data.Dataset.from_tensor_slices((x,y))
train_db = train_db.map(preprocess).batch(batch_size)

建立模型

主要还是卷积神经网络经过池化,然后全连接层处理

model = Sequential([
    # 第一个卷积层
    layers.Conv2D(32, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    # 第二个卷积层
    layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    layers.Flatten(),

    # 全连接
    layers.Dense(128),
    layers.Dense(40), # 因为这里我们4个数字,所以也就4*10可能性
    layers.Reshape([4,10])
])

model.build(input_shape=[None, 20, 80, 1])
model.summary()
# 设置学习率
optimizer = optimizers.Adam(lr=1e-3)

进行训练

    # 进行20次重复训练
    for epoch in range(20):
        for step, (x, y) in enumerate(train_db):
            # 有的时候验证码不是这种格式,就没处理所以就不是的直接过滤
            if x.shape == (10, 20, 80, 1):
                with tf.GradientTape() as tape:
                    # logits
                    logits = model(x)
                    # 真实值就行one_hot编码来对比
                    y_onehot = tf.one_hot(y, depth=10)
                    # 设置loss
                    loss_ce = tf.losses.MSE(y_onehot, logits)
                    loss_ce = tf.reduce_mean(loss_ce)
                # 不断更新梯度
                grads = tape.gradient(loss_ce, model.trainable_variables)
                optimizer.apply_gradients(zip(grads, model.trainable_variables))

                if step % 10 == 0:
                    print(epoch, step, 'loss:', float(loss_ce))

可以看见从开始的0.14一直下降到0.006,大约准确率99%
在这里插入图片描述

!](https://img-blog.csdnimg.cn/20200819143200511.png#pic_center)
对数据进行测试预测,发现确实挺高的,偶尔有那么一两个不对
在这里插入图片描述

全部代码

验证码文件

""" makeVerification.py 文件"""

import random
import os
from PIL import Image,ImageDraw,ImageFont,ImageFilter

# 设置图片宽高
width = 80
height = 20
"""
"""

def getRandomColor(is_light = True):
    """
    生成随机颜色
    :param is_light: 为了设置浅色和深色
    :return:  (r, g, b)
    """
    r = random.randint(0, 127) +int(is_light)* 128
    g = random.randint(0, 127) +int(is_light)* 128
    b = random.randint(0, 127) +int(is_light)* 128
    return (r, g, b)

# 这里全部数据
def getRandomChar():
    random_num = str(random.randint(0, 9))      # 数字 0~9
    random_lower = chr(random.randint(97, 122)) # 小写字母a~z
    random_upper = chr(random.randint(65, 90))  # 大写字母A~Z
    random_char = random.choice([random_num, random_upper])
    return random_num

def drawLine(draw):
    """
    随机生成4个干扰线,然后每个设置随机颜色
    """
    for i in range(4):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, y1, x2, y2), fill=getRandomColor(is_light=True))

def drawPoint(draw):
    """
    随机生成80个干扰点,然后每个设置随机颜色
    """
    for i in range(80):
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.point((x,y), fill=getRandomColor(is_light=True))

def createImg(folder):
    # 随机生成一个颜色为背景色颜色
    bg_color = getRandomColor(is_light=True)

    # 创建一张随机背景色的图片
    img = Image.new(mode="RGB", size=(width, height), color=bg_color)

    # 获取图片画笔,用于描绘字
    draw = ImageDraw.Draw(img)

    # 修改字体
    font = ImageFont.truetype(font="arial.ttf", size=18)

    # 保存图片的名字
    file_name = ''

    # 这里生成4位数字,就循环4次
    for i in range(4):
        # 随机生成4种字符+4种颜色
        random_txt = getRandomChar()
        txt_color = getRandomColor(is_light=False)

        # 避免文字颜色和背景色一致重合
        while txt_color == bg_color:
            txt_color = getRandomColor(is_light=False)
        # 根据坐标填充文字
        draw.text((15 + 15 * i, 0), text=random_txt, fill=txt_color, font=font)
        file_name +=random_txt
    # 画干扰线和点
    drawLine(draw)
    drawPoint(draw)
    print(file_name)

    # 打开图片操作,并保存在当前文件夹下
    with open("./{}/{}.png".format(folder,file_name), "wb") as f:

        img.save(f, format="png")

if __name__ == '__main__':
    # 创建num张验证码
    num = 1000

    # 创建train和test文件夹
    os.path.exists('train') or  os.makedirs('train')
    os.path.exists('test') or os.makedirs('test')

    # 每个文件夹创建num个
    for i in range(num):
        createImg('train')
        createImg('test')


识别文件

import os
from PIL import Image
import numpy as np
import tensorflow as tf
from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
train_data_dir = r'D:\test\python\machinelearning\tensorFlow20\深度学习与TF-PPT和代码\深度学习与TensorFlow入门实战-源码和PPT\lesson32-Keras实战\验证码\train'
test_data_dir = r'D:\test\python\machinelearning\tensorFlow20\深度学习与TF-PPT和代码\深度学习与TensorFlow入门实战-源码和PPT\lesson32-Keras实战\验证码\test'
model_dir = r'D:\test\python\machinelearning\tensorFlow20\深度学习与TF-PPT和代码\深度学习与TensorFlow入门实战-源码和PPT\lesson32-Keras实战\验证码\model.h5'

def denoising(image):
    """
    处理图片,方便更好识别,学习
    :param image:图片对象
    :return: 处理之后的图片
    """

    threshold = 128  # 通过设置阈值,去除不必要的干扰物

    for i in range(image.width):
        for j in range(image.height):
            r,g,b = image.getpixel((i,j))
            if (r > threshold or g >threshold or b > threshold):
                r=255
                g=255
                b=255
                image.putpixel((i,j),(r,g,b))
            else:
                r = 0
                g = 0
                b = 0
                image.putpixel((i, j), (r, g, b))

    # 灰度图片
    image = image.convert('L')
    return image

def gen_train_data(filePath):
    '''
       生成数据集
       :param filePath: 存filePath文件夹获取全部图片处理
       :return: x_data:图片数据,shape=(num, 20, 80),y_data:标签信息, shape=(num, 4)
       '''

    #返回指定的文件夹包含的文件或文件夹的名字的列表。
    train_file_name_list = os.listdir(filePath)
    # 返回值
    x_data = []
    y_data = []

    # 对每个图片单独处理
    for selected_train_file_name in train_file_name_list:
        if selected_train_file_name.endswith('.png'):

            # 获取图片对象
            captcha_image = Image.open(os.path.join(filePath, selected_train_file_name))

            # 对图片去噪,后面对这个方法单独说明
            captcha_image = denoising(captcha_image)
            # captcha_image = captcha_image.convert('L') # 对于简单的不用去噪,灰度反而更有利
            captcha_image_np = np.array(captcha_image)

            # 下面这两个是tensorflow获取图片信息,这里我们还是以上面为例
            # img = tf.io.read_file(os.path.join(filePath, selected_train_file_name))
            # img_np = tf.image.decode_jpeg(img, channels=0)

            img_np = np.array(captcha_image_np)
            # 把每个处理后的数据,塞进x_data,y_data
            x_data.append(img_np)
            y_data.append(np.array(list(selected_train_file_name.split('.')[0])).astype(np.int))

    x_data = np.array(x_data).astype(np.float)
    y_data = np.array(y_data)
    return x_data,y_data

# 生成训练集
(x,y) = gen_train_data(train_data_dir)
# 生成测试集
(x_test,y_test) = gen_train_data(test_data_dir)
print(x.shape,y.shape) #(num个图片验证码, 20宽, 80高) (955个图片验证码, 4)


def preprocess(x,y):
    """
    对x,y进行数据处理,转成tensor张量,小范围缩小在-1~1之间
    """
    x = 2*tf.cast(x,dtype=tf.float32)/255.-1
    x = tf.expand_dims(x,-1)
    y = tf.cast(y,dtype=tf.int32)
    return x,y


batch_size = 10
train_db = tf.data.Dataset.from_tensor_slices((x,y))
train_db = train_db.map(preprocess).batch(batch_size)

test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.map(preprocess).batch(1)


model = Sequential([
    # 第一个卷积层
    layers.Conv2D(32, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    # layers.Dropout(0.25),
    # 第二个卷积层
    layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    # layers.Dropout(0.25),
    layers.Flatten(),

    # 全连接
    layers.Dense(128),
    layers.Dense(40), # 因为这里我们4个数字,所以也就4*10可能性
    layers.Reshape([4,10])
])

model.build(input_shape=[None, 20, 80, 1])
model.summary()
# 设置学习率
optimizer = optimizers.Adam(lr=1e-3)


def train():
    global model
    # 如果存在模型,就拿以前的继续训练,不用再从头开始
    if os.path.exists(model_dir):
        model = tf.keras.models.load_model('model.h5', compile=False)

    # 进行20次重复训练
    for epoch in range(20):
        for step, (x, y) in enumerate(train_db):
            # 有的时候验证码不是这种格式,就没处理所以就不是的直接过滤
            if x.shape == (10, 20, 80, 1):
                with tf.GradientTape() as tape:
                    # logits
                    logits = model(x)
                    # 真实值就行one_hot编码来对比
                    y_onehot = tf.one_hot(y, depth=10)
                    # 设置loss
                    loss_ce = tf.losses.MSE(y_onehot, logits)
                    loss_ce = tf.reduce_mean(loss_ce)
                # 不断更新梯度
                grads = tape.gradient(loss_ce, model.trainable_variables)
                optimizer.apply_gradients(zip(grads, model.trainable_variables))

                if step % 10 == 0:
                    print(epoch, step, 'loss:', float(loss_ce))
    # 因为一次就已经很高了,所以直接保存模型
    model.save('model.h5')

def test():
    model = tf.keras.models.load_model('model.h5', compile=False)
    for step, (x, y) in enumerate(test_db):
        if x.shape == (1, 20, 80, 1):
            logits = model(x)
            logits = tf.nn.softmax(logits)
            pred = tf.cast(tf.argmax(logits,axis=2),dtype=tf.int32)
            print('预测值:',pred[0].numpy(),'真实值:',y[0].numpy(),'是否相同:',int(tf.reduce_sum(tf.cast(tf.equal(pred,y),dtype=tf.int32)))==4)

if __name__ == '__main__':

    #判断是否存在模型文件,没有则训练生成
    choice_flag = 1 # 0训练 1测试
    if os.path.exists(model_dir) and choice_flag==1:
        test()
    else:
        train()

  • 12
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值