[机器视觉]使用python自动识别验证码详解


Posted in Python onMay 16, 2019

前言

CAPTCHA全称Completely Automated Public Turing Test to Tell Computers and Humans Apart,即全自动区分人机的图灵测试。这也是验证码诞生的主要任务。但是随着近年来大数据运算和机器视觉的发展,用机器视觉识别图像已经变得非常容易,过去用于区分人机的验证码也开始变得不再安全。

接下来就让我们从零开始,深入图像处理和算法构建,来看看使用机器视觉来识别过时的验证码( 如下所示 )究竟可以有多简单。

[机器视觉]使用python自动识别验证码详解

载入需要的程序包 & 设置全局变量

import requests
import time
from io import BytesIO
from PIL import Image
import os
import numpy as np

# 获取验证码的网址
CAPT_URL = "http://xxxxxxxxxxxx.cn/servlet/ImageServlet"

# 验证码的保存路径
CAPT_PATH = "capt/"
if not os.path.exists(CAPT_PATH):
  os.mkdir(CAPT_PATH)

# 将验证码转为灰度图时用到的"lookup table"
THRESHOLD = 165
LUT = [0]*THRESHOLD + [1]*(256 - THRESHOLD)

从网站获取验证码

capt_fetch()方法非常简单,我们直接从网站获取验证码,将其转换为Image对象,等待被训练和测试等环节调用。

def capt_fetch():
  """
  从网站获取验证码,将验证码转为Image对象

  :require requests: import requests
  :require time: import time
  :require BytesIO: from io import BytesIO
  :require Image: from PIL import Image

  :param:
  :return capt: 一个Image对象
  """
  # 从网站获取验证码
  capt_raw = requests.get(CAPT_URL)

  # 将二进制的验证码图片写入IO流
  f = BytesIO(capt_raw.content)

  # 将验证码转换为Image对象
  capt = Image.open(f)

  return capt

保存验证码到本地

  1. 一个强大的机器学习模型,是离不开强大的训练集作支持的。这里我们也必须先有一个预先打好标签(预分类)的验证码图片集,才能开始训练模型。
  2. capt_download()方法就是我们用来建立训练图像集的方法。它会调用capt_fetch()方法,将获得的Image对象展示给用户,等待用户输入验证码中的字符,然后将图片命名为用户输入的字符存储起来。
  3. 当然,为了避免文件名重复(比如获取到了两张字符完全相同的验证码),capt_download()方法将系统时间也加入到了文件名中。
def capt_download():
  """
  将Image类型的验证码对象保存到本地

  :require Image: from PIL import Image
  :require os: import os

  :require capt_fetch(): 从nbsc网站获取验证码
  :require CAPT_PATH: 验证码保存路径

  :param:
  :return: 
  """
  capt = capt_fetch()
  capt.show()

  text = raw_input("请输入验证码中的字符:")
  suffix = str(int(time.time() * 1e3))

  capt.save(CAPT_PATH + text + "_" + suffix + ".jpg")

 图像预处理

  1. capt_process()方法会先将验证码转为灰度图,然后再根据全局变量中定义的LUT将灰度图转化为黑白图片。并按照验证码中四个字符所在的位置进行切割。
  2. 从彩色图片到灰度图,再到黑白图,看似验证码中的信息损失了很多,实际上这样做的目的是为了使字符的特征更加明显。
  3. 其实我们最终得到的黑白图像会有一些噪点存在,这主要是由于前景色与背景色不存在严格的区分度,我们可以使用滤波器过滤掉这些噪点,但少量的噪点会被训练模型当作误差处理,并不影响我们分类。至于过滤噪点的方法,我会专门写一篇帖子。
def capt_process(capt):
  """
  图像预处理:将验证码图片转为二值型图片,按字符切割

  :require Image: from PIL import Image
  :require LUT: A lookup table, 包含256个值

  :param capt: 验证码Image对象
  :return capt_per_char_list: 一个数组包含四个元素,每个元素是一张包含单个字符的二值型图片
  """
  capt_gray = capt.convert("L")
  capt_bw = capt_gray.point(LUT, "1")

  capt_per_char_list = []
  for i in range(4):
    x = 5 + i * 15
    y = 2
    capt_per_char = capt_bw.crop((x, y, x + 15, y + 18))
    capt_per_char_list.append(capt_per_char)

  return capt_per_char_list

