Python机器学习的简单验证码识别(基于SVM)

验证码识别是一个适合入门机器学习的项目,本人经过几天学习,做了一套验证码识别,分享给大家:
这一批验证码较为简单,是基本的数字,没有字母,有字母方法一样 就是建立训练库的时候麻烦点.
首先我们有以下这几个主要的库:

import urllib.request
import pytesseract
from PIL import Image
import time
import cv2
import os
from svmutil import *
from sklearn.cross_validation import train_test_split
from fnmatch import fnmatch

OpenCV 用于图片一些处理 比如灰度处理和二值化
pytesseract 用于验证码识别,这个需要tesseract的支持,下载地址:http://tesseract-ocr.googlecode.com/files/tesseract-ocr-setup-3.02.02.exe or https://sourceforge.net/projects/tesseract-ocr-alt/files/可能需要更改环境变量
svmutil 是由台湾大学林智仁教授开发的一个简单、易用的SVM模式识别与回归的软件包,下载地址:https://www.csie.ntu.edu.tw/~cjlin/libsvm/, 具体安装方法百度一下.


首先我们从网上拷一份验证码
这是我们得到的验证码

fp=urllib.request.urlopen('Your webpage')#示例网址的验证码图片地址
name="./out_img/1.jpg"
f=open(name,'wb')
f.write(fp.read())#读取验证码图片并保存为1.jpg
f.close()

我们首先分析一下思路,分为以下几个步骤:
1.灰度处理用OpenCV
2.清除边缘噪点
3.线降噪与点降噪
4.切割
5.获得特征
6.学习与预测
其中4 5 6 是非必须的,进行1 2 3并用pytesseract 基本可以解决识别验证码了,有兴趣的可以自己用SVM建立库 进行识别(4 5 6)

1.灰度处理用OpenCV

'''
Author: Fu_Connor
'''
def _get_dynamic_binary_image(img_name):
  filename ='./out_img/' + img_name.split('.')[0] + '-binary.jpg'
  img_name = './out_img' + '/' + img_name
  print('.....' + img_name)
  image = cv2.imread(img_name)
  image2 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度处理
  th1 = cv2.adaptiveThreshold(image2, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
  cv2.imwrite(filename,th1)
  return th1

处理之后的图片这里写图片描述
我们灰度处理的目的是让电脑更方便进一步处理与识别图片,如果图片颜色为彩色 则太过于不方便,处理完之后我们发现主要干扰的地方是右下方 有一大部分噪点,我们下一步就是将这部分噪点去掉.

2.清除边缘噪点
清除边缘噪点的原理非常简单,白色RGB颜色为255,黑色为0,那么我们只需要将右下角的坐标点全部变为255 即可消除.

'''
Author: Fu_Connor
'''
def clear_border(img,img_name):
  filename = './out_img/' + img_name.split('.')[0] + '-clearBorder.jpg'
  h, w = img.shape[:2]
  for y in range(0, w):
    for x in range(0, h):
      if y > w -20 or y < 1:#右方与左方一列
        img[x, y] = 255
      if x > h - 10:#下方
        img[x, y] = 255
  cv2.imwrite(filename,img)
  return img

处理之后的图片:这里写图片描述我们发现处理之后的图片右下角噪点已经消失,其实这个时候让电脑识别也已经可以了,但这样错误率会比较高因为数字之间仍有噪点,我们第三步就是处理中间这些散乱分布的噪点.
3.线降噪与点降噪
点降噪:
点降噪的基本原理是 在一个九宫格内 如果有6个点或以上为白色则认为中间的点为白色,当然具体数目不一定必须为7 你也可以根据自己的验证码进行更改,然后四个顶点和四个周边的线同理.
我放张图大家看一下:
Python机器学习的简单验证码识别(基于SVM)_第1张图片画的比较low 大家凑合理解一下:)
这6个红点中心的那个点颜色为黑色,但因以其中心的九宫格内有6葛白点,那么这个黑点会被认为是噪点进而被消除.个人感觉点降噪的效果要优于线降噪,线降噪的局限性会在下面说.

