Python机器学习之验证码识别

  • Python分类模型之验证码识别
    • 下载验证码
    • 图像处理
      • 二值化原始图
      • 声明图像类
      • 切割图片
      • 标注图片
      • 生成训练集矩阵csv文件
      • 验证训练集
      • 训练模型
    • 识别并计算验证码
    • 尾注

Python分类模型之验证码识别


下载验证码

首先,我们从目标网站下载足够多数量的验证码,以用来制作训练集,并通过训练集生成模型。这里以深圳信用网为例,下载500张验证码。

def download_image():
    """download captcha image"""
    url = 'http://www.szcredit.org.cn/web/WebPages/Member/CheckCode.aspx'
    headers = {'user-agent': ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) "
                              "AppleWebKit/536.3 (KHTML, like Gecko) "
                              "Chrome/19.0.1063.0 Safari/536.3")}
    for i in range(0, 500):
        image = Img.download_image(url=url, headers=headers)
        with open('source_image/%s.png' % str(i), 'wb') as f:
            f.write(image)
            f.close()

图像处理

图像处理是验证码识别过程中最复杂,最难的一点,通常需要自己琢磨一些算法逻辑,来对验证码进行切割处理。

二值化原始图

在图像处理之前,我们首先对所有的原始验证码图片进行二值化操作。

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

def two_value():
    """将训练集所有原始图进行二值化"""

    file_list = listdir('source_image/')
    for each in file_list:
        image = Image.open('source_image/%s' % each)
        image = Img.twoValueImage(image, 200)
        image.save('two_value_image/%s' % each)

if __name__ == '__main__':
    two_value()

声明图像类

其次需要进行图片切割,但在切割之前,我们先定义一个验证码的类,图像处理的过程都是通过这个类的方法来实现。

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

class SZ_Captcha:
    """captcha of sz_credit.org"""

    def __init__(self, image):
        """初始化验证码,声明下列属性

        :param image: PIL Image object
        """
        self.image = image      # 图像本身
        self.size = image.size  # 图像尺寸
        self.all_chunks = []    # 所有切块
        self.all_format_chunks = [] # 所有进行重定义尺寸之后的块

    def attributes(self):
        """获取图片的类型和切割线的横坐标位置"""

        if self.size[0] == 90:
            self.type = '11'    # '11':个位数+个位数
            self.node = (0, 19, 39, 55)
        elif self.size[0] == 120:
            self.type = '22'    # '22':十位数+十位数
            self.node = (0, 17, 30, 50, 65, 79)
        else:
            self.type = '12'      # '21': 十位数+个位数, '12':个位数+十位数
            self.node = (0, 19, 39, 53, 67)
            two_value_image = Img.twoValueImage(image=self.image, G=200)
            for j in range(self.size[1]):
                g = two_value_image.getpixel((40, j))   # 循环第四十列的颜色,出现黑色则判断为‘21’,否则为‘12’
                if g == 0:
                    self.type = '21'
                    self.node = (0, 17, 30, 50, 67)
                    break

    def crop(self):
        """根据横坐标位置进行切割,将切割后的图片保存到self.all_chunks里。"""

        for i in range(len(self.node) - 1):
            img = self.image.crop((self.node[i], 0, self.node[i + 1], 30))
            self.all_chunks.append(img)
        if self.type == '11' or self.type == '12':
            self.symbol = self.all_chunks[1]
        else:
            self.symbol = self.all_chunks[2]

    def format(self):
        """将self.all_chunks里的图片进行二值、去边框、环切、重定义尺寸,
        并保存至self.all_format_chunks当中"""

        for i, each in enumerate(self.all_chunks):
            two_value_image = Img.twoValueImage(each, 200)
            remove_frame = Img.clear_frame(two_value_image, 1)
            cut_around = Img.cut_around(remove_frame)
            new_img = Img.format_size(cut_around, (20, 30))
            self.all_format_chunks.append(new_img)

    def recognize(self, model):
        """从self.all_format_chunks中取出图片进行识别,需要传入模型路径。

        :param model str, 由sklearn生成的模型的路径
        :return 四个数字和一个运算符
        """

        result = []
        for each_img in self.all_format_chunks:
            x = Img.classify(each_img, model=model)
            result.append(x)
        if self.type == '11':
            result.insert(2, 0)
            result.insert(0, 0)
        elif self.type == '12':
            result.insert(0, 0)
        elif self.type == '21':
            result.insert(3, 0)
        else:
            pass
        return result       # [x1, x2, symbol, x3, x4]

    def calculate(self, model):
        """对验证码做数学计算"""

        self.attributes()   # 提取属性
        self.crop()         # 切割
        self.format()       # 格式化切割
        x1, x2, symbol, x3, x4 = self.recognize(model=model)
        if symbol.upper() == 'X':
            result = (int(x1) * 10 + int(x2) * 1) * (int(x3) * 10 + int(x4) * 1)
        else:
            result = (int(x1) * 10 + int(x2) * 1) + (int(x3) * 10 + int(x4) * 1)

        return result

