[机器视觉]使用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实现提取百度搜索结果的方法
May 19 Python
Python实现栈的方法
May 26 Python
使用Python多线程爬虫爬取电影天堂资源
Sep 23 Python
Python使用正则表达式过滤或替换HTML标签的方法详解
Sep 25 Python
python实现C4.5决策树算法
Aug 29 Python
详解Python是如何实现issubclass的
Jul 24 Python
Python中断多重循环的思路总结
Oct 04 Python
Python 3.8正式发布重要新功能一览
Oct 17 Python
win10下安装Anaconda的教程(python环境+jupyter_notebook)
Oct 23 Python
使用python matplotlib 画图导入到word中如何保证分辨率
Apr 16 Python
利用python如何实现猫捉老鼠小游戏
Dec 04 Python
Python基础之操作MySQL数据库
May 06 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
openPNE常用方法分享
2011/11/29 PHP
解析php dirname()与__FILE__常量的应用
2013/06/24 PHP
php实现水仙花数示例分享
2014/04/03 PHP
PHP简单实现断点续传下载的方法
2015/09/25 PHP
laravel中的错误与日志用法详解
2016/07/26 PHP
JavaScript实现统计文本框Textarea字数增强用户体验
2012/12/21 Javascript
JS实现悬浮移动窗口(悬浮广告)的特效
2013/03/12 Javascript
jquery+ajax+C#实现无刷新操作数据库数据的简单实例
2014/02/08 Javascript
纯js实现遮罩层效果原理分析
2014/05/27 Javascript
JavaScript极简入门教程(三):数组
2014/10/25 Javascript
jquery-mobile基础属性与用法详解
2016/11/23 Javascript
详解webpack babel的配置
2018/01/09 Javascript
详解js访问对象的属性和方法
2018/10/25 Javascript
vue在App.vue文件中监听路由变化刷新页面操作
2020/08/14 Javascript
vue点击Dashboard不同内容 跳转到同一表格的实例
2020/11/13 Javascript
python 读写txt文件 json文件的实现方法
2016/10/22 Python
python3制作捧腹网段子页爬虫
2017/02/12 Python
详解Python中的静态方法与类成员方法
2017/02/28 Python
Python中easy_install 和 pip 的安装及使用
2017/06/05 Python
python tornado修改log输出方式
2019/11/18 Python
Python实时监控网站浏览记录实现过程详解
2020/07/14 Python
如何快速一次性卸载所有python包(第三方库)呢
2020/10/20 Python
详解利用python识别图片中的条码(pyzbar)及条码图片矫正和增强
2020/11/17 Python
scrapy实践之翻页爬取的实现
2021/01/05 Python
涂鸦板简单实现 Html5编写属于自己的画画板
2016/07/05 HTML / CSS
关于iframe跨域使用postMessage的实现
2019/10/29 HTML / CSS
Smashbox英国官网:美国知名彩妆品牌
2017/11/13 全球购物
美国在线旅行社:Crystal Travel
2018/09/11 全球购物
北京RT科技有限公司.net工程师面试题
2013/02/15 面试题
聚美优品励志广告词
2014/03/14 职场文书
求职意向书
2014/07/29 职场文书
租车协议书
2015/01/27 职场文书
会计工作能力自我评价
2015/03/05 职场文书
行政撤诉申请书
2015/05/18 职场文书
关于社会实践的心得体会(2016最新版)
2016/01/25 职场文书
《实心球》教学反思
2016/02/23 职场文书