寒假闲在家,想搞点事,对某网站研究了一通,登录界面的验证码看起来很简单,心血来潮搞一波
after google
参考了这一片文章:
https://www.cnblogs.com/beer/p/5672678.html

一、开发环境:
1.python3.7
Life is short, you need Python
2.PIL库
pip install Pillow
3.libsvm
libsvm源码
下载之后,在libsvm目录以及python子目录下都要make一下,要不然会少东西
二、流程概述:
1.准备图片素材
2.预处理
3.字符切割
4.图片尺寸归一化(未做)
5.打标签
6.特征提取
7.生成训练数据集
8.训练、生成模型
9.使用识别模型预测
10.测试
三、林肯死大头
1.准备图片素材

写了个函数存了1k张验证码图片
以其中一张为例:

大图:

5位纯数字,排列较规则,无扭曲变形,有部分噪点,这种算是比较容易识别的验证码
PS:关于验证码识别的难度,可以看一下这个问题的第一个回答
获取验证码:

def getimage(count = 1000):
    url = 'XXX'
    for i in range(count):
        data = requests.get(url).content
        image = open(path + str(i) + '.jpg', 'wb')
        image.write(data)
        image.close()

2.预处理
预处理主要分两部分:①二值化,②去噪点
①二值化
所谓二值化,就是把彩图转化为黑白图,图里的像素点只有0和1(或者0和255),即为二值。
主要步骤如下:
将RGB彩图转为灰度图
将灰度图按照设定阈值转化为二值图

def get_bin_table(threshold = 128):
    table = []
    for i in range(256):
        if i < threshold:
            table.append(0)
        else:
            table.append(1)
    return table

image = Image.open(img_path)
imgry = image.convert('L')  # 转化为灰度图

table = get_bin_table()
out = imgry.point(table, '1')
out.save(path)

out就是二值化后的image对象,save一下,二值化的结果:


注意这里要保存成png或者bmp格式,可能因为jpg经过有损压缩,再次打开读像素点出现了类似249这样的值

Image模块的一些方法(比如以上代码中的point)使用最好参考一下手册
我自己当时瞎搜半天搜不到点子上

这是带噪点的二值化图输出像素点值的效果(如果是255,转化为1),1为白色,0为黑色

可以清晰地看到68459
②去噪点
还是使用开头参考文章中的方法,具体原理可以看原文
这里讲一下我用的时候碰到的问题
在使用他的sum_9_region函数时,他是按照调用Image的getpixel函数的返回值非0即1使用的,但我在实际使用时返回的是0和255,所以我另外写了一个getpixel函数,将255转化成了1。
sum_9_region函数计算了一个黑点周围的黑点数量,周围黑点数量少(比如1或者2),则基本可说明这个点是孤立点,即视作噪点,使用putpixel((x, y), 255)去除
函数如下

def remove_noise_point(img):
    for i in range(img.height):
        for j in range(img.width):
            noisepoint = sum_9_region(img, j, i)
            if noisepoint == 1 or noisepoint == 2:
                img.putpixel((j, i), 255)
    return img

去除噪点之后的结果:

可以看到图片已经比较"干净"了。
3.字符切割
字符型验证码图片可以看做多个单字符图片拼成的,我们也可以将图片分解为多个单字符图片,则研究对象变成了0-9十个阿拉伯数字,极大的减小了复杂度。
字符验证码不尽相同,关于字符分割的算法,也没有很通用的方式,要具体图片具体分析。
我用了尽量简单的方法进行切割(难的我也不会)
我处理的验证码尺寸固定,虽然5个字符在图片中的位置不固定,但彼此相对位置是差不多的,所以我打算先把5个字符一起切出来
通过遍历周边行/列的像素点,将一行/列中黑点较少(比如少于2)的行/列舍弃掉
效果图:

这样切割之后,5个字符之间的相对位置基本不变,可以通过固定坐标进行切割,可以得到单个字符的图片:

4.图片尺寸归一化(未做,因为尺寸都是相同的)
接下来要进行模型的训练
5.打标签
首先已经通过前面的步骤获得大量完成预处理并切割到原子级的图片素材

去除一些会影响训练和识别的强干扰的干扰素材
机器在最开始是不具备任何数字的观念的,即并不知道1是1,所以需要人为的对素材进行标识,
建立0-9共十个文件夹(mkdir -p {0..9})
手动往文件夹放对应数字的图片,差不多每个文件夹100张图(一般素材越多,效果越好)
"0"文件夹:

6.特征提取
一种简单粗暴的特征定义:
每行上黑色像素的个数,可以得到width个特征
每列上黑色像素的个数,可以得到height个特征
最后得到width+height维的一组特征
以我使用的图片为例,有18行,11列,得到29维特征。

def get_feature(img):
    """
    获取指定图片的特征值,
    1. 按照每排的像素点,高度为18,则有18个维度,然后为11列,总共29个维度
    :param img_path:
    :return:一个维度为18(高度)的列表
    """

    width, height = img.size
    # img.show()
    pixel_cnt_list = []
    # height = 18
    for y in range(height):
        pix_cnt_x = 0
        for x in range(width):
            # print(img.getpixel((x, y)))
            if img.getpixel((x, y)) == 0:  # 黑色点
                pix_cnt_x += 1

        pixel_cnt_list.append(pix_cnt_x)

    for x in range(width):
        pix_cnt_y = 0
        for y in range(height):
            if img.getpixel((x, y)) == 0:  # 黑色点
                pix_cnt_y += 1

        pixel_cnt_list.append(pix_cnt_y)

    return pixel_cnt_list

7.生成训练数据集
按照 libSVM 指定的格式生成一组带特征值和标记值的向量文件,如下:

第一列是标签列,就是之前人为标记的值,说明这一组特征对应数字几
我每个数字使用了120张素材图,所以向量文件有1200行
8.训练、生成模型

from python.svm import *
from python.svmutil import *
def train_svm_model():
    """
    训练并生成model文件
    :return:
    """
    y, x = svm_read_problem(svm_model_file_path)
    model = svm_train(y, x)
    svm_save_model(model_path, model)

直接调用开源的libSVM方案生成识别模型
BTW:该函数文件我放在了libsvm目录下,注意import的层级
9.使用识别模型预测

def svm_model_test():
    yt, xt = svm_read_problem(predict_file_path)
    model = svm_load_model(model_path)
    p_label, p_acc, p_val = svm_predict(yt, xt, model)  #p_label即为识别的结果

使用时,将验证码进行之前的步骤处理得到同样格式的libsvm文件predict_file,调用即可
p_label为一个列表,包含了每个数字的识别结果。
10.测试
使用了100张验证码进行测试,将识别结果作为图片名保存,如下图:

偶尔会出现识别某个数字错误,但概率很小,可以通过增加训练素材减小。

打赏作者