前言
验证码识别涉及很多方面的内容。入手难度大,但是入手后,可拓展性又非常广泛,可玩性极强,成就感也很足。
验证码内的字符识别主要以机器学习的分类算法来完成,目前我所利用的字符识别的算法为KNN(K邻近算法)和SVM (支持向量机算法)
本文使用SVM,适合初学者,简单易懂,效果显著
准备工具
- Python3.6
- libsvm (svm机器学习库)
- PIL (图像处理库)
大致流程
- 抓取原始图片素材(验证码图片)
- 原始图片处理 (灰度化)
- 图片切割 (图片切割成单个字符的小图片)
- 图片尺寸归一化 (小图片变为同种格式)
- 图片字符标记 (人工分类小图片,并打标)
- 图片特征提取
- 得到特定格式的特征训练集
- 通过模型实现图片验证码
准备工作
下载
- 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)
图像处理
图片处理,是为了减少后面训练时的复杂。
图片二值化
- 将RGB彩图转为灰度图
- 将灰度图按照设定阈值转化为二值图
1
2
3
4
5
6
7
8
9
10
11
12
13def 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
19def 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))
模型训练
基本步骤:
- 准备图片素材
- 人工对图片打上标签
- 得到图片识别特征
- 模型训练
素材准备
剔除单个字符小图片其中一个难以辨认的图片。
人工打标
这一个是工作量最大,也是最无聊的阶段,
就是将众多单个字符小图片放入0-9这10个文件夹中。
但这一部也是最简单的,靠眼睛认即可。
例如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
13def 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网站 完全对外公开 的公共图片资源。
- 本文未做任何破坏性操作,如有需要 ,本文可以积极删除。
参考资料
- 字符型图片验证码识别完整过程及Python实现https://www.cnblogs.com/beer/p/5672678.html
- LibSVM学习详细说明https://www.cnblogs.com/-ldzwzj-1991/p/5897199.html