机器学习案例:验证码识别(Captcha)

验证码(CAPTCHA,全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人工智能的全自动程序。
实验步骤:

  • 1、创建验证码
  • 2、对验证码进行01值化
  • 3、降噪
  • 4、对验证码进行切分
  • 5、对切分后的验证码进行图片转数字化
  • 6、使用逻辑回归建模
  • 7、对新输入的图片进行预测

验证码的创建
1、随机生成验证码的颜色
2、随机生成验证码数字
3、使用PIL进行画图

import os
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import random
import matplotlib.pyplot as plt
def getRandomColor():
    """
    获取一个随机颜色(r,g,b)格式的
    :return:
    """
    c1 = random.randint(0, 255)
    c2 = random.randint(0, 255)
    c3 = random.randint(0, 255)
    if c1 == 255:
        c1 = 0
    if c2 == 255:
        c2 = 0
    if c3 == 255:
        c3 = 0
    return (c1, c2, c3)
def getRandomStr():
    """
    获取一个随机数字,每个数字的颜色也是随机的
    :return:
    """
    random_num = str(random.randint(0, 9))
    return random_num
def generate_captcha():
    """
    使用PIL画图步骤
    :return: 
    """
    # 获取一个Image对象,参数分别是RGB模式。宽150,高30, 随机颜色
    image = Image.new('RGB', (150, 50), (255, 255, 255))
    # 获取一个画笔对象,将图片对象传过去
    draw = ImageDraw.Draw(image)
    # 获取一个font字体对象参数是ttf的字体文件的目录,以及字体的大小
    font = ImageFont.truetype("arlrdbd.ttf", size=32) # 如果找不到字体,需要从网上下载到本地
    label = ""
# 随机生成有5个数字的字符串
    for i in range(5):
        random_char = getRandomStr()

        label += random_char

        # 在图片上写东西,参数是:定位,字符串,颜色,字体
        draw.text((10+i*30, 0), random_char, getRandomColor(), font=font)
   # 画出随机噪点噪线
    width = 150
    height = 30
    # 画线
    for i in range(3):
        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=(0, 0, 0))
    # 画点
    for i in range(5):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=getRandomColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=(0, 0, 0))
    # 保存到硬盘,名为test.png格式为png的图片
    image.save(open(''.join(['captcha_images/', label, '.png']), 'wb'), 'png')
    # image.save(open(''.join(['captcha_predict/', label, '.png']), 'wb'), 'png')

执行代码之后,会在‘captcha_images’下生成实验所需的图片,如图:
在这里插入图片描述
图像处理:对生成的图片进行处理
(1)对验证码图片二值化,首先把图像从RGB 三通道转化成Gray单通道,然后把灰度图(0~255)转化成二值图(0,1)。
(2)将处理好的二值图进行降噪,去除图片中的噪点和噪线

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import os

def binarization(path):
    """
    把一个rgb的图转换成一个二值图
    :param path:
    :return:
    """
    # 通过path把图像laod进来
    img = Image.open(path)
    # 把图像转化成一个灰度图
    img_gray = img.convert("L")
    # 把灰度图组装成数组形式
    img_gray = np.array(img_gray)
    # print(img_gray)
    # 得到灰度图的宽和高
    w, h = img_gray.shape
    for x in range(w):
        for y in range(h):
            # 得到每一个像素块里的灰度值
            gray = img_gray[x, y]
            # 如果灰度值小于等于220, 就把它变成黑色
            if gray <= 220:
                img_gray[x, y] = 0
            # 如果灰度值大于220,就把它变成白色
            else:
                img_gray[x, y] = 1

    plt.figure("")
    plt.imshow(img_gray, cmap="gray")
    plt.axis("off")
    plt.show()

    return img_gray

def noiseReduction(img_gray, label):
    """
    降噪,也就是处理离群点
    如果一个像素点周围只有小于4个黑点的时候,那么这个点就是离群点
    :param img_gray:
    :param label:
    :return:
    """
    height, width = img_gray.shape
    for x in range(height):
        for y in range(width):
            cnt = 0
            # 白色的点不用管
            if img_gray[x, y] == 1:
                continue
            else:
                try:
                    if img_gray[x-1, y-1] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x-1, y] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x-1, y+1] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x, y-1] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x, y+1] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x+1, y-1] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x+1, y] == 0:
                        cnt += 1
                except:
                    pass

                try:
                    if img_gray[x+1, y+1] == 0:
                        cnt += 1
                except:
                    pass

                if cnt < 4:  # 周围少于4个点就算是噪点
                    img_gray[x, y] = 1

    plt.figure(" ")
    plt.imshow(img_gray, cmap="gray")
    plt.axis("off")
    plt.savefig("".join(["clean_captcha_img/", label, ".png"]))

def image_2_clean():
    """
    把所有的图像都转化成二值图
    :return:
    """
    captchas = os.listdir("".join(["captcha_images/"]))
    for captcha in captchas:
        label = captcha.split(".")[0]
        image_path = "".join(["captcha_images/", captcha])
        # 二值化
        im = binarization(image_path)
        # 降噪
        noiseReduction(im, label)

if __name__ == '__main__':
    image_2_clean()
    # path = "captcha_images/00006.png"
    # img_gray = binarization(path)
    # noiseReduction(img_gray, label='00006')

在这里插入图片描述
图像分割:对降噪后的图片进行分割,并对分割后的图片进行存储