def interference_point(img,img_name, x = 0, y = 0):
    """
    点降噪
    Autor:Fu_Connor
    """
    filename =  './out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
    cur_pixel = img[x,y]# 当前像素点的值
    height,width = img.shape[:2]

    for y in range(0, width - 1):
      for x in range(0, height - 1):
        if y == 0:  # 第一行
            if x == 0:  # 左上顶点,4邻域
                sum = int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            elif x == height - 1:  # 右上顶点
                sum = int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            else:  # 最上非顶点,6邻域
                sum = int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 3 * 255:
                  img[x, y] = 255
        elif y == width - 1:  # 最下面一行
            if x == 0:  # 左下顶点
                sum = int(cur_pixel) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x, y - 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            elif x == height - 1:  # 右下顶点
                sum = int(cur_pixel) \
                      + int(img[x, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y - 1])

                if sum >= 2 * 255:
                  img[x, y] = 255
            else:  # 最下非顶点,6邻域
                sum = int(cur_pixel) \
                      + int(img[x - 1, y]) \
                      + int(img[x + 1, y]) \
                      + int(img[x, y - 1]) \
                      + int(img[x - 1, y - 1]) \
                      + int(img[x + 1, y - 1])
                if sum >= 3 * 255:
                  img[x, y] = 255
        else:  # y不在边界
            if x == 0:  # 左边非顶点
                sum = int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])

                if sum >= 3 * 255:
                  img[x, y] = 255
            elif x == height - 1:  # 右边非顶点
                sum = int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x - 1, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1])

                if sum >= 3 * 255:
                  img[x, y] = 255
            else:  # 具备9领域条件的
                sum = int(img[x - 1, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1]) \
                      + int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 7 * 255:
                  img[x, y] = 255
    cv2.imwrite(filename,img)
    return img

处理完后的图片:这里写图片描述
线降噪:
线降噪基本原理是 如果一个点的上下左右均为白色则认为这个点是白色,这样有什么用呢,这样可以去除一些细的斜线,我画一张图大家理解一下:
Python机器学习的简单验证码识别(基于SVM)_第2张图片
这四个红点是白色的,那么中间这个点就会认为是白色进而被消除,同理这四个点都可以被消除 ,这里我设置的参数是3个点,也就是说只要上下左右有三个点为白色就会认为这点是白色. 这样有个缺点就是遇到粗的线 和水平的线就凉了(水平的线只能消掉两头).

def interference_line(img, img_name):
  '''
  Autor:Fu_Connor
  线降噪
  '''

  filename =  './out_img/' + img_name.split('.')[0] + '-interferenceline.jpg'
  h, w = img.shape[:2]
  for y in range(1, w - 1):
    for x in range(1, h - 1):
      count = 0
      if img[x, y + 1] > 245:
        count = count + 1
      if img[x, y - 1] > 245:
        count = count + 1
      if img[x - 1, y] > 245:
        count = count + 1
      if img[x + 1, y] > 245:
        count = count + 1
      if count > 2:
        img[x, y] = 255
  cv2.imwrite(filename,img)
  return img

处理完之后的图片:这里写图片描述
这里我们的处理方法是先点降噪后进行线降噪,当然你要根据自己的验证码进行选择.
进行到这里。如果使用pytesseract 就已经结束了,直接调用:

pytesseract.image_to_string()

准确率已经很高了,但是如果想自己建立库,针对这种验证码进行特殊学习了话 就要更深一步了

4.切割
如果想用SVM进行学习,那么就必须要自己进行切割并单独学习每个数字or字母
切割数字的原理很简单,经过观察我们处理后的图片我们发现,每个数字基本宽9,高16个像素,每个间隔为1像素,那么我们就可以每宽9+1个像素切割一次,并且高恒定为16.

这个时候我们要用PIL库的crop了

def get_crop_imgs(img):
    """
    按照图片的特点,进行切割,这个要根据具体的验证码来进行工作.
	Autor:Fu_Connor
    """
    child_img_list = []
    for i in range(4):#4个数字
        x = 0 + i * (9 + 1) 
        y = 3
        child_img = img.crop((x, y, x + 9, y + 16))
        child_img.save('./out_img/cutting/cutting_%s.jpg' %(i))
        child_img_list.append(child_img)

这种方法属于比较直接的,也可以从第一个黑色像素识别作为起点在切割,都是可以的
切割之后:Python机器学习的简单验证码识别(基于SVM)_第3张图片
5.获得特征
获得特征是很简单的,只需要遍历整个图片记录下每行每列有几个黑色(RGB=0)的数目即可,这样作为一个9*16的图片就有25个特征值:
我这里的参量比较多,是因为这部分我没完全写完,不过基本就是这样了,大家稍微改一下就行

'''
Autor:Fu_Connor
'''
def get_feature(address,dir, file):
	adress='./out_img/train_data/'
    f = open('./train.txt', 'w')
    f.write(dir)
    im = Image.open(address + dir + file)
    count = 0
    width, height = im.size
    for i in range(height):
        c = 0
        for j in range(width):
            if im.getpixel((j, i)) == 0: c += 1
        f.write(' %d:%d'%(count, c))
        count += 1
    for i in range(width):
        c = 0
        for j in range(height):
            if im.getpixel((i, j)) == 0: c += 1
        f.write(' %d:%d'%(count, c))
        count += 1
    f.write('\n')

