使用 Tesseract 识别字符验证码

这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!

依赖

依赖的安装

字符验证码识别的一般套路

使用 python 进行预处理

引入 pillow

from PIL import Image

载入图片

image = Image.open('CAPTCHA.png')

灰度

imgry = image.convert('L')

二值化

def get_bin_table(threshold=128):
    '''获取灰度转二值的映射table,0表示黑色,1表示白色'''
    table = []
    for i in range(256):
        if i < threshold:
            table.append(0)
        else:
            table.append(1)
    return table
binary = imgry.point(get_bin_table(), '1')

降噪

def sum_9_region_new(img, x, y):
    '''确定噪点 '''
    cur_pixel = img.getpixel((x, y)) # 当前像素点的值
    width = img.width
    height = img.height
  
    if cur_pixel == 1: # 如果当前点为白色区域,则不统计邻域值
        return 0
  
    # 因当前图片的四周都有黑点,所以周围的黑点可以去除
    if y < 3: # 本例中,前两行的黑点都可以去除
        return 1
    elif y > height - 3: # 最下面两行
        return 1
    else: # y不在边界
        if x < 3: # 前两列
            return 1
        elif x == width - 1: # 右边非顶点
            return 1
        else: # 具备9领域条件的
            sum = img.getpixel((x - 1, y - 1)) \
                 + img.getpixel((x - 1, y)) \
                 + img.getpixel((x - 1, y + 1)) \
                 + img.getpixel((x, y - 1)) \
                 + cur_pixel \
                 + img.getpixel((x, y + 1)) \
                 + img.getpixel((x + 1, y - 1)) \
                 + img.getpixel((x + 1, y)) \
                 + img.getpixel((x + 1, y + 1))
            return 9 - sum
  
def collect_noise_point(img):
    '''收集所有的噪点'''
    noise_point_list = []
    for x in range(img.width):
        for y in range(img.height):
            res_9 = sum_9_region_new(img, x, y)
            if (0 < res_9 < 3) and img.getpixel((x, y)) == 0: # 找到孤立点
                pos = (x, y)
                noise_point_list.append(pos)
    return noise_point_list
  
def remove_noise_pixel(img, noise_point_list):
    '''根据噪点的位置信息,消除二值图片的黑点噪声'''
    for item in noise_point_list:
        img.putpixel((item[0], item[1]), 1)

noise_point_list = collect_noise_point(binary)
remove_noise_pixel(binary, noise_point_list)

分割字符 和 归一化 都是为了方便识别,大多数情况下 分割字符 和 归一化 都是难点, 这里就直接交给 tesseract 识别了。

使用 tesseract 识别

保存预处理的图片

image_path = 'pre.png'
binary.save(image_path)

调用 tesseract 命令识别

cmd = 'tesseract ' + image_path  + ' stdout -l osd --psm 7 digits'
res = os.popen(cmd)
print(res.buffer.read().decode('utf-8'))

命令参数解释

这是上文出现的 tesseract 命令

tesseract image_path stdout -l osd --psm 7 digits

可以通过这两个命令来查看命令行的帮助

tesseract --help
tesseract --help-extra

限定要识别的文字

  1. 找到 tesseract 的安装目录
  2. 找到 安装目录\tessdata\configs
  3. 在这里新建一个名为 digits_new 的文件,并写入以下内容
    tessedit_char_whitelist 0123456789abcdefghijklnmopqrstuvwsyz
    
  4. 表示只识别 0-9 a-z 这 36 个字符,可以参考这个目录 安装目录\tessdata\configs 下的其它文件的写法
    • 例如上文提及的命令里的 digits
  5. 在命令行里这样使用
    tesseract image_path stdout -l osd --psm 7 digits_new
    

限定要识别的文字 能有效提高识别的准确率。

字库训练

完整的 python 代码

from PIL import Image

image = Image.open('CAPTCHA.png')

imgry = image.convert('L')

def get_bin_table(threshold=128):
    '''获取灰度转二值的映射table,0表示黑色,1表示白色'''
    table = []
    for i in range(256):
        if i < threshold:
            table.append(0)
        else:
            table.append(1)
    return table
binary = imgry.point(get_bin_table(), '1')

def sum_9_region_new(img, x, y):
    '''确定噪点 '''
    cur_pixel = img.getpixel((x, y)) # 当前像素点的值
    width = img.width
    height = img.height
  
    if cur_pixel == 1: # 如果当前点为白色区域,则不统计邻域值
        return 0
  
    # 因当前图片的四周都有黑点,所以周围的黑点可以去除
    if y < 3: # 本例中,前两行的黑点都可以去除
        return 1
    elif y > height - 3: # 最下面两行
        return 1
    else: # y不在边界
        if x < 3: # 前两列
            return 1
        elif x == width - 1: # 右边非顶点
            return 1
        else: # 具备9领域条件的
            sum = img.getpixel((x - 1, y - 1)) \
                 + img.getpixel((x - 1, y)) \
                 + img.getpixel((x - 1, y + 1)) \
                 + img.getpixel((x, y - 1)) \
                 + cur_pixel \
                 + img.getpixel((x, y + 1)) \
                 + img.getpixel((x + 1, y - 1)) \
                 + img.getpixel((x + 1, y)) \
                 + img.getpixel((x + 1, y + 1))
            return 9 - sum
  
def collect_noise_point(img):
    '''收集所有的噪点'''
    noise_point_list = []
    for x in range(img.width):
        for y in range(img.height):
            res_9 = sum_9_region_new(img, x, y)
            if (0 < res_9 < 3) and img.getpixel((x, y)) == 0: # 找到孤立点
                pos = (x, y)
                noise_point_list.append(pos)
    return noise_point_list
  
def remove_noise_pixel(img, noise_point_list):
    '''根据噪点的位置信息,消除二值图片的黑点噪声'''
    for item in noise_point_list:
        img.putpixel((item[0], item[1]), 1)

noise_point_list = collect_noise_point(binary)
remove_noise_pixel(binary, noise_point_list)

cmd = 'tesseract ' + image_path  + ' stdout -l osd --psm 7 digits'
res = os.popen(cmd)
print(res.buffer.read().decode('utf-8'))