1、背景
    验证码自动识别在模拟登陆上使用的较为广泛,一直有耳闻好多人在使用机器学习来识别验证码,最近因为刚好接触这方面的知识,所以特定研究了一番。发现网上已有很多基于machine learning的验证码识别,本文主要参考几位大牛的研究成果,集合自己的需求,进行改进、学习。

2、基本工具   
开发环境:

python 3.5 + pycharm

模块:

Pillow、sklearn、numpy及其他子模块

3、基本流程
描述整个识别流程:

①验证码清理并生成训练集样本

②验证码特征提取

③拟合识别模型

④识别模型测试

4、关于数据集
        没有特意网上找python的生成脚本,用了一个java的验证码生成脚本。验证码是数字+大写字母+小写字母的组合,即[0-9]+[A-Z]+[a-z]。文件名是验证码的正确数字标签,实例如下

使用三个数据集:

①训练集(training set):10000张验证码

②测试集(test set):100张验证码

③验证集(validation set):100张验证码

5、验证码清理并生成训练集样本
(1)读取图片

首先读取该文件路径下的所有图片文件名称,并逐张打开。返回结果image_array,每一个元素类型为“<class 'PIL.JpegImagePlugin.JpegImageFile'>”。

def read_captcha(path):
image_array = []
image_label = []
file_list = os.listdir(path) # 获取captcha文件
for file in file_list:
image = Image.open(path + '/' + file) # 打开图片
file_name = file.split(".")[0] #获取文件名,此为图片标签
image_array.append(image)
image_label.append(file_name)
return image_array, image_label
(2)图像粗清理

图像粗清理包括以下步骤:

step 1:原始图像是RGB图像,即维度为 (26, 80, 3)。将其转换为灰度图像,维度变为(26, 80)。

原始图像:

灰度图像:

step 2:对于将要识别的验证码,显然,里面出现了很多用于干扰作用的灰色线条。博主通过设定灰度阈值(默认100),对图像中大于阈值的像素,赋值为255(灰度图像中像素值范围是0~255,其中255是白色,0是黑色)。发现对于此类型的验证码,这种方法很实用有木有。

def image_transfer(image_arry):
"""
:param image_arry:图像list,每个元素为一副图像
:return: image_clean:清理过后的图像list
"""
image_clean = []
for i, image in enumerate(image_arry):
image = image.convert('L') # 转换为灰度图像,即RGB通道从3变为1
im2 = Image.new("L", image.size, 255)
for y in range(image.size[1]): # 遍历所有像素,将灰度超过阈值的像素转变为255(白)
for x in range(image.size[0]):
pix = image.getpixel((x, y))
if int(pix) > threshold_grey: # 灰度阈值
im2.putpixel((x, y), 255)
else:
im2.putpixel((x, y), pix)
image_clean.append(im2)
return image_clean

(3)图像细清理

仅仅通过粗清理的办法,无法完全去除所有噪声点。此处引入了更细粒度的清理方法,参考这位大牛的清理方法。

主要有3大步骤:

step 1:找出图像中所有的孤立点;

step 2:计算黑色点近邻9宫格中黑色点个数,若小于等于2个,那么认为该点为噪声点;

step 3:去除所有噪声点。

经过细清理后,虽然可以看到还存在一个噪声点,但效果其实很不错了。

(4)单字符图像切割

去除孤立点后,我们还是没法一下子就识别出这四个字符,需要对经过处理后的图片进行切分。(其实可以使用deep learning的方法进行识别,但本文仅介绍基于machine learning的识别方法)

切割方式主要有一下步骤:

step 1:找出图片中所有分离图像的开始结束位置。遍历width&height,当每出现一个黑色点,记为该字符开始位置;当新的一列出现全白色点,那么记为结束位置。

[(8, 9), (14, 22), (29, 38), (42, 50), (57, 66)]

step 2:尽管经过清理后,还是可能存在噪声点。在找到所有切割开始结束位置后,计算并选出(结束值-开始值)最大的切割位置。

[(14, 22), (29, 38), (42, 50), (57, 66)]
切割后视图如下:

code:

def image_split(image):
"""
:param image:单幅图像
:return:单幅图像被切割后的图像list
"""
inletter = False #找出每个字母开始位置
foundletter = False #找出每个字母结束位置
start = 0
end = 0
letters = [] #存储坐标
for x in range(image.size[0]):
for y in range(image.size[1]):
pix = image.getpixel((x, y))
if pix != True:
inletter = True
if foundletter == False and inletter == True:
foundletter = True
start = x
if foundletter == True and inletter == False:
foundletter = False
end = x
letters.append((start, end))
inletter = False # 因为切割出来的图像有可能是噪声点
# 筛选可能切割出来的噪声点,只保留开始结束位置差值最大的位置信息
subtract_array = [] # 存储 结束-开始 值
for each in letters:
subtract_array.append(each[1]-each[0])
reSet = sorted(subtract_array, key=lambda x:x, reverse=True)[0:image_character_num]
letter_chioce = [] # 存储 最终选择的点坐标
for each in letters:
if int(each[1] - each[0]) in reSet:
letter_chioce.append(each) image_split_array = [] #存储切割后的图像
for letter in letter_chioce:
im_split = image.crop((letter[0], 0, letter[1], image.size[1])) # (切割的起始横坐标,起始纵坐标,切割的宽度,切割的高度)
im_split = im_split.resize((image_width, image_height)) # 转换格式
image_split_array.append(im_split) return image_split_array[0:int(image_character_num)]

(5)保存到训练集

将按上述方法切分后的单个数字、字母,保存到新建的文件夹里,专门用来作为模型的训练集。

6、特征提取
    特征提取是针对每一个切割出后的单个字符,如6。此处构建特征的方法较为简单,统计每个字符图像每一行像素值为黑色的总和(灰度值为0),加上每一列像素值为黑色的总和。因为我们切割后的图像大小为8*26(width*height),故特征个数为34=8+26。当然此处其实可以把单字符图像按像素值展开为一个208=8*26的向量,以此作为特征向量,也是可以的。示例结果如下所示:

feature vector: [7, 11, 13, 4, 4, 13, 11, 7, 0, 0, 0, 0, 0, 4, 6, 4, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0]
    聪明的你可能会发现了一个致命的问题,如果使用新类型/不同像素大小的验证码来做处理和特征提取,那程序不就报错了?我们已经在切割的步骤后面加上像素大小的转换:

im_split = im_split.resize((image_width, image_height)) # 转换格式,im_split为切割后的图像,image_width为目标像素宽度,iamge_height为目标像素高度
    当然,在读取图像的时候就转换格式也是可以的~

code:

def feature_transfer(image):
"""
:param image (图像list)
:return:feature (特征list)
"""
image = image.resize((image_width, image_height)) #标准化图像格式 feature = []#计算特征
for x in range(image_width):#计算行特征
feature_width = 0
for y in range(image_height):
if image.getpixel((x, y)) == 0:
feature_width += 1
feature.append(feature_width) for y in range(image_height): #计算列特征
feature_height = 0
for x in range(image_width):
if image.getpixel((x, y)) == 0:
feature_height += 1
feature.append(feature_height)
# print('feature length :',len(feature))
print("feature vector:",feature)
return feature

7、训练识别模型
    关于训练识别模型,使用全量学习的方式,训练集用于拟合模型,测试集用于测试模型效果。本博客对比了SVC、random forest。

关于SVC的参数,使用了不同kernel(线性核、高斯核),以及在一定范围内修改了正则项C,但测试效果不十分理想。对于正则项C,位于SVM模型的目标函数位置,当C越大时,模型对误分类的惩罚增大,反之,减少。

关于随机森林的参数,调整了树的深度、每个节点分支需要的最少样本数,尽量简化了每棵树的结构。效果较SVC好。

得到的结果如下

model parameters training accuracy test accuracy
SVC(linear) C=1.0 0.9125 0.65
SVC(rbf) C=1.0 0.9055 0.55
Random Foest max_depth=10, min_sample_split=10 0.9420 0.75
code:

def trainModel(data, label):
print("fit model >>>>>>>>>>>>>>>>>>>>>>") # svc_rbf = svm.SVC(decision_function_shape='ovo',kernel='rbf') # rbf核svc
# svc_linear = svm.SVC(decision_function_shape='ovo',kernel='linear') #linear核svc
rf = RandomForestClassifier(n_estimators=100, max_depth=10,min_samples_split=10, random_state=0) #随机森林
scores = cross_val_score(rf, data, label,cv=10) #交叉检验,计算模型平均准确率
print("rf: ",scores.mean())
rf.fit(data, label) # 拟合模型 joblib.dump(rf, model_path) # 模型持久化,保存到本地
print("model save success!") return rf

关于数据量问题:

当训练样本非常大,如上千万/亿的时候,若使用传统machine learning的全量学习方法,需要消耗大量内容,对个人用户并不友好,此时可以引入增量学习。类似于训练neural network,每次训练只使用一个batch的小数据集,经过多次迭代,可达到非常robust的效果。

scikit-learn中支持SGD、Naive Bayes等分类模型的增量学习。通过迭代的方式,每次生成小数据集batch,使用partial_fit()方法训练模型。

scikit-learn 0.19.1 支持如下模型的增量学习

8、模型测试效果
    得到的结果如下