0 0:0 1:3 2:2 3:3 4:1 5:3 6:0 7:4 8:2 9:2 10:2 11:2 12:2 13:0 14:0 15:0 16:0 17:3 18:4 19:3 20:1 21:3 22:3 23:5 24:4
0 0:0 1:0 2:3 3:2 4:3 5:2 6:2 7:1 8:3 9:2 10:2 11:2 12:3 13:4 14:0 15:0 16:0 17:2 18:3 19:6 20:2 21:5 22:2 23:3 24:6
0 0:0 1:2 2:2 3:2 4:2 5:2 6:3 7:2 8:2 9:2 10:2 11:2 12:3 13:0 14:0 15:0 16:0 17:1 18:4 19:6 20:2 21:2 22:1 23:4 24:6
0 0:0 1:2 2:1 3:1 4:1 5:1 6:2 7:2 8:2 9:3 10:2 11:2 12:3 13:1 14:0 15:0 16:0 17:2 18:4 19:6 20:1 21:2 22:2 23:1 24:5
第一列为图片表示的数字,后面为特征,有几张图片就有几行.
6.学习并预测
到这里基本就完成了,我们只需要调用SVM函数学习即可:

def train_test_svm_model():
    y, x = svm_read_problem('./train.txt')
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
    model = svm_train( y_train, x_train)
    svm_save_model('./model_file', model)#保存模型方便使用
    p_label, p_acc, p_val = svm_predict(y_test, x_test, model)

最终使用留出法测试模型,留出的测试数据结果为73%准确率,还是可以用的
下次我们只需要调用SVM即可完成任意图片预测:

model = svm_load_model('./model_file')
y, x = svm_read_problem('./out_img/predict.txt')#predict.txt=需要再获取一次你需要预测的图片的特征.
p_label, p_acc, p_val = svm_predict(y, x, model)

这样我们就大功告成啦,我们看一下结果
SVM该验证码为:6046
Pytesseract识别为:6046
还是可以的,但有些情况下,可能还是没有Pytesseract预测的准确,这可能是因为我们的训练数据不够多造成的,我现在只有100+个训练集,随着训练集的增加 准确率会越来越高.
运行方法:建立一个out_img文件夹,在这个文件夹下面在建立cutting和train-data文件夹,train-data下面放训练集文件及其cutting图片.如图所示:Python机器学习的简单验证码识别(基于SVM)_第4张图片

最终源代码附上:

import urllib.request
import pytesseract
from PIL import Image
import time
import cv2
import os
from svmutil import *
from fnmatch import fnmatch

path = './out_img'
for i in os.listdir(path):
   path_file = os.path.join(path,i)
   if os.path.isfile(path_file):
      os.remove(path_file)

address = './out_img/train_data/'
f1 = open('./train.txt', 'w')
f2 = open('./out_img/predict.txt', 'w')
address2='./out_img/cutting/'

fp=urllib.request.urlopen('http://www.ems.com.cn/servlet/ImageCaptchaServlet')#Verification Code
name="./out_img/1.jpg"
f=open(name,'wb')
f.write(fp.read())#save Verification Code as 1.jpg
f.close()

def clear_border(img,img_name):
  filename = './out_img/' + img_name.split('.')[0] + '-clearBorder.jpg'
  h, w = img.shape[:2]
  for y in range(0, w):
    for x in range(0, h):
      if y > w -20 or y < 1:
        img[x, y] = 255
      if x > h - 10:
        img[x, y] = 255
  cv2.imwrite(filename,img)
  return img