现在图片的类已经有了,接下来的图像处理操作将都基于这个类进行。

切割图片

对二值化之后的图片进行切割,并将所有切割后的小图重定义尺寸,使得他们的尺寸一样大,这样做的目的是为了统一特征矩阵,便于数学计算。这里我们统一到20 * 30大小, 同时需要保存起来。

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

def crop():
    """遍历二值图,切割所有图片,并将图片保存。"""

    file_list = listdir('two_value_image/')
    for each in file_list:
        img = Image.open('two_value_image/%s' % each)
        img = SZ_Captcha(img)
        img.attributes()  # 提取属性
        img.crop()  # 切割
        img.format()  # 格式化切割
        for i, a in enumerate(img.all_format_chunks):
            a.save('train_image/%s' % (str(i) + each))

if __name__ == '__main__':
    crop()

标注图片

这一步虽然繁琐,但很重要,对图片进行重命名,命名的第一个字符需要是该图片所表达的字符,如图:

Python机器学习之验证码识别_第1张图片

生成训练集矩阵csv文件

生成csv文件是为了方便训练模型时能更方便的生成矩阵。

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

def create_train_csv():
"""生成训练集"""

file_list = listdir('train_image/')
for each in file_list:
    img = Image.open('train_image/%s' % each)
    x = Img.two_Value(img, 'list')
    y = each[0]
    x.insert(0, y)
    Img.write_csv(fileName='train_csv.csv', values=x)

if __name__ == '__main__':
    create_train_csv()

验证训练集

交叉验证,需要训练集按比例分成训练集和测试集,来验证识别正确率。

# -*- coding:utf-8 -*-
# python 3.6

def verify():
    """交叉验证,校验训练集的准确率。"""

    import csv
    import sklearn.cross_validation as cross_validation
    from sklearn import neighbors
    import sklearn.metrics as metrics

    csvfile = open('train_csv.csv', 'r')
    reader = csv.reader(csvfile)

    # 读取特征信息和结果信息
    featureList = []
    labelList = []
    for row in reader:
        labelList.append(row[0])
        featureList.append(row[1:])

    # 将原始信息按8:2分割为训练集与测试集
    train_data, test_data, train_target, test_target = cross_validation.train_test_split(
        featureList, labelList, test_size=0.2, random_state=0)

    # 输入默认模型
    knn = neighbors.KNeighborsClassifier()
    # 训练模型
    knn.fit(train_data, train_target)
    # 预测测试集
    predict_test = knn.predict(test_data)
    # 现实预测结果
    print(metrics.classification_report(test_target, predict_test))

if __name__ == '__main__':
    verify()

[print]
             precision    recall  f1-score   support

          +       1.00      1.00      1.00        39
          0       1.00      1.00      1.00        14
          1       1.00      1.00      1.00        37
          2       1.00      1.00      1.00        21
          3       0.91      1.00      0.95        10
          4       1.00      1.00      1.00        12
          5       0.83      1.00      0.91        10
          6       1.00      1.00      1.00        12
          7       1.00      1.00      1.00        11
          8       1.00      1.00      1.00        13
          9       1.00      0.80      0.89        15
          x       1.00      1.00      1.00        60

avg / total       0.99      0.99      0.99       254

从结果可以看到,该训练集的正确达到的99%,测试样本254个,如果怀疑这个正确率,也可以将比分按7:3或6:4分开。

训练模型

确定训练集的准确率达到要求之后,接下来开始训练分类模型、保存模型

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

def create_model():
    """建立分类模型"""

    from sklearn import neighbors       
    from sklearn.externals import joblib

    train_x, train_y = Img.loadTrainSet('train_csv.csv')
    knn_cly = neighbors.KNeighborsClassifier()      # 这里选择 knn 分类模型,也可以选择其他分类模型。
    knn_cly.fit(train_x, train_y)               # 训练数据
    joblib.dump(knn_cly, "classify_model.m")   # py2需要在py2环境下训练并保存

if __name__ == '__main__':
    create_model()

识别并计算验证码

下面的代码是调用原始图进行计算,同时展示图片、打印结果,可通过debug来观察计算是否正确。

# -*- coding:utf-8 -*-
# python 3.6

from os import listdir
from PIL import Image
import Img      # 自定义的包

if __name__ == '__main__':
    file_list = listdir('source_image/')
    for each in file_list:
        image = Image.open('source_image/%s' % each)
        image.show()
        img = SZ_Captcha(image=image)
        print(img.calculate(model='classify_model.m'))

尾注

到此,一个验证码识别基本完成,经过观察之后,准确率几乎达到了100%。
最终我们只需要保存 Img.py、sz_credit.py、classify_model.m三个文件即可,同时sz_credit.py中除了class SZ_Captcha,其他函数均可以删除,也可以保留作为日记,反正生产环境用不到的。
完整的代码资源请点击此处下载

你可能感兴趣的:(Python机器学习实战)