训练自己的 Tesseract LSTM模型用于识别验证码

Posted querw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了训练自己的 Tesseract LSTM模型用于识别验证码相关的知识,希望对你有一定的参考价值。

训练自己的 Tesseract LSTM模型用于识别验证码
by 阙荣文 2022.12.12

Github 源码

Tesseract-OCR 官方仓库包含的训练数据直接用于识别验证码通常效果并不好,因为验证码字体往往会带有一定程度的扭曲,有必要训练自己的模型.
根据我在网上找到的资料,训练模型的方法大约有以下几种:
a. 参考 https://www.cnblogs.com/nayitian/p/15240143.html,可在 windows 平台下操作,较为繁琐
b. 使用 Tesseract-OCR 官方提供的基于 make 的训练工具 tesstrain,缺点是只支持 Linux 平台,这也是本文将要介绍的方法(以 Ubuntu 环境为例)
c. 改造官方训练工具使之可以在 windows 平台上运行,请参考 https://livezingy.com/train-tesseract-lstm-with-make-on-windows/

0. 安装
a. 安装 Tesseract-OCR 引擎, 可以参考 https://tesseract-ocr.github.io/tessdoc/Home.html从官方 GitHub 仓库源码安装或者从 Ubuntu 软件仓库直接安装编译好的版本 "sudo apt install tesseract-ocr"
b. 安装 tesstrain 训练工具, git clone https://github.com/tesseract-ocr/tesstrain.git
c. 下载训练数据 https://github.com/tesseract-ocr/tessdata_best 只需下载 eng.traineddata, chi_sim.traineddata, 保存到 tesstrain/data/tessdata
d. 下载 langdata_lstm, git clone https://github.com/tesseract-ocr/langdata_lstm.git,整个项目很大,下载困难的话可以只下载顶层文件以及 eng, chi_sim 两个子目录, 保存到 tesstrain/data/langdata

1. 准备用于训练的验证码图片
需要收集大量验证码图片,数量越多效果越好.

2. 验证码图片通常带有较多干扰因素,需要预先处理(清洗)
通常包括灰度化,二值化,降噪,字符切分,并存储为 tif 或 png 格式

3. 标注
对于每个输入图片 "a.tif"(假设图片的内容就是字符 "a") tesstrain 要求同目录下存在名为 "a.gt.txt" 的文本文件,且该文件的内容为 "a",这样 tesstrain 就可以建立起图片与内容之间的联系,此过程即通常所说的"标注".

验证码图片内容通常比较简单,我们可以像上述那样用验证码内容作为文件名,比如从网站获取到内容为 "ABCD" 的验证码图片我们就把它存为ABCD.jpg,然后再编写一个 python 脚本根据文件名生成对应的 ABCD.gt.txt 文件,其内容为 "ABCD". 我们约定,如果有相同内容的验证码图片,则按照 ABCD_0.jpg ABCD_1.jpg 依次命名,附录的 python 脚本依赖于这个约定.

我们可以编写一个 python 脚本(参考附录1),批量执行步骤 2, 3. 这样,我们只要收集大量的验证码图片,然后人工根据验证码内容重命名这些文件(这一步人工介入就是"教会"模型的关键),剩下的事可以由 python 脚本处理

4. 选择基础训练数据
我们在 Tesseract-OCR 官方提供的 _best 训练数据的基础上添加验证码字符特征而不是重头训练一个新模型,如果验证码只包含英文字符则选择 eng.traineddata 如果含有中文字符则选择 chi_sim.traineddata

5. 准备输入数据
为自己的模型取个名字,假设为 "abc",把第 3 步中由 python 脚本生成的所有 .tif 和 .gt.txt 文件复制到 tesstrain/data/abc-ground-truth

6. 开始训练
$make training MODEL_NAME=abc START_MODEL=eng TESSDATA=data/tessdata PSM=7
(PSM=7 表示假设输入图片为单行文本,如果输入图片已经过了字符切割,则设置 PSM=10) 

7. 把训练结果 tesstrain/data/abc.traineddata 复制到 tesseract-ocr 目录(/usr/share/tessdata)
运行 $tesseract --list-langs 可以看到我们的模型 "abc" 已经出现在列表中

附录1:
实际应用中,验证码图片各式各样,如何做前期清洗非常见功力,除了通常的灰度化,二值化之外并无一定之规,不过关于如何处理验证码图片网上的文章较多,这里我提供一个 python 脚本实现上述步骤 2, 3 只实现简单清洗和字符切分,仅供参考.

运行前把收集到的验证码图片(jpg 格式),按照步骤 3 人工标注后放在目录 input 中,另建立一个输出目录 output,然后运行 "tesstrain_helper.py -s input output" (选项 -s 可以控制是否切割为单个字符)
即可得到用于训练模型的 .tif 和 .gt.txt 文件

#!/usr/bin/env python3
import sys
import argparse
import os
import os.path
from PIL import Image

APP_NAME = 'TessertrainHelper'
VERSION = '0.0.1'