图像预处理的效果如下:

[机器视觉]使用python自动识别验证码详解

原始图像

[机器视觉]使用python自动识别验证码详解

灰度图

[机器视觉]使用python自动识别验证码详解

黑白图

[机器视觉]使用python自动识别验证码详解

按字符切分后的黑白图像

由于字符宽窄有差异,这里我们按字符切分后,有些字符会多出来一部分,有些字符会丢失一部分。比如7多了一笔看起来像个三角形,M少了一竖看起来像N。但只要符号之间有区分度,依然能够准确分类。

提取图像中的特征值

  1. 到了这一步,我们得到的图像都是由单个字符组成的黑白图片(0为黑色像素点,1为白色像素点)。此时,如果我们把图片转为数组,就会得到一个由0,1组成的矩阵,其长宽恰与图片的大小相同, 每一个数字代表一个像素点。
  2. 接下来我们需要考虑如何提取能够区分不同字符的特征值。我们可以直接用图像中的每一个像素点作为一个特征值,也可以汇总图像中共有多少黑色像素点(当然,这样每张图片只能提取一个特征值),还可以按区域汇总图像中的像素点,比如先将图片四等分,汇总四张“子图片”中的像素点,如果觉得特征不够多,还可以继续下分,直至精确到每一个像素点。
  3. 为了使代码更加简洁,我们这里直接汇总、分行列汇总了图像像素点的个数,共提取了1张图片 + 15列 + 18行 ==> 34个特征值。至于按区域汇总的方法,还是等我们有空了单独写一篇帖子。
def capt_inference(capt_per_char):
  """
  提取图像特征

  :require numpy: import numpy as np

  :param capt_per_char: 由单个字符组成的二值型图片
  :return char_features:一个数组,包含 capt_per_char中字符的特征
  """
  char_array = np.array(capt_per_char)

  total_pixels = np.sum(char_array)
  cols_pixels = np.sum(char_array, 0)
  rows_pixels = np.sum(char_array, 1)

  char_features = np.append(cols_pixels, rows_pixels)
  char_features = np.append(total_pixels, char_features)

  return char_features.tolist()

生成训练集

这里我们会将预分类的每张验证码分别读入内存,从它们的图像中提取特征值,从它们的名称中提取验证码所对应的文字(标签),并将特征值与标签分别汇总成列表,注意train_labels中的每个元素是与train_table中的每一行相对应的。

def train():
  """
  将预分类的验证码图片集转化为字符特征训练集

  :require Image: from PIL import Image
  :require os: import os

  :require capt_process(): 图像预处理
  :require capt_inference(): 提取图像特征

  :param:
  :return train_table: 验证码字符特征训练集
  :return train_labels: 验证码字符预分类结果
  """
  files = os.listdir(CAPT_PATH)

  train_table = []
  train_labels = []

  for f in files:
    train_labels += list(f.split("_")[0])

    capt = Image.open(CAPT_PATH + f)
    capt_per_char_list = capt_process(capt)
    for capt_per_char in capt_per_char_list:
      char_features = capt_inference(capt_per_char)
      train_table.append(char_features)

  return train_table, train_labels

定义分类模型

  1. 只要我们提取的特征值具有足够的区分度(能够区分不同字符),理论上我们可以使用任何机器学习的模型建立特征值与标签的相关。
  2. 我尝试过使用knn,svm,Decision Tree,ANN等各种主流机器学习模型对提取的训练数据进行分类,尽管不同模型表现各异,但识别准确率都可以很轻松的达到90%以上。
  3. 这里我们不打算使用复杂的第三方模型完成学习过程,所以我们在这里自己写了一个分类模型。模型实现的nnc算法是knn的一种,通过比对未知分类的一组特征值测试集与那一组已知分类的特征值训练集最接近,判定测试集的分类情况应该和与其最接近的训练集的分类情况训练集标签相同。
  4. 当然,我们也可以稍加改造直接实现knn算法,找到3组、5组或k组与未知分类的特征向量最接近的训练集中的特征向量,并通过票选与其对应的标签,预测未知分类的特征向量在大概率上应该属于哪一类。
