Python Tesseract识别验证码

学习记录, 仅供参考


我们在写爬虫的时候经常遇到网页需要登陆的情况, 如果是一次性爬虫的话, 可以用Cookie等方式模拟登录, 但如果要持续性运行的爬虫, 就要考虑自动登录, 而在登录时, 验证码是最常遇到, 需要处理的问题.
tesseract是一款开源的OCR识别引擎, 我们也可以对它进行不断地训练, 提高识别率. 在验证码处理中, 我们就将利用它进行验证码识别.

准备工作

安装python
tesseract源码地址: https://github.com/tesseract-ocr/tesseract
tesseract安装: https://github.com/tesseract-ocr/tesseract/wiki
jTessBoxEditor下载: https://sourceforge.net/projects/vietocr/files/jTessBoxEditor/
jTessBoxEditor是tesseract训练工具, 需要安装Java8环境

tesseract初试

登录页面: http://www.jkpan.com/account.php?action=login

以上就是我们需要登录的页面, 非常简单, 现在我们就要用tesseract来识别图中的验证码.
将验证码下载到本地, 我们尝试直接使用tesseract来识别图中验证码.
在图片目录执行以下命令:

tesseract imgcode.inc.png output -l eng

imgcode.inc.png 图片文件名
output 输出文件名
-l eng eng是tesseract自带的语言文件, 包括英文+数字+符号

命令执行后会发现当前目录下生成了一个output.txt文件
识别结果
可以看到, 识别结果为ED, 并不是我们需要结果.
识别不准确的原因有几个, 验证码图片中有干扰线, 现在几乎所有图片验证码中都存在干扰线, tesseract的eng语言包差别较大也是很重要的问题.
为了提高识别率, 我们需要对验证码进行降噪处理, 我们也需要对tesseract进行训练, 使用训练后的语言包识别验证码.

下载样本

为了对tesseract进行有针对性的训练, 我们需要从网站上下载一些验证码图片.

import os
from urllib.request import urlretrieve
url = 'http://www.jkpan.com/includes/imgcode.inc.php?verycode_type=2' # 验证码下载地址
def download_jpg(folder, num):
	if not os.path.exists(folder):
		os.makedirs(folder)
	for i in range(num):
		urlretrieve(url, '%s/imgcode-%d.jpg' % (folder, i))
download_jpg('image', 100)
download_jpg('test', 100)

新建tesseract文件夹, 将以上代码保存为 download.py, 执行命令

python download.py

等待程序执行完后, 将会创建两个文件夹, image下有100张图片, 用于训练, test下有100张图片, 用于测试训练结果.
tesseract文件夹
image文件下的图片文件
待训练图片

图片降噪

验证码图片中包含很多干扰线, 干扰线的存在会影响验证码识别, 增加识别难度, 所以我们要尽可能清除图片的干扰源.
通过观察, 发现这个网站验证码的干扰线比较简单, 单个验证码图片内, 所有干扰线的颜色一样, 从左边框开始, 到右边框结束. 针对这点, 我们可以获取到干扰线的颜色, 清除图片内的所有相同颜色, 即可清除干扰线.
清除干扰先后, 验证码的颜色与识别无关, 将对图片进行灰度处理.

import os
from PIL import Image

def clear_save_image(image_path):
	image = Image.open(image_path)
	image = clear_image(image)
	image = image.convert('L') #灰度处理
	file_name = os.path.splitext(os.path.basename(image_path))[0]
	save_path = os.path.join(os.path.dirname(image_path), '../clear_image', file_name + '.tif')
	image.save(save_path) #图片转换成tif保存到clear_image文件夹中

def clear_image(image):
	image = image.convert('RGB')
	width = image.size[0]
	height = image.size[1]
	noise_color = get_noise_color(image)
	for x in range(width):
		for y in  range(height):
			#清除边框和干扰色
			if (x == 0 or y == 0 or x == width - 1 or y == height - 1 
				or image.getpixel((x, y)) == noise_color):
				image.putpixel((x, y), (255, 255, 255))
	return image

def get_noise_color(image):
	for y in range(1, image.size[1] - 1):
		# 获取第2列非白的颜色
		(r, g, b) = image.getpixel((2, y))
		if r < 255 and g < 255 and b < 255:
			return (r, g, b)

if __name__ == '__main__':
	for file_path in os.listdir('image'):
		file_path = os.path.join('image', file_path)
		if os.path.isfile(file_path):
			clear_save_image(file_path)