model parameters training accuracy test accuracy
SVC(linear) C=1.0 0.9125 0.65
SVC(rbf) C=1.0 0.9055 0.55
Random Foest max_depth=10, min_sample_split=10 0.9420 0.75

9、识别预测流程
    经过上述步骤,我们已经训练好了一个具有一定识别验证码能力的模型,为了能让模型自动化实现输入验证码文件,输出验证码识别结果,流程如下:

①读取将要识别的验证码文件

②验证码粗清理。将灰度值小于阈值的像素值转化为255。

③验证码细清理。找出所有孤立的噪声点,并将该像素值转化为255。

④字符切割。找出所有字符的开始结束位置,并切割出4幅图像。

⑤图像特征提取。对于4幅图像中的每一幅,分别从行、列统计其灰度值为0(黑色)的和,构建4个特征向量。

⑥识别。读取训练好的模型,分别对4个特征向量进行识别,得到4个预测结果。

⑦输出。将识别出的4个字符结果,串起来,并输出到结果文件。

code:

#-*- coding:utf-8 -*

import os
from captcha_test.captcha_soc import image_process, image_feature, image_model, image_training
from sklearn.externals import joblib
from captcha_test.captcha_soc.config import * #验证码数据清洗
def clean():
#验证码清理
image_array, image_label = image_process.read_captcha(test_data_path) #读取待测试验证码文件
print("待测试的验证码数量:", len(image_array))
image_clean = image_process.image_transfer(image_array) #转换成灰度图像,并去除背景噪声
image_array = [] #[[im_1_1,im_1_2,im_1_3,im_1_4],[im_2_1,im_2_2,im_2_3,im_2_4],...]
for each_image in image_clean:
image_out = image_process.get_clear_bin_image(each_image) #转换为二值图片,并去除剩余噪声点
split_result = image_process.image_split(image_out) #切割图片
image_array.append(split_result)
return image_array, image_label #特征矩阵生成
def featrue_generate(image_array):
feature = []
for num, image in enumerate(image_array):
feature_each_image = []
for im_meta in image:
fea_vector = image_feature.feature_transfer(im_meta)
# print('label: ',image_label[num])
# print(feature)
feature_each_image.append(fea_vector)
# print(fea_vector)
# print(len(feature_each_image))
if len(feature_each_image) == 0:
feature_each_image = [[0]*(image_width+image_height)]*int(image_character_num)
# print(feature_each_image)
feature.append(feature_each_image)
print("预测数据的长度:", len(feature))
print("预测数据特征示例:", feature[0])
return feature #将结果写到文件
def write_to_file(predict_list):
file_list = os.listdir(test_data_path)
with open(output_path, 'w') as f:
for num, line in enumerate(predict_list):
if num == 0:
f.write("file_name\tresult\n")
f.write(file_list[num] + '\t' + line + '\n')
print("结果输出到文件:", output_path) def main():
#验证码清理
image_array, image_label = clean()
#特征处理
feature = featrue_generate(image_array)
#预测
predict_list = []
acc = 0
model = joblib.load(model_path) #读取模型
# print("预测错误的例子:")
for num, line in enumerate(feature):
# print(line)
predict_array = model.predict(line)
predict = ''.join(predict_array)
predict_list.append(predict)
if predict == image_label[num]:
acc += 1
else:
pass
print("-----------------------")
print("actual:",image_label[num])
print("predict:", predict)
print("测试集预测acc:", acc/len(image_label))
#输出到文件
write_to_file(predict_list) if __name__ == '__main__':
main()

10、总结
    关于上述机器学习的验证码识别,只是作了一个简单例子的过程演示。仅仅是针对某种特定类型的验证码,若换成其他类型的验证码做测试,不能保证识别的准确率。

这就是传统机器学习的不足:需要人工做数据清理和提炼特征。有个办法可以解决这种繁琐的数据清理,以及人工提取验证码特征的缺点,那就是深度学习的方法。

博主使用深度学习的循环神经网络,训练了一个识别模型,具体请跳转到这里。

11、相关博客&文献
https://www.cnblogs.com/TTyb/p/6156395.html?from=timeline&isappinstalled=0

https://www.cnblogs.com/beer/p/5672678.html

完整代码及数据集:github.com/wzzzd/captcha_ml

---------------------
作者:Neleuska
来源:CSDN
原文:https://blog.csdn.net/Neleuska/article/details/80040304
版权声明:本文为博主原创文章,转载请附上博文链接!