def nnc(train_table, test_vec, train_labels):
  """
  Nearest Neighbour Classification(近邻分类法),
  根据已知特征矩阵的分类情况,预测未分类的特征向量所属类别

  :require numpy: import numpy as np

  :param train_table: 预分类的特征矩阵
  :param test_vec: 特征向量, 长度必须与矩阵的列数相等
  :param labels: 特征矩阵的类别向量
  :return : 预测特征向量所属的类别 
  """

  dist_mat = np.square(np.subtract(train_table, test_vec))
  dist_vec = np.sum(dist_mat, axis = 1)
  pos = np.argmin(dist_vec)

  return train_labels[pos]

 测试模型分类效果

最后,我们需要测试我们的理论是否有效,通过调用test()方法,我们会先从网站获取验证码图像,对图像进行处理、特征提取,然后调用nnc()方法对提取到的四组特征值做近邻分类,分别得到验证码中的四个字符。最后将验证码图像和识别到的字符传出,方便我们比对识别结果。

def test():
  """
  测试模型分类效果

  :require Image: from PIL import Image

  :require capt_fetch(): 从nbsc网站获取验证码
  :require capt_process(): 图像预处理
  :require capt_inference(): 提取图像特征
  :train_table, train_labels: train_table, train_labels = train()

  :param:
  :return capt: 验证码图片
  :return test_labels: 验证码识别结果
  """

  test_labels = []

  capt = capt_fetch()
  capt_per_char_list = capt_process(capt)
  for capt_per_char in capt_per_char_list:
    char_features = capt_inference(capt_per_char)
    label = nnc(train_table, char_features, train_labels)
    test_labels.append(label)

  test_labels = "".join(test_labels)

  return capt, test_labels

训练数据,识别验证码

  1. 方法具备,接下来就是我们实践的环节了。
  2. 首先,我们需要建立一个机器学习库,即一个预分类的验证码图片集。这里我们仅仅获取了120张验证码作为训练集,相比tensorflow动辄成千上万次的迭代,我们建立模型所需的样本量非常之少。当然,这也要感谢我们使用的nnc算法并不需要十分庞大的训练集支持,才使得我们能够节省很多预分类时人工识别验证码的精力。
  3. 接下来,我们会调用train()方法生成训练集和训练集标签,这两个数组会被test()方法用到,但我们把这两个数组存储在全局变量里,所以不需要特意传递给test()方法。
# 下载120张图片到本地
for i in range(120):
  capt_download()

# 模型的训练与测试
train_table, train_labels = train()
test_capt, test_labels = test()

最后我们调用test()方法验证我们的理论是否成立,识别效果如下:

获取到的验证码

[机器视觉]使用python自动识别验证码详解

识别结果

[机器视觉]使用python自动识别验证码详解

结语

至此,我们通过机器视觉识别验证码的任务算是完成了。至于正确率,大概每10张验证码,40各字符中会预测失误一个字符。这已经比较接近我们人类的识别准确率了,当然,我们还可以通过建立起更庞大的学习库,使用knn或更复杂的模型,使用卷积核处理图片等方式,使识别准确率更高。

当然,我们这里所用的方法,只适用于识别比较简单的验证码。对于更加复杂的验证码(如下图),以上方法是不起作用的,但这并不代表这样的验证码不能通过机器视觉进行识别。

[机器视觉]使用python自动识别验证码详解

我们已经看到,随着机器视觉的发展,通过传统的验证码来区分人机已经越来越难了。当然,为了网络的安全性,我们也可使用更复杂的验证码,或者新型的验证方式,比如拖动滑块、短信验证、扫码登陆等。

