教务处引起的验证码识别的机器学习-Python版本

前言

验证码识别涉及很多方面的内容。入手难度大,但是入手后,可拓展性又非常广泛,可玩性极强,成就感也很足。

验证码内的字符识别主要以机器学习的分类算法来完成,目前我所利用的字符识别的算法为KNN(K邻近算法)和SVM (支持向量机算法)

本文使用SVM,适合初学者,简单易懂,效果显著

准备工具

  • Python3.6
  • libsvm (svm机器学习库)
  • PIL (图像处理库)

大致流程

  1. 抓取原始图片素材(验证码图片)
  2. 原始图片处理 (灰度化)
  3. 图片切割 (图片切割成单个字符的小图片)
  4. 图片尺寸归一化 (小图片变为同种格式)
  5. 图片字符标记 (人工分类小图片,并打标)
  6. 图片特征提取
  7. 得到特定格式的特征训练集
  8. 通过模型实现图片验证码

准备工作

下载

  • Libsvm下载 https://www.csie.ntu.edu.tw/~cjlin/libsvm/oldfiles/
    将libsvm包放入C:\Python36\Lib\site-packages目录下
    如果svmutil.py, line 5 报错
    将其5、6 行的from svm import 改为from .svm import
  • PIL numpy os sys requests bs4(beautifulsoup4) pytesser3

pytesser3 OCR in Python using the Tesseract engine from Google。是谷歌OCR开源项目的一个模块,可将图片中的文字转换成文本(主要是英文)。

在这里,pytesser,我觉得识别效果一般,所以我就没使用。

批量抓取验证码图片

通过在某网站登录处获取验证码
验证码总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 验证码下载
def get_pic(s ,pic_name):
global codeurl
path = 'images'
isExists=os.path.exists(path)
if isExists != True:
os.makedirs(path)
print('文件夹'+path+' 创建成功')
else:
pass
# 得到验证码图片
rcode = s.get(codeurl)
f = open('./images/{}.png'.format(pic_name), 'wb')
f.write(rcode.content)
f.close()
print(pic_name,"\t图片写入成功!")
# 利用session 保持同步
s = requests.Session()
# 图片数量
end_num = 100
for i in range(0,end_num):
pic_name = 'pic_'+str(i)
get_pic(s, pic_name)

图像处理

图片处理,是为了减少后面训练时的复杂。

图片二值化

  1. 将RGB彩图转为灰度图
  2. 将灰度图按照设定阈值转化为二值图
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def get_bin_table(threshold=140):
    """
    获取灰度转二值的映射table
    :param threshold:
    :return:table
    """
    table = []
    for i in range(256):
    if i < threshold:
    table.append(0)
    else:
    table.append(1)
    return table

这里阈值需要自己通过图片对比来自行调整,感觉最合适的值即可,我设的是200.

图片切割

将验证码图片 切割成 只包含单个字符的图片

使用图像编辑软件(Photoshop)打开验证码图片,放大到像素级别,观察其它一些参数特点
原子级
仔细观察,可以看到一格一格的像素点。
这里统一将图片(5218)切割成818格式的小图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_crop_imgs(img):
"""
按照图片的特点,进行切割,这个要根据具体的验证码来进行工作.
:param img
:return:child_img_list
"""
child_img_list = []
for i in range(4):
x = 2 + i * (8 + 5) # 见原理图
y = 0
child_img = img.crop((x, y, x + 8, y + 18))
child_img_list.append(child_img)
return child_img_list
# 分割图片
images = get_crop_imgs(out)
for i in images:
o = i.resize((8, 18))
o.save('./tmp/'+str(n)+'.png','PNG')
n += 1

尺寸归一化

将数据集整合为统一格式的状态文件

resize((8, 18))

模型训练

基本步骤:

  1. 准备图片素材
  2. 人工对图片打上标签
  3. 得到图片识别特征
  4. 模型训练

素材准备

剔除单个字符小图片其中一个难以辨认的图片。

人工打标

这一个是工作量最大,也是最无聊的阶段,
就是将众多单个字符小图片放入0-9这10个文件夹中。
但这一部也是最简单的,靠眼睛认即可。
0-9图片
例如4文件夹
4

特征提取

字符图片 宽8个像素,高18个像素,经过降维之后可以得到18维的一组特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#获取指定图片的特征值
def get_feature(img):
"""
获取指定图片的特征值,
1. 按照每排的像素点,高度为18,则有18个维度,然后为8列,总共144个维度
:param img_path:
:return:一个维度为18(高度)的列表
"""
width, height = img.size