将代码保存为clear.py到tesseract目录, 执行

python clear.py

等待程序执行完后, 原图片已清除噪点, 并灰度处理, 转换为tif文件保存在clear_image文件夹中
tesseract文件夹内容
clear_image文件夹内容
清除噪点后的图片
噪点清除后, 验证码已经非常清晰, 再使用tesseract 命令对清除噪点后的验证码进行识别.

cd clear_image
tesseract imgcode-14.tif output -l eng

经过对多张图片识别测试后, 可以发现, 清除噪点后, 识别率已经提高了很多, 但还是存在不少错误, 很多数字和字母被识别成符号等问题. 为了继续提高识别率, 我们需要对tesseract进行训练.

tesseract初训练

通过样本图片分析, 我们可以发现, 该网站验证码字符只包含数字1-9和大写字母A-Z, 所以第一步, 我们针对这些字符进行训练, 这样就不会出现字母数字被识别成标点符号.
新建train文件夹, 以后训练文件就保存在该文件夹下
tesseract 文件夹
安装 jTessBoxEditor
tesseract 有很多训练工具, 我选了jTessBoxEditor, 其它的请自行尝试. jTessBoxEditor依赖Java8环境, 所以还需额外安装Java8环境
开始训练
创建chars.txt, 填入验证码所有可能存在的字符, 使用这些字符生成tif图片
charts.txt
运行jTessBoxEditor.jar, 选择TIFF/Box Generator, 选择chars.txt, 输入自定义语言包名, 点击Generate
生成字体图片
运行后, 回到train目录, 可以看到生成了3个文件
在这里插入图片描述
tif文件, 是字符图片, 包含chars.txt里的所有字符

tif文件命名格式[lang].[fontname].exp[num].tif
其中lang为自定义训练语言的名称, 也是执行tesseract命名是 -l 后指定的名称
fontname问字体名称,
exp固定, num为编号, 如果有多个文件则往上增加, 如果有大量样本文件, 可以分批训练

box文件, 是字符, 图片的映射文件, 标识每个字符在图片中的坐标, 文件名称与tif文件相同
font_properties文件, 记录图片中字符使用的字体, 打开font_properties查看其内容为arial 0 0 0 0 0

内容格式 <fontname> <italic> <bold> <fixed> <serif> <fraktur>
fontname为字体名称, italic为斜体, bold为黑体字, fixed为默认字体, serif为衬线字体, fraktur德文黑字
1和0代表有和无,精细区分时可使用

现在, 我们使用jTessBoxEditor打开jkpan.arial.exp0.tif, 看下box文件是字符, 图片的映射
点击Box Editor > Open选择jkpan.arial.exp0.tif
字符坐标映射
现在tif文件, box文件, font_properties文件都已生成, 可以开始训练了, 你可以自己输入命令的方式训练, 也可以使用jTessBoxEditor工具完成训练.
打开jTessBoxEditor > Trainer, 选择对应的文件和目录, 输入自定义语言包名称, 选择Train with Existing Box, 点击Run
在这里插入图片描述
执行完后, 训练数据就已经生成, 打开tessdata文件夹, 其中的traineddata文件, 就是我们自定义的语言包.
在这里插入图片描述
不使用工具, 可以通过直接输入命名方式, 生成以上训练文件

  1. 生成 tr 文件, 命令执行完后会生成jkpan.arial.exp0.tr文件
tesseract jkpan.arial.exp0.tif jkpan.arial.exp0 box.train
  1. 生成unicharset文件
unicharset_extractor jkpan.arial.exp0.box
  1. Clustering: MF Training
mftraining -F jkpan.font_properties -U unicharset -O jkpan.unicharset jkpan.arial.exp0.tr
  1. Clustering: CN Training
cntraining jkpan.arial.exp0.tr
  1. 合并文件
    以上命名执行完后, 会生成shapetable, pffmtable, normproto, inttemp文件, 重命名这些文件, 用[lang].为前缀
    例如这里, 我们将shapetable改为jkpan.shapetable, 修改完后, 开始执行合并操作
combine_tessdata jkpan.

执行完后, 我们可以在train目录下找到一个jkpan.traineddata文件, 这个文件和工具训练执行完后tessdata目录下的traineddata文件是一样的, 你可以直接将这个文件拷贝到Tesseract-OCR安装目录下的tessdata目录下, 使用, 也可以和工具一样新建一个tessdata目录使用, 如果你选择拷贝的方法, 那你只需要使用tesseract命令时通过 -l 参数指定语言即可, 如果你不拷贝, 则需要指定–tessdata-dir参数
现在我们将jkpan.traineddata文件复制到Tesseract-OCR安装目录下的tessdata目录下, 使用tesseract命令来检测一下我们训练的结果