以上所述是小编给大家介绍的python自动识别验证码详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
python模拟新浪微博登陆功能(新浪微博爬虫)
Dec 24 Python
Python中使用PyQt把网页转换成PDF操作代码实例
Apr 23 Python
详解Python list 与 NumPy.ndarry 切片之间的对比
Jul 24 Python
使用python 和 lint 删除项目无用资源的方法
Dec 20 Python
Python爬取数据并写入MySQL数据库的实例
Jun 21 Python
Python装饰器基础概念与用法详解
Dec 22 Python
Python基础学习之类与实例基本用法与注意事项详解
Jun 17 Python
python 实现识别图片上的数字
Jul 30 Python
Python如何省略括号方法详解
Mar 21 Python
基于Django signals 信号作用及用法详解
Mar 28 Python
python Cartopy的基础使用详解
Nov 01 Python
如何编写python的daemon程序
Jan 07 Python
Python redis操作实例分析【连接、管道、发布和订阅等】
May 16 #Python
Python操作redis实例小结【String、Hash、List、Set等】
May 16 #Python
Python 实现数据结构中的的栈队列
May 16 #Python
Python 一键制作微信好友图片墙的方法
May 16 #Python
Python实现京东秒杀功能代码
May 16 #Python
使用Python进行体育竞技分析(预测球队成绩)
May 16 #Python
Python异步操作MySQL示例【使用aiomysql】
May 16 #Python
You might like
php输出指定时间以前时间格式的方法
2015/03/21 PHP
PHP读取Excel类文件
2017/05/15 PHP
PHP使用递归按层级查找数据的方法
2019/11/10 PHP
Javascript代码混淆综合解决方案-Javascript在线混淆器
2006/12/18 Javascript
自己的js工具 Cookie 封装
2009/08/21 Javascript
iframe 异步加载技术及性能分析
2011/07/19 Javascript
js中arguments,caller,callee,apply的用法小结
2014/01/28 Javascript
jQuery实现table中的tr上下移动并保持序号不变的实例代码
2016/07/11 Javascript
JS实现简易的图片拖拽排序实例代码
2017/06/09 Javascript
Vue 多层组件嵌套二种实现方式(测试实例)
2017/09/08 Javascript
javascript删除数组元素的七个方法示例
2019/09/09 Javascript
如何配置vue.config.js 处理static文件夹下的静态文件
2020/06/19 Javascript
vue动态设置页面title的方法实例
2020/08/23 Javascript
vue在图片上传的时候压缩图片
2020/11/18 Vue.js
ES2020让代码更优美的运算符 (?.) (??)
2021/01/04 Javascript
[56:38]DOTA2-DPC中国联赛正赛Aster vs Magma BO3 第一场 3月5日
2021/03/11 DOTA
python numpy函数中的linspace创建等差数列详解
2017/10/13 Python
Python二叉树的遍历操作示例【前序遍历,中序遍历,后序遍历,层序遍历】
2018/12/24 Python
Python中使用遍历在列表中添加字典遇到的坑
2019/02/27 Python
Python json模块与jsonpath模块区别详解
2020/03/05 Python
jupyter notebook tensorflow打印device信息实例
2020/04/20 Python
Anaconda的安装及其环境变量的配置详解
2020/04/22 Python
Python 远程开关机的方法
2020/11/18 Python
pytorch 中forward 的用法与解释说明
2021/02/26 Python
介绍一下Python中webbrowser的用法
2013/05/07 面试题
Java语言程序设计测试题改错题部分
2014/07/22 面试题
毕业生教师求职信
2013/10/20 职场文书
关于爱情的广播稿
2014/01/16 职场文书
关于打架的检讨书
2014/01/17 职场文书
竞聘上岗演讲稿
2014/05/16 职场文书
2014年党员评议表自我评价
2014/09/27 职场文书
2015年施工员工作总结范文
2015/04/20 职场文书
2015年档案室工作总结
2015/05/23 职场文书
浅谈MySQL中的六种日志
2022/03/23 MySQL
Python OpenCV实现图形检测示例详解
2022/04/08 Python
Python如何用re模块实现简易tokenizer
2022/05/02 Python