用 Python 进行简单的验证码识别

探讨验证码识别的基本思路与实现方法。文章通过 Python 的 PIL 库演示了图片加载、灰度处理、二值化及字符转换的完整过程,并提供了包括图片分割、特征提取及相似度比对在内的完整验证码识别代码示例,适用于简单的图形验证码识别场景。

由于模拟登陆的需要,需要做验证码识别

基本原理

简单验证码识别主要原理还是 建立模型 > 去噪点 > 二值化 > 对比 如果是复杂一点的验证码则另论 下面简单介绍简单验证码的识别

大致思路

  • 先打开原始图片 验证码 1 验证码 2 验证码 3
import Image
im = Image.open("temp.jpg")
  • 图片灰度处理
im = im.convert('L')

灰度处理后的效果 验证码灰度后 1 验证码灰度后 2 验证码灰度后 3

  • 根据阀值(threshold)生成表 table 再根据 table 对图片二值化处理
threshold = 140
table = [0 if i < threshold else 1 for i in range(256)]
im = im.point(table, '1')

二值化处理后的效果 验证码二值化后 1 验证码二值化后 2 验证码二值化后 3

  • 将二值化后的图片转为字符串
im_data = ''.join(str(x) for x in list(im.getdata()))

这时候就会得到图片对应的字符串,将字符串与数据库进行比较,就可以了(当然这里的验证码分割后会更好处理)

完整的验证码识别代码

  • 定义图片的一些基本信息(图片的一些基本信息,识别过程中也可以随时调整)
#验证码图片数量
imgNum = 100
#灰度阀值
threshold = 127
table = [0 if i < threshold else 1 for i in range(256)]
#图片信息
info = {
    "offsetWidth": 2,   #宽度偏移
    "offsetHeight": 4,  #高度偏移
    "wordNum": 4,       #字符数量
    "wordWidth": 12,    #字符宽度
    "wordHeight": 12,   #字符高度
    "wordSpace": -2     #字符空隙
}
  • 获取验证码图片
import requests
cookies = requests.get('http://www.domain.com').cookies
for i in range(imgNum):
    img = requests.get('http://www.domain.com', cookies = cookies).content
    with open('code/%s.jpg' % i, 'w') as f:
        f.write(img)
  • 验证码图片分割,分割为单个字符一个图片
import Image
for i in range(imgNum):
    im = Image.open("code/%s.jpg" % i)
    #分割单个字符
    for j in range(info['wordNum']):
        beginX = info['offsetWidth'] + (info['wordWidth'] + info['wordSpace']) * j
        beginY = info['offsetHeight']
        endX = beginX + info['wordWidth']
        endY = beginY + info['wordHeight']
        box = (beginX, beginY, endX, endY)
        im.crop(box).save('part/%s-%s.jpg' % (i,j))

分割后部分图片效果 验证码分割后后 0 验证码分割后后 1 验证码分割后后 2 验证码分割后后 3

图片手动选择

  • 这一步我处理的很蛋痛
  • 这一步的目的就是用后面的代码生成数据库
  • 操作方法:手动选择较好的图片并将图片

手动分类

  • 灰度处理后二值化,然后统计,生成字符数据库
import os
keys = {}
path = "part/"
folders = [x for x in os.listdir(path) if os.path.isdir(path + x)]
for key in folders:
    imgFlag = []
    tmpPath = path + key + '/'
    files = [tmpPath + x for x in os.listdir(tmpPath) if os.path.isfile(tmpPath + x)]
    for img in files:
    im = Image.open(img).convert('L')
    im = im.point(table, '1')
    imgData = ''.join(str(x) for x in list(im.getdata()))
    for i in range(len(imgData)):
        if(len(imgFlag) <= i):
            imgFlag.append([0,0])
        imgFlag[i][int(imgData[i])] += 1
keys[key] = ''.join('0' if flag[0] > flag[1] else '1' for flag in imgFlag)
  • 正常情况下,每个字符对应的值就存放在 keys 里面了,可以输出看看
import json
print json.dumps(keys, indent=2)

输出的结果如下:

{
  "c": "111111111111111111111111111111111111111100001111111000000111110001101111110011111111110011111111110011111111110001111111111000001111111100011111",
  "b": "111111111111110011111111110011111111110000001111110000000111110001100011110011110011110011110011110011110011110001100011110000000111111000001111",
  "m": "111111111111111111111111111111111111111000001100110000000000110001000010110011100111110011100111110011100111110011100111110011100111111111111111",
  "n": "111111111111111111111111111111111111111000000111110000000011110001100011110011110011110011110011110011110011110011110011110011110011111111111111",
  "1": "111110111111111100011111111000011111111000011111111110011111111110011111111110011111111110011111111110011111111110011111111110011111111111111111",
  "3": "111000001111110000000111111111000111111111000111111110001111111110001111111111000111111111100111110111100111100011000111110000001111111000011111",
  "2": "111000011111110000001111100011000111110111100111111111100111111111001111111110001111111100011111111000111111110000111111100000001111110000001111",
  "v": "111111111111111111111111111111111111111111111111110011100111110011100111111001001111111000001111111000001111111100011111111100011111111110111111",
  "x": "111111111111111111111111111111111111111011101111110001000111111000001111111100011111111100011111111100011111111000001111110001000111111011101111",
  "z": "111111111111111111111111111111111111111000001111111000000111111110001111111110001111111100011111111000111111111000111111110000001111111000001111"
}

现在数据库也有了,那么可以进入识别阶段了

  • 验证码识别
import difflib
for i in range(imgNum):
    codes = code = ""
    im = Image.open("code/%s.bmp" % i).convert('L')
    #分割单个字符
    for j in range(info['wordNum']):
        beginX = info['offsetWidth'] + (info['wordWidth'] + info['wordSpace']) * j
        beginY = info['offsetHeight']
        endX = beginX + info['wordWidth']
        endY = beginY + info['wordHeight']
        box = (beginX, beginY, endX, endY)
        imgData = ''.join(['0' if x > threshold else '1' for x in list(im.crop(box).
        getdata())])
        percent = maxPercent=0.0
        #选取相似度最大的值
        for key in keys:
            percent = difflib.SequenceMatcher(None, imgData,keys[key]).ratio()
            if percent > maxPercent:
                maxPercent = percent
                code = key
            codes += code
    print i, codes