# 读入输入目录的图片,经过灰度,二值化后以 tif 格式写入输出目录
# 输入图片的文件名为图片内容,同时生成图片内容 .gt.txt 文件也写入输出目录

# 转灰度
def image_grayscale(im):
	return im.convert("L")

# 二值化并反转(白底黑字)
def image_binarization(im, threshold=127):
	im2 = im.copy()
	pixdata = im2.load()
	w, h = im2.size
	for y in range(h):
		for x in range(w):
			if pixdata[x, y] < threshold:
				pixdata[x, y] = 0
			else:
				pixdata[x, y] = 255
	return im2

def get_vertial_pixel_count(pixdata, x, h):
	c = 0
	for y in range(h):
		if pixdata[x, y] == 0:
			c += 1
	return c

def image_split(im, blank_line_max_pixel=1, end_line_width=2):
	sub_ims = []
	pixdata = im.load()
	w, h = im.size
	x0 = 0
	st_find_start = 0
	st_find_end = 1
	st = st_find_start
	end_line_count = 0
	for x in range(w):
		if st == st_find_start:
			# 找到字符的开始的 x 坐标(在此 x 坐标下 h 个像素至少有 1 个点有效)
			if get_vertial_pixel_count(pixdata, x, h) >= blank_line_max_pixel:
				st = st_find_end
				end_line_count = 0
		elif st == st_find_end:
			# 找到字符的结束的 x 坐标(连续 N 个像素宽度的竖线都没有有效点)
			if get_vertial_pixel_count(pixdata, x, h) >= blank_line_max_pixel:
				end_line_count = 0
			else:
				end_line_count += 1
				if end_line_count >= end_line_width:
					st = st_find_start
					sub_ims.append(im.crop((x0, 0, x, h)))
					x0 = x
		else:
			assert 0, f'unexepct state: st'
	
	# 最后一个字符
	if st == st_find_end:
		sub_ims.append(im.crop((x0, 0, w, h)))

	return sub_ims

if __name__ == "__main__":
	# commander line args
	arg_parser = argparse.ArgumentParser(
		prog=APP_NAME,
		description='generate .tif and .gt.txt from input images',
		epilog=f'APP_NAME vVERSION (C) powered by Que\\'s C++ Studio'
	)
	arg_parser.add_argument("input_dir", help=".jpg or .png images input directory", nargs=1)
	arg_parser.add_argument("output_dir", help=".tif and .gt.txt output directory", nargs=1)
	arg_parser.add_argument("-n", "--dry-run", help="do not generate output file", action="store_true")
	arg_parser.add_argument("-s", "--split", help="split image into single characters", action="store_true")
	args = arg_parser.parse_args()

	print(f'Welcome to APP_NAME vVERSION by Que\\'s C++ Studio')

	input_dir = args.input_dir[0]
	output_dir = args.output_dir[0]
	split = args.split
	dry_run = args.dry_run
	print(f'generation begin, input_dir -> output_dir ...')

	input_files = os.listdir(input_dir)
	output_file_count = 0
	for ifn in input_files:
		with Image.open(os.path.join(input_dir, ifn)) as im:
			# 根据文件名提取图片内容,格式为 <content>_xxx.jpeg
			nm, ext = os.path.splitext(ifn)
			content = nm.partition('_')[0]

			# 灰度二值化
			im_gray = image_grayscale(im)
			im_bin = image_binarization(im_gray)

			if split:
				for i, sub_im in enumerate(image_split(im_bin)):
					ofn_tif = f'nm_i.tif'
					if not dry_run:
						sub_im.save(os.path.join(output_dir, ofn_tif))

					ofn_gt_txt = f'nm_i.gt.txt'
					if not dry_run:
						with open(os.path.join(output_dir, ofn_gt_txt), "w", encoding='utf-8') as f:
							f.write(content[i])

					output_file_count += 1
					print(f'ifn -> ofn_tif, ofn_gt_txt("content[i]")')
			else:
				# 存为 tif 格式
				ofn_tif = f'nm.tif'
				if not dry_run:
					im_bin.save(os.path.join(output_dir, ofn_tif))

				# 生成 utf-8 编码的内容 .gt.txt 文件
				ofn_gt_txt = f'nm.gt.txt'
				ofn_tif = f'nm.tif'
				if not dry_run:
					with open(os.path.join(output_dir, ofn_gt_txt), "w", encoding='utf-8') as f:
						f.write(content)
				
				output_file_count += 1
				print(f'ifn -> ofn_tif, ofn_gt_txt("content")')

	print(f'done, len(input_files) input images parsed, output_file_count output images generated')
	print('Bye')

以上是关于训练自己的 Tesseract LSTM模型用于识别验证码的主要内容,如果未能解决你的问题,请参考以下文章

[tesseract-ocr][原创]tesseract训练lstm模型报错:LSTM: Training - Error msg - Encoding of string failed!

用于验证码识别的训练 Tesseract

用于英文手写文本的 Tesseract 4.0

开源OCR识别库-Tesseract介绍

Tesseract-OCR5.0 Lstm傻瓜式训练工具使用教程

是否有一些用于时间序列预测的预训练 LSTM、RNN 或 ANN 模型?