python 基于机器学习识别验证码的更多相关文章

  1. 使用Python基于OpenCV的验证码识别

    Blog:https://blog.csdn.net/qq_40962368/article/details/89312429(Verification_Code_Identification) 步骤 ...

  2. Python Tensorflow CNN 识别验证码

    Python+Tensorflow的CNN技术快速识别验证码 文章来源于: https://www.jianshu.com/p/26ff7b9075a1 验证码处理的流程是:验证码分析和处理—— te ...

  3. 【python】入门级识别验证码

    前情:这篇文章所提及的内容是博主上个暑假时候做的,一直没有沉下心来把自己的心得写在纸面上,所幸这个假期闲暇时候比较多,想着能写多少是多少,于是就有了此篇. 验证码?我也能破解? 关于验证码的介绍就不多 ...

  4. 吴裕雄--天生自然python学习笔记:python 用 Tesseract 识别验证码

    用 Selenium 包实现网页自动化操作的案例中,发现很多网页都因 需输入图形验证码而导致实验无法进行 . 解决的办法就是对验证码进行识别 . 识 别的方法之 一 是通过图形处理包将验证码的大部分背 ...

  5. 基于Python使用SVM识别简单的字符验证码的完整代码开源分享

    关键字:Python,SVM,字符验证码,机器学习,验证码识别 1   概述 基于Python使用SVM识别简单的验证字符串的完整代码开源分享. 因为目前有了更厉害的新技术来解决这类问题了,但是本文作 ...

  6. Python之selenium+pytesseract 实现识别验证码自动化登录脚本

    今天写自己的爆破靶场WP时候,遇到有验证码的网站除了使用pkav的工具我们同样可以通过py强大的第三方库来实现识别验证码+后台登录爆破,这里做个笔记~~~ 0x01关于selenium seleniu ...

  7. 文字识别还能这样用?通过Python做文字识别到破解图片验证码

    前期准备 1. 安装包,直接在终端上输入pip指令即可: # 发送浏览器请求 pip3 install requests # 文字识别 pip3 install pytesseract # 图片处理 ...

  8. python识别验证码——PIL,pytesser,pytesseract的安装

    1.使用Python识别验证码需要安装Python的图像处理模块(PIL.pytesser.pytesseract) (安装过程需要pip,在我的Python中已经安装pip了,pip的安装就不在赘述 ...

  9. Python爬虫入门教程 60-100 python识别验证码,阿里、腾讯、百度、聚合数据等大公司都这么干

    常见验证码 之前的博客中已经解决了一些常见验证码的问题,但是验证码是层出不穷的,目前解决验证码除了通过常规手段解决以外,还可以通过人工智能领域的深度学习去解决 深度学习?! 无疑对爬虫coder提高了 ...

随机推荐

  1. Python正则表达式里的单行re.S和多行re.M模式

    Python正则表达式里的单行re.S和多行re.M模式 Python 的re模块内置函数几乎都有一个flags参数,以位运算的方式将多个标志位相加.其中有两个模式:单行(re.DOTALL, 或者r ...

  2. SpringBoot返回date日期格式化,解决返回为TIMESTAMP时间戳格式或8小时时间差

    问题描述 在Spring Boot项目中,使用@RestController注解,返回的java对象中若含有date类型的属性,则默认输出为TIMESTAMP时间戳格式 ,如下所示: 解决方案    ...

  3. Activiti工作流框架——快速上手

        一.前言 最近在做公司的OA,里面有用到工作流,公司用的是 jbpm4,感觉比较老,资料有点少,就先学学 新一点的 activiti  ㄟ(▔▽▔)ㄏ 首先工作流的概念是:工作流(Workfl ...

  4. 5.15 pymysql 模块

    pymysql 模块 安装 pip3 install pymysql 链接,执行sql,关闭(游标) import pymysql user= input('用户名:>>').strip( ...

  5. emwin 之 LISTWVIEW 控件禁止列滑动

    @2019-02-25 [小记] hHeader = LISTVIEW_GetHeader(hListView); WM_DisableWindow(hHeader);

  6. ovs之组网实验

    介绍 本示例将创建两个OVS实例和两个主机,其中每个OVS上接入一个主机,OVS实例之间有链路连接,形成一个链状拓扑,如图.在OVS组网完成之后,再通过手动方式添加流表,实现网络通信,从而验证实验可行 ...

  7. Python【第一篇】python安装、pip基本用法、变量、输入输出、流程控制、循环

    一.python安装 Ubuntu下 系统版本已经同时安装了python2和python3 如果没有python3,可以参考这个貌似是印度阿三的安装视频:http://v.youku.com/v_sh ...

  8. [NOI2005]维护数列——平衡树观止

    本题题解并不详细,不推荐大家看这一篇. 可以看这篇 题目描述 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格) 100%的数据中, ...

  9. WebSocke实时通讯协议

    WebSocket 是什么? WebSocket 是一种网络通信协议.RFC6455 定义了它的通信标准. WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议 ...

  10. 使用SO_REVTIMEO套接字选项为recvfrom设置超时

    void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; ]; struct timeval ...