python爬虫学习:验证码之机器学习

                                                                                                                                  上文学习了?OCR?破解识别验证码,但是还是发现识别的精度不高,因此针对这个问题本文利用机器学习的方法去破解验证码。

                                                                                                                                  本文所用的机器学习的方法为?余弦相似度?,重点的思想是将图片的每一个像素点作为一个坐标点,构造成一个很长的向量。例如,假设某一张图片由200个像素点组成,每个像素点都以?RGB?颜色的值来表示,其取值范围为?0-255?,利用该图片的向量和训练样本中的样本做?余弦相似对比?,如果夹角越小值越大,也就是说两条向量越相似,则图片和训练样本越相似,那么就可以识别出这张图片所代表的验证码了,原理不难,下面是代码实现。

                                                                                                                                  新建?3?个文件夹,分别命名为:iconset?、?jpg?、?py?。iconset?文件夹用于存放训练样本,训练样本下载:

                                                                                                                                  iconset.rar

                                                                                                                                  jpg?文件夹用于存放需要识别的验证码,本文所用的?验证码?为:

                                                                                                                                  '''
                                                                                                                                  遇到python不懂的问题,可以加Python学习交流群:1004391443一起学习交流,群文件还有零基础入门的学习资料
                                                                                                                                  '''

                                                                                                                                  py?文件夹用于存放代码。读取图片出来,得到图像的统计直方图:

                                                                                                                                  # !/usr/bin/python3.4
                                                                                                                                  # -*- coding: utf-8 -*-
                                                                                                                                  
                                                                                                                                  from PIL import Image
                                                                                                                                  
                                                                                                                                  im = Image.open("../jpg/captcha.gif")
                                                                                                                                  his = im.histogram()
                                                                                                                                  

                                                                                                                                  返回的?his?为统计直方图,其类型是一个数组,长度为255,his[0]?代表?RGB?颜色?0?的数量有多少,例如该图例如最后一个元素为625,即255(代表的是白色)最多。将其统计直方图和?RGB?颜色的值构造成字典,并且按照由高到低排序:

                                                                                                                                  values = {}
                                                                                                                                  for i in range(0, 256):
                                                                                                                                      values[i] = his[i]
                                                                                                                                  
                                                                                                                                  temp = sorted(values.items(), key=lambda x: x[1], reverse=True)
                                                                                                                                  print(temp)
                                                                                                                                  

                                                                                                                                  可以看到颜色?255?最多,也就是白色最多,其次是?212?和?220。然后生成一张和原长宽相等的白底的空白图片:

                                                                                                                                  # 获取图片大小,生成一张白底255的图片
                                                                                                                                  im2 = Image.new("P", im.size, 255)
                                                                                                                                  

                                                                                                                                  将统计直方图的颜色依次添加到白底图片中:

                                                                                                                                  # 获得y坐标
                                                                                                                                  for y in range(im.size[1]):
                                                                                                                                      # 获得y坐标
                                                                                                                                      for x in range(im.size[0]):
                                                                                                                                  
                                                                                                                                          # 获得坐标(x,y)的RGB值
                                                                                                                                          pix = im.getpixel((x, y))
                                                                                                                                  
                                                                                                                                          # 这些是要得到的数字
                                                                                                                                          if pix == 212:
                                                                                                                                              # 将黑色0填充到im2中
                                                                                                                                              im2.putpixel((x, y), 0)
                                                                                                                                  im2.show(1)
                                                                                                                                  

                                                                                                                                  多次重复上面的操作,可以发现?pix?的值为220和227的时候能打印出完整的验证码出来:

                                                                                                                                  其实如果对于?RGB?颜色了解的人,一眼就可以看出验证码包含的红色最多,所以?pix?直接可以等于:

                                                                                                                                  # 227红色
                                                                                                                                  if pix == 227:
                                                                                                                                  

                                                                                                                                  但是现实的图片不完整,观看统计直方图可以知道?220?非常多,也就是灰色很多,所以写成:

                                                                                                                                  # 220灰色,227红色
                                                                                                                                  if pix == 220 or pix == 227:
                                                                                                                                  

                                                                                                                                  找到了颜色,然后将每一个数字字母按照颜色切割出来,因为得到的?im2?这张新的图片是白底黑字,所以只要不是白色就可以将其切割下来:

                                                                                                                                  inletter = False
                                                                                                                                  foundletter = False
                                                                                                                                  start = 0
                                                                                                                                  end = 0
                                                                                                                                  
                                                                                                                                  letters = []
                                                                                                                                  
                                                                                                                                  for x in range(im2.size[0]):
                                                                                                                                      for y in range(im2.size[1]):
                                                                                                                                          pix = im2.getpixel((x, y))
                                                                                                                                          if pix != 255:
                                                                                                                                              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
                                                                                                                                  

                                                                                                                                  得到的?letters?为:

                                                                                                                                  [(6, 14), (15, 25), (27, 35), (37, 46), (48, 56), (57, 67)]
                                                                                                                                  

                                                                                                                                  即可以将图片切割成6份,第一份是按照?x=6?到?x=14?纵向切割成一个条状,其他同理。

                                                                                                                                  现在需要复习余弦相似度的公式:

                                                                                                                                  将公式编写成代码:

                                                                                                                                  # 夹角公式
                                                                                                                                  import math
                                                                                                                                  
                                                                                                                                  class VectorCompare:
                                                                                                                                      # 计算矢量大小
                                                                                                                                      # 计算平方和
                                                                                                                                      def magnitude(self, concordance):
                                                                                                                                          total = 0
                                                                                                                                          # concordance.iteritems:报错'dict' object has no attribute 'iteritems'
                                                                                                                                          # concordance.items()
                                                                                                                                          for word, count in concordance.items():
                                                                                                                                              total += count ** 2
                                                                                                                                          return math.sqrt(total)
                                                                                                                                  
                                                                                                                                      # 计算矢量之间的 cos 值
                                                                                                                                      def relation(self, concordance1, concordance2):
                                                                                                                                          topvalue = 0
                                                                                                                                          for word, count in concordance1.items():
                                                                                                                                              if word in concordance2:
                                                                                                                                                  # 计算相乘的和
                                                                                                                                                  topvalue += count * concordance2[word]
                                                                                                                                          return topvalue / (self.magnitude(concordance1) * self.magnitude(concordance2))
                                                                                                                                  

                                                                                                                                  将图片按照每个像素点构造成一个向量:

                                                                                                                                  # 将图片转换为矢量
                                                                                                                                  def buildvector(im):
                                                                                                                                      d1 = {}
                                                                                                                                      count = 0
                                                                                                                                      for i in im.getdata():
                                                                                                                                          d1[count] = i
                                                                                                                                          count += 1
                                                                                                                                      return d1
                                                                                                                                  

                                                                                                                                  读取训练集,将训练集的图片变成向量:

                                                                                                                                  v = VectorCompare()
                                                                                                                                  iconset = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
                                                                                                                                             'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
                                                                                                                                  import os
                                                                                                                                  
                                                                                                                                  imageset = []
                                                                                                                                  for letter in iconset:
                                                                                                                                      for img in os.listdir('../iconset/%s/' % (letter)):
                                                                                                                                          temp = []
                                                                                                                                          if img:
                                                                                                                                              temp.append(buildvector(Image.open("../iconset/%s/%s" % (letter, img))))
                                                                                                                                          imageset.append({letter: temp})
                                                                                                                                  

                                                                                                                                  训练识别:

                                                                                                                                  # 开始破解训练
                                                                                                                                  count = 0
                                                                                                                                  for letter in letters:
                                                                                                                                      # (切割的起始横坐标,起始纵坐标,切割的宽度,切割的高度)
                                                                                                                                      im3 = im2.crop((letter[0], 0, letter[1], im2.size[1]))
                                                                                                                                  
                                                                                                                                      guess = []
                                                                                                                                      # 将切割得到的验证码小片段与每个训练片段进行比较
                                                                                                                                      for image in imageset:
                                                                                                                                          # image.iteritems:报错'dict' object has no attribute 'iteritems'
                                                                                                                                          # 改成image.items()
                                                                                                                                          for x, y in image.items():
                                                                                                                                              if len(y) != 0:
                                                                                                                                                  # y[0]为训练集里面的字母图片,即正确的图片
                                                                                                                                                  # buildvector(im3))为切割出来的字母切片,用来和y[0]进行夹角比对
                                                                                                                                                  # x为iconset
                                                                                                                                                  # x依次显示为0,1,2,3,。。。,x,y,z
                                                                                                                                                  guess.append((v.relation(y[0], buildvector(im3)), x))
                                                                                                                                  
                                                                                                                                      # 排序选出夹角最小的(即cos值最大)的向量,夹角越小则越接近重合,匹配越接近
                                                                                                                                      guess.sort(reverse=True)
                                                                                                                                      print("", guess[0])
                                                                                                                                  
                                                                                                                                      count += 1
                                                                                                                                  

                                                                                                                                  得到结果如下:

                                                                                                                                   (0.9637681159420289, '7')
                                                                                                                                   (0.96234028545977, 's')
                                                                                                                                   (0.9286884286888929, '9')
                                                                                                                                   (0.9835037060984447, 't')
                                                                                                                                   (0.9675116507250627, '9')
                                                                                                                                   (0.9698971168877263, 'j')
                                                                                                                                  

                                                                                                                                  完美识别!于识别的方法其实有监督的学习几乎都可以做到,例如?KNN?或者?决策树?等。

                                                                                                                                  展开阅读全文

                                                                                                                                  没有更多推荐了,返回首页