def _get_dynamic_binary_image(img_name):
  filename ='./out_img/' + img_name.split('.')[0] + '-binary.jpg'
  img_name = './out_img' + '/' + img_name
  print('.....' + img_name)
  image = cv2.imread(img_name)
  image2 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  th1 = cv2.adaptiveThreshold(image2, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
  cv2.imwrite(filename,th1)
  return th1

def interference_line(img, img_name):
  filename =  './out_img/' + img_name.split('.')[0] + '-interferenceline.jpg'
  h, w = img.shape[:2]
  # !!!opencv矩阵点是反的
  # img[1,2] 1:图片的高度,2:图片的宽度
  for y in range(1, w - 1):
    for x in range(1, h - 1):
      count = 0
      if img[x, y + 1] > 245:
        count = count + 1
      if img[x, y - 1] > 245:
        count = count + 1
      if img[x - 1, y] > 245:
        count = count + 1
      if img[x + 1, y] > 245:
        count = count + 1
      if count > 2:
        img[x, y] = 255
  cv2.imwrite(filename,img)
  return img

def interference_point(img,img_name, x = 0, y = 0):
    filename =  './out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
    cur_pixel = img[x,y]
    height,width = img.shape[:2]

    for y in range(0, width - 1):
      for x in range(0, height - 1):
        if y == 0:  # first row
            if x == 0:
                sum = int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            elif x == height - 1:
                sum = int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            else:
                sum = int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 3 * 255:
                  img[x, y] = 255
        elif y == width - 1:
            if x == 0:
                sum = int(cur_pixel) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x, y - 1])
                if sum >= 2 * 255:
                  img[x, y] = 255
            elif x == height - 1:
                sum = int(cur_pixel) \
                      + int(img[x, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y - 1])

                if sum >= 2 * 255:
                  img[x, y] = 255
            else:
                sum = int(cur_pixel) \
                      + int(img[x - 1, y]) \
                      + int(img[x + 1, y]) \
                      + int(img[x, y - 1]) \
                      + int(img[x - 1, y - 1]) \
                      + int(img[x + 1, y - 1])
                if sum >= 3 * 255:
                  img[x, y] = 255
        else:
            if x == 0:
                sum = int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])

                if sum >= 3 * 255:
                  img[x, y] = 255
            elif x == height - 1:
                sum = int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x - 1, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1])

                if sum >= 3 * 255:
                  img[x, y] = 255
            else:
                sum = int(img[x - 1, y - 1]) \
                      + int(img[x - 1, y]) \
                      + int(img[x - 1, y + 1]) \
                      + int(img[x, y - 1]) \
                      + int(cur_pixel) \
                      + int(img[x, y + 1]) \
                      + int(img[x + 1, y - 1]) \
                      + int(img[x + 1, y]) \
                      + int(img[x + 1, y + 1])
                if sum >= 7 * 255:
                  img[x, y] = 255
    cv2.imwrite(filename,img)
    return img

def get_crop_imgs(img):
    child_img_list = []
    for i in range(4):
        x = 0 + i * (7 + 4)
        y = 3
        child_img = img.crop((x, y, x + 9, y + 16))
        child_img.save('./out_img/cutting/cutting_%s.jpg' %(i))
        child_img_list.append(child_img)

def get_feature(address,assume,dir, file,f,a):
    # assume is assume predict value
    f.write(assume)
    im = Image.open(address + dir + a + file)
    count = 0
    width, height = im.size
    for i in range(height):
        c = 0
        for j in range(width):
            if im.getpixel((j, i)) == 0: c += 1
        f.write(' %d:%d'%(count, c))
        count += 1
    for i in range(width):
        c = 0
        for j in range(height):
            if im.getpixel((i, j)) == 0: c += 1
        f.write(' %d:%d'%(count, c))
        count += 1
    f.write('\n')

def train_test_svm_model():
    y, x = svm_read_problem('./train.txt')
    '''
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
    model = svm_train( y_train, x_train)
    svm_save_model('./model_file', model)
    p_label, p_acc, p_val = svm_predict(y_test, x_test, model)
    p_label, p_acc, p_val = svm_predict(y, x, model)
    测试模型
    '''
    model = svm_train(y, x)
    svm_save_model('./model_file', model)


def main():
  filedir = './out_img'

  for file in os.listdir(filedir):
    if fnmatch(file, '*.jpg'):
        img_name = file
        im = _get_dynamic_binary_image(img_name)

        im = clear_border(im, img_name)

        im = interference_point(im, img_name)

        interference_line(im, img_name)

        time.sleep(2)
        global image
        image = Image.open('./out_img/1-interferenceline.jpg')
        get_crop_imgs(image)


if __name__ == '__main__':
  main()
  dirs = os.listdir(address)
  for dir in dirs:
      files = os.listdir(address + dir)
      for file in files:
          get_feature(address, dir, dir, file, f1, '/')
  f1.close()
  train_test_svm_model()
  model = svm_load_model('./model_file')
  files = os.listdir(address2)
  for file in files:
      get_feature(address2, '1', '', file, f2, '')
  f2.close()
  y, x = svm_read_problem('./out_img/predict.txt')
  p_label, p_acc, p_val = svm_predict(y, x, model)
  print('SVM identify:', end='')
  for label in p_label:
      print(int(label), end='')

  vcode = pytesseract.image_to_string(image, config='digits')
  print('\nPytesseract identify:%s' % vcode)
  vcode = ''
  for i in range(4):
      try:
          file = './out_img/cutting/cutting_%s.jpg' % (i)
          vcode = vcode + pytesseract.image_to_string(Image.open(file), config='-psm 10 outputbase digits')
      except Exception as err:
          pass
  print('Pytesseract_cutting identify:%s' % vcode)

一个MEP建筑理工男

你可能感兴趣的:(Python,机器学习,验证码,SVM,图像识别)