cd clear_image
tesseract imgcode-1.tif output -l jkpan

多选择几个图片测试后, 可以发现, 识别率又进一步提高了, 但还是存在一些问题, 如P识别成F不分, 5识别成S等, 为了进一步提高识别率, 我们现在使用下载的样本图片进行二次训练

tesseract样本训练

样本训练前, 为了方便, 我们需要将clear_image下的样本多个图片合并成一个文件, 这一步, 使用的tif格式的文件, 清除噪点时, 我们已经将原jpg格式的图片转换为了tif, 如果你是自己下的验证码, 需要将jpg格式转换为tif
打开jTessBoxEditor > Tools > Merge TIFF
Tools
选择clear_image下的所有图片, 并将合并后的文件保存到train下, 文件名jkpan.arial.exp1.tif
相比第一次训练exp0, 我们文件名exp1增加1, 每次训练, 我们都可以往上+1
生成对应box文件

cd train
tesseract jkpan.arial.exp1.tif jkpan.arial.exp1 -l jkpan batch.nochop makebox

参数-l jkpan, 用第一次训练的语言文件来生成box文件, 可以提高识别率, 减少我们手工标注的工作量
你也可以使用jTessBoxEditor来生成box文件, 如果你使用jTessBoxEditorg来生成box文件, 最好将tif拷贝临时目录, 生成box文件后, 再将tif和box文件拷贝回train目录, 因为jTessBoxEditorg是批量处理, 会覆盖前面训练生成的box文件
Make Box
box文件生成后, 我们需要开始手工标注图片中每个字符的位置, 识别错误的我们修改正确
打开jTessBoxEditor > Box Editor > Open, 选择jkpan.arial.exp1.tif
Box Editor
需要注意, 这个文件刚才经过合并, 是存在多页, 我们需要通过下方的翻页按钮, 把每页的字符标注出来
如果遇到下方这种, 没有识别出任何文字的图片
在这里插入图片描述
我们可以使用文本编辑器, 打开box文件, 找到相应页位置, box文件每行最后一个数字就等于page-1, 只需要加上一行未识别的图片数据, 例如上图中58页没有识别到任何内容, 只要在jkpan.arial.exp1.box文件加上下图中4行, 然后使用jTessBoxEditor重新打开jkpan.arial.exp1.tif, 继续标注数据即可
Empty Page
完成标注后, 开始重新训练, 训练方式和之前一样, 直接使用jTessBoxEditor工具
训练
训练完成后, 将tessdata目录下的traineddata拷贝到Tesseract-OCR的tessdata目录下
接下来, 使用test目录下的测试样本对训练结果进行测试, 不要用clear_image下的文件测试 这是训练样本

测试训练结果

首先, 我们将test目录下的样本进行重命名, 用正确的验证码做文件名称
test
安装pytesseract

pip install pytesseract

编写测试代码

import os
import pytesseract
from PIL import Image
from clear import clear_image

total = 0
count_success = 0
count_failue = 0

def test_one(image_path):
	image = Image.open(image_path)
	image = clear_image(image) #降噪
	code = pytesseract.image_to_string(image, lang='jkpan') #识别
	code = code.replace(' ', '')
	file_name = os.path.splitext(os.path.basename(image_path))[0]
	global total
	global count_success
	global count_failue
	total += 1
	print('识别文件: %s, 识别结果: %s ' % (file_name, code))
	if code == file_name:
		count_success += 1
	else: 
		count_failue += 1

def print_result():
	ratio = count_success / total * 100
	print('识别验证码个数: ', total)
	print('正确识别个数: ', count_success)
	print('错误识别个数: ', count_failue)
	print('识别成功率: %.2f%%' % ratio)

if __name__ == '__main__':
	for file_path in os.listdir('test'):
		file_path = os.path.join('test', file_path)
		if os.path.isfile(file_path):
			test_one(file_path)
		else:
			print('not file')
	print_result()

把以上保存为 test.py, 执行

python test.py

程序执行完成后, 就可以看到我们的训练结果
训练结果描述
这个识别率不一定准确, 因为我们的测试样本很少.
接下来只要下载更多训练样本和测试样本, 不断进行样本训练, 提高识别率, 直到达到一个满意的结果.

  • 12
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值