pixel_cnt_list = []
height = 10
for y in range(height):
pix_cnt_x = 0
for x in range(width):
# == 0 如果按照示例程序无法提取特征值,如果没有出现,就看看当前像素点的值进行自我处理。
if img.getpixel((x, y)) < 240: # 黑色点
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)) < 240: # 黑色点
pix_cnt_y += 1

pixel_cnt_list.append(pix_cnt_y)

return pixel_cnt_list

# 得到特征值文件
def get_fea_file(img_path,n,flag_print):
# 遍历图片文件
for i in os.walk(img_path + '/' + str(n)):
im_list = i[2]
for im_file in im_list:
im_name = './' + img_path + '/' + str(n) + '/' + im_file
# print(im_name)
im = Image.open(im_name)
# 特征选择
im_feature = get_feature(im)
# print(im_feature)
z_index = 1
if os.path.exists(str(n) + '_feature.txt'):
ctreate_file = False
else:
ctreate_file = True
# f = open(str(n) + '_feature.txt', 'a')
# f = open(str(n) + '_feature.txt', 'a')
#单个特征值文件
f = open('feature.txt', 'a')
if ctreate_file:
if flag_print:
for z in im_feature:
if z_index == 1:
print(n, end=' ')
f.write(str(n) + ' ')
print(str(z_index) + ':' + str(z), end=' ')
f.write(str(z_index) + ':' + str(z) + ' ')
z_index += 1
print('')
f.write('\n')
else:
for z in im_feature:
if z_index == 1:
f.write(str(n) + ' ')
f.write(str(z_index) + ':' + str(z) + ' ')
z_index += 1
f.write('\n')
f.close()

将图片素材特征化,按照 libSVM 指定的格式生成一组带特征值和标记值的向量文件。

内容举例如下:
特征值

此文件格式、如何使用-参考:

https://www.csie.ntu.edu.tw/~cjlin/libsvm/

https://www.cnblogs.com/-ldzwzj-1991/p/5893054.html

https://www.cnblogs.com/-ldzwzj-1991/p/5897199.html

http://www.cnblogs.com/Finley/p/5329417.html

模型训练

这里就是如何使用libsvm的过程,

1
2
3
4
5
6
7
8
9
10
11
12
13
def train_model_main2():
# LibSVM
# 按照 libSVM 指定的格式生成一组带特征值和标记值的向量文件
svm_path = r"C:\Python36\risk_down\libsvm"
sys.path.append(svm_path + r"\python")
import svmutil
# y, x = svmutil.svm_read_problem('./' + str(n) + '_feature.txt')
y, x = svmutil.svm_read_problem('./' + 'feature.txt')
model = svmutil.svm_train(y[:50], x[:50], '-c 4')
model_path = "num_feature.model"
svmutil.svm_save_model(model_path, model)
# p_label,p_acc,p_val = svmutil.svm_predict(y[0:], x[0:],model)
# print(p_label,p_acc,p_val)

模型测试

使用训练集之外的图片作为测试集,得到其特征值,通过模型进行比对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#train.txt是所有训练集的特征值文件,test.txt是所有测试集的特征值文件。
def main():
y, x = svm_read_problem('train.txt')
yt, xt = svm_read_problem('test.txt')
# 训练模型过程中,参数含义
"""
iter 为迭代次数,
nu 与前面的操作参数 -n nu 相同,
obj 为 SVM 文件转换为的二次规划求解得到的最小值,
rho 为判决函数的常数项 b ,
nSV 为支持向量个数,
nBSV 为边界上的支持向量个数,
Total nSV 为支持向量总个数
"""
model = svm_train(y, x)
print('测试结果:',end='\t')
p_label, p_acc, p_val = svm_predict(yt, xt, model)
print(p_label)

测试结果:

1
2
测试结果:Accuracy = 85.7143% (17/21) (classification)
[8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 8.0, 8.0, 8.0, 8.0]

完整代码及数据

GitHub:xiaobing88https://github.com/xiaobing88/OCR_by_MI

免责声明

  • 本文只做了该网站对外公开的公共图片资源进行了爬取, 并未越权 做任何多余操作。
  • 本文的主要目的也仅是用于 OCR交流学习 和引起大家对 验证安全的警觉 。
  • 本文研究所用素材来自于某Web网站 完全对外公开 的公共图片资源。
  • 本文未做任何破坏性操作,如有需要 ,本文可以积极删除。

参考资料