import os
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import random
import matplotlib.pyplot as plt
def cutImg(label):
    """
    把图像的每一个数字都切分出来,并且存到新的文件夹下
    :param label:
    :return:
    """
    labels = list(label)
    img = Image.open("".join(['clean_captcha_img/', label, '.png']))
    for i in range(5):
        pic = img.crop((100*(1+i), 170, 100*(1+i)+100, 280))
        plt.imshow(pic)
        # seq就是我们需要存到文件的文件名
        seq = get_save_seq(label[i])
        pic.save("".join(["cut_number/", str(label[i]), "/", str(seq), '.png']))
def get_save_seq(num):
    """
    得到需要保存的数据的文件名
    每一个数文件下的文件名,都是从0开始保存 0.png, 1.png....
    :param num:
    :return:
    """
    nmlist = os.listdir("".join(["cut_number/", num, "/"]))
    if len(nmlist) == 0 or nmlist is None:
        return 0
    else:
        max_file = 0
        for file in nmlist:
            if int(file.split(".")[0]) > max_file:
                max_file = int(file.split(".")[0])
        return int(max_file) + 1
def clean_to_cut():
    """
    对每一个文件都进行切分
    :return:
    """
    captchas = os.listdir("".join(["clean_captcha_img"]))
    for captcha in captchas:
        label = captcha.split(".")[0]
        cutImg(label)
def create_dir():
    for i in range(10):
        os.mkdir("".join(["cut_number/", str(i)]))

if __name__ == '__main__':
    # create_dir()
    clean_to_cut()

在这里插入图片描述

图片转数字化:对切分后的图片灰度化、二值化,使用Image.open()打开图片文件,得到plt图片对象,将plt图片对象转换为ndarray对象,将二值化后的图像转化为1行n列,存入X列表中,并将其对应的数字存入Y列表中。
模型的生成:将X,Y传入逻辑回归模型中,使用交叉验证和网格搜索寻找最优的参数。

import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.externals import joblib

from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
def load_data():
    """
    把数据从cut_number里面导出来
    其中X指的是每一个数字的01值的排列, Y指的是这个数字是什么
    :return:
    """
    X, Y = [], []
    cut_list = os.listdir("cut_number")
    # 循环cut_number文件夹下的每一个自文件夹(1,2,3,4,5...)
    for numC in cut_list:
        num_list_dir = "".join(["cut_number/", str(numC), "/"])
        nums_dir = os.listdir(num_list_dir)
        # 循环子文件夹中的每一个图片
        # print(np.array(Image.open(''.join(['cut_number/', str(numC), '/', '0.png']))))
        for num_file in nums_dir:
            # 导入数字图片
            img = Image.open("".join(["cut_number/", str(numC), "/", num_file]))
            # print(np.array(img))
            # 对数字图片做灰度化
            img_gray = img.convert("L")
            # plt.imshow(img_gray)
            # 把灰度化图片保存到数组里
            img_array = np.array(img_gray)
            w, h = img_array.shape
            # 把灰度化的图片做二值化
            for x in range(w):
                for y in range(h):
                    gray = img_array[x, y]
                    if gray <= 220:
                        img_array[x, y] = 0
                    else:
                        img_array[x, y] = 1

            # 把二值化的图片reshape成1行,n列
            img_re = img_array.reshape(1, -1)
            # print(img_re[0])
            X.append(img_re[0])
            Y.append(int(numC))
    return np.array(X), np.array(Y)

def generate_model(X, Y):
    """
    生成模型
    :param X:
    :param Y:
    :return:
    """
    # 区分测试集和训练集,37开
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3)
    log_clf = LogisticRegression(multi_class="ovr", solver="sag", max_iter=10000)
    # log_clf.fit(X_train, Y_train)

    # 利用交叉验证选择参数
    param_grid = {"tol": [1e-4, 1e-5, 1e-2], "C": [0.4, 0.6, 0.8]}
    grid_search = GridSearchCV(log_clf, param_grid=param_grid, cv=3)
    grid_search.fit(X, Y)
    print(grid_search.best_params_)
    print("模型生成成功")

    # 将模型持久化
    joblib.dump(log_clf, "captcha_model/captcha_model.model")
    print("模型保存成功")

if __name__ == '__main__':

    X, Y = load_data()
    generate_model(X, Y)

图片的预测:
输入要预测的图片,对其进行灰度化,二值化,并进行分割,将分割出来的五个图片输入进模型中。

import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.externals import joblib

from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from .captcha_logistic import *

def get_model():
    model = joblib.load('captcha_model/captcha_model.model')
    return model

def model_predict():
    path = 'captcha_predict/unknown.png'
    pre_img_gray = binarization(path)
    noiseReduction(pre_img_gray, 'unknown')
    # cut image
    labels = ['0', '1', '2', '3', '4']
    img = Image.open(''.join(['clean_captcha_img/unknown.png']))
    for i in range(5):
        pic = img.crop((100 * (1 + i), 170, 100 * (1 + i) + 100, 280))
        plt.imshow(pic)
        pic.save(''.join(['captcha_predict/', labels[i], '.png']))

    result = ''
    model = get_model()
    for i in range(5):
        path = ''.join(['captcha_predict/', labels[i], '.png'])
        img = Image.open(path)
        img_gray = img.convert('L')
        img_array = np.array(img_gray)
        w, h = img_array.shape
        for x in range(w):
            for y in range(h):
                gray = img_array[x, y]
                if gray <= 220:
                    img_array[x, y] = 0
                else:
                    img_array[x, y] = 1

        img_re = img_array.reshape(1, -1)
        X = img_re[0]
        y_pre = model.predict([X])
        result = ''.join([result, str(y_pre[0])])
    return result
if __name__ == '__main__':
    result = model_predict()
    print(result)
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值