本文主要介绍通过开源项目 TextRecognitionDataGenerator 生成定制OCR数据集,用于训练模型和测试模型效果,达到特定场景下识别的优化。
1、现有数据集的问题
在自然场景(如照片,视频)的文字OCR识别中,由于文字的字体和颜色不同,长短和布局不定,同时文字所处背景的复杂性,模型训练后在特定场景下的文字识别效果依赖于数据集是否覆盖类似风格的文字(训练-测试同分布假设)。
当前虽然有不少开放的大规模OCR数据集,比如牛津大学的 Synthetic Word Dataset 和 SynthText in the Wild Dataset,作为业界的标准数据集,两者大小均在100g以上,但是覆盖的场景仍然非常有限,比如训练集中不包含标点符号,很多字体没有覆盖,另外还有各大竞赛的数据集 IC13, IC15, IIIT, SVT. IIIT, SVT, IC03, IC13, IC15, SVTP 和 CUTE,也多是特定场景下的数据集。
在实际测试中发现,即使在这些大规模数据集(如 Synthetic Word Dataset )达到非常高的测试精度,在特定场景下的识别精度仍然不理想(比如身份证文字的识别),原因可能有两点,当前的 crnn+ctc 或者 attention+ctc(目前识别模型就这两种)难以学习到较好泛化性能的权值,二是尽管数据集足够大,但是由于自然场景中文字的多样性,数据集很难覆盖到特定场景下的文字风格。
所以从数据集的角度考虑问题,针对特定场景生成相应的数据集是解决特定场景下识别问题的一个思路。
2、数据集生成方法
从训练集和测试集同分布假设考虑,最好的数据集当然是从实际的样本标注而来。但是文字的标注比较耗时,ocr数据集通常至少需要包含几十万张图片,所以实际样本的标注是一个大工程。
本文主要介绍另外一种方法:合成法
所谓合成法,是通过已有的单词或者字符进行随机组合,然后通过模糊、倾斜、扭曲、仿射变换和加背景等方法,生成接近真实场景中的图片。由于单词和已有的字符已知,所以省去了标注的过程。
实际上工业上普遍采用的Synthetic Word Dataset 和 SynthText in the Wild Dataset就是合成而来,下图是两个数据集的合成方式,实践证明,用这两个数据集训练的模型在街景文字识别中有较好的性能。
本文介绍一个开源OCR数据集生成项目TextRecognitionDataGenerator ,可以实现类似的合成效果,但是可以根据具体的场景定制自己的数据集。
3、开源OCR数据集生成项目TextRecognitionDataGenerator
该项目通过 Python实现,可以通过 pip 安装:
终端: pip install trdg
然后在终端中输入以下命令:
终端:trdg -c 1000 -w 5
就可以生成如下图片,其中 -c 参数表示生成图片的数量, -w 表示图片中单词的个数。
但是用 pip 安装和 shell 命令生成的方式不够灵活,本文主要介绍通过源码的方式生成数据集。
终端:git clone git@github.com:Belval/TextRecognitionDataGenerator.git
进入项目后,修改 run.py 文件中的默认参数即可,该项目的强大之处在于作者提供了非常多的定制参数,可以根据自己的需求来生成数据集。
(1)参数说明
参数 | 参数说明 | |
---|---|---|
| 生成图片输出路径 | |
| 生成图片中文字的源文件(路径),不指定用项目默认文件 | |
| 语言:en—英文,ch—中文,默认英文 | |
| 生成的图片数量 | |
在 -rs 为 True 的情况下可以有右侧设置 | | 用字符随机生成单词,用于随机生成单词的字符中包含字母 |
| 用字符随机生成单词,用于随机生成单词的字符中包含数字 | |
| 用字符随机生成单词,用于随机生成单词的字符中包含符号 | |
| 随机生成图片包含的单词数 | |
| 以-w设置的单词数为上限,随机生成不同单词数的图片 | |
| 生成图片的像素高度(水平排版),生成图片的像素宽度(竖直排版) | |
| 运行程序使用的线程数,实测8线程下,生成一万张图片仅需 6s,设置较高的线程可以明显提速 | |
| 生成图片的保存格式,默认”jpg“ | |
| 文字在图片中的倾斜角度 | |
| 在倾斜角度 -k 设置的情况下,比如设为 a,则生成图片文字的倾斜角度在 -a~a之间随机选择 | |
| 用维基百科作为单词源,生成图片,实测好像会连接失败 | |
| 设定图片的高斯模糊值,默认为0,即无高斯模糊处理 | |
| 在设定高斯模糊值 -rbl 的情况下,比如设为b,则生成图片的高斯模糊值在 0~b之间随机取值 | |
| 设置图片的背景,0-高斯噪声; 1-白色背景; 2-图片 | |
| 在设定背景参数 -b 的值为2(即图片)的情况下,从指定的图片文件夹中读取图片作为背景。 | |
| 利用训练好的RNN模型,生成手写字体图片(强大。。。) | |
| 生成图片的命名格式,图片名称通常包含标签,对于一些包含特殊符号的图片,由于图片命名中不能包含特殊图片,所以另生成一个文本记录标签。 | |
| 对于每一张生成的图片,输出同样尺寸的掩码(全黑图片),训练的时候作为一种trick | |
| 对生成图片中的文字进行扭曲,默认为0。1-正弦扭曲,2-余弦扭曲 | |
| 在 -d 设定为正弦扭曲或者余弦扭曲的情况下,设定扭曲方向,0 - 竖直方向上的扭曲 1-横向扭曲 | |
| 设定图片的像素宽度,在不指定的情况下,宽度为文本的宽度+10,假如设定宽度,过短会截取部分文本 | |
| 在设定文本宽度参数 -wd的情况下,截取文本的方式,0 -从左侧开始截取 1- 从中心向两边截取 2-从右侧开始截取 | |
| 文本在图片中的排版,0- 横向排版,1- 竖向排版,默认横向排版 | |
| 文本的颜色,通过设定的颜色,或者颜色范围,生成特定颜色的文本,颜色格式为16进制 如:#282828,(#000000,#282828) | |
| 设定图片中单词之间的像素间隔,默认为1像素 | |
| 设定图片中字符之间的像素间隔,默认为0像素 | |
| 设定图片中文本,上下左右的空白间隔,以间隔的像素值表示,默认(5,5,5,5,) | |
| 是否按文本裁切图片,使图片中文本上下左右的间隔均为0,默认为 False | |
| 设定生成文本所用的字体文件(.ttf)格式 | |
| 设定生成文本所用字体的文件夹,生成的图片从文件夹中随机选择字体 | |
| 设定图片中生成的文字大小写:upper/lower | |
| 设定从字典文件(路径)中选择单词生成图片 | |
| 设定是设定根据单词还是字符分隔文字,True-根据单词 Talse-根据字符 |
对应的代码如下:
def parse_arguments():
"""
Parse the command line arguments of the program.
"""
parser = argparse.ArgumentParser(
description="Generate synthetic text data for text recognition."
)
parser.add_argument(
"--output_dir", type=str, nargs="?", help="The output directory", default="out/"
)
parser.add_argument(
"-i",
"--input_file",
type=str,
nargs="?",
help="When set, this argument uses a specified text file as source for the text",
default="",
)
parser.add_argument(
"-l",
"--language",
type=str,
nargs="?",
help="The language to use, should be fr (French), en (English), es (Spanish), de (German), ar (Arabic), cn (Chinese), or hi (Hindi)",
default="en"
)
parser.add_argument(
"-c",
"--count",
type=int,
nargs="?",
help="The number of images to be created.",
default=10
)
parser.add_argument(
"-rs",
"--random_sequences",
action="store_true",
help="Use random sequences as the source text for the generation. Set '-let','-num','-sym' to use letters/numbers/symbols. If none specified, using all three.",
default=True,
)
parser.add_argument(
"-let",
"--include_letters",
action="store_true",
help="Define if random sequences should contain letters. Only works with -rs",
default=True,
)
parser.add_argument(
"-num",
"--include_numbers",
action="store_true",
help="Define if random sequences should contain numbers. Only works with -rs",
default=True,
)
parser.add_argument(
"-sym",
"--include_symbols",
action="store_true",
help="Define if random sequences should contain symbols. Only works with -rs",
default=False,
)
parser.add_argument(
"-w",
"--length",
type=int,
nargs="?",
help="Define how many words should be included in each generated sample. If the text source is Wikipedia, this is the MINIMUM length",
default=2,
)
parser.add_argument(
"-r",
"--random",
action="store_true",
help="Define if the produced string will have variable word count (with --length being the maximum)",
default=False,
)
parser.add_argument(
"-f",
"--format",
type=int,
nargs="?",
help="Define the height of the produced images if horizontal, else the width",
default=32,
)
parser.add_argument(
"-t",
"--thread_count",
type=int,
nargs="?",
help="Define the number of thread to use for image generation",
default=8,
)
parser.add_argument(
"-e",
"--extension",
type=str,
nargs="?",
help="Define the extension to save the image with",
default="jpg",
)
parser.add_argument(
"-k",
"--skew_angle",
type=int,
nargs="?",
help="Define skewing angle of the generated text. In positive degrees",
default=0,
)
parser.add_argument(
"-rk",
"--random_skew",
action="store_true",
help="When set, the skew angle will be randomized between the value set with -k and it's opposite",
default=False,
)
parser.add_argument(
"-wk",
"--use_wikipedia",
action="store_true",
help="Use Wikipedia as the source text for the generation, using this paremeter ignores -r, -n, -s",
default=False,
)
parser.add_argument(
"-bl",
"--blur",
type=int,
nargs="?",
help="Apply gaussian blur to the resulting sample. Should be an integer defining the blur radius",
default=0,
)
parser.add_argument(
"-rbl",
"--random_blur",
action="store_true",
help="When set, the blur radius will be randomized between 0 and -bl.",
default=False,
)
parser.add_argument(
"-b",
"--background",
type=int,
nargs="?",
help="Define what kind of background to use. 0: Gaussian Noise, 1: Plain white, 2: Quasicrystal, 3: Image",
default=1,
)
parser.add_argument(
"-hw",
"--handwritten",
action="store_true",
help='Define if the data will be "handwritten" by an RNN',
)
parser.add_argument(
"-na",
"--name_format",
type=int,
help="Define how the produced files will be named. 0: [TEXT]_[ID].[EXT], 1: [ID]_[TEXT].[EXT] 2: [ID].[EXT] + one file labels.txt containing id-to-label mappings",
default=0,
)
parser.add_argument(
"-om",
"--output_mask",
type=int,
help="Define if the generator will return masks for the text",
default=0,
)
parser.add_argument(
"-d",
"--distorsion",
type=int,
nargs="?",
help="Define a distorsion applied to the resulting image. 0: None (Default), 1: Sine wave, 2: Cosine wave, 3: Random",
default=0,
)
parser.add_argument(
"-do",
"--distorsion_orientation",
type=int,
nargs="?",
help="Define the distorsion's orientation. Only used if -d is specified. 0: Vertical (Up and down), 1: Horizontal (Left and Right), 2: Both",
default=0,
)
parser.add_argument(
"-wd",
"--width",
type=int,
nargs="?",
help="Define the width of the resulting image. If not set it will be the width of the text + 10. If the width of the generated text is bigger that number will be used",
default=-1,
)
parser.add_argument(
"-al",
"--alignment",
type=int,
nargs="?",
help="Define the alignment of the text in the image. Only used if the width parameter is set. 0: left, 1: center, 2: right",
default=1,
)
parser.add_argument(
"-or",
"--orientation",
type=int,
nargs="?",
help="Define the orientation of the text. 0: Horizontal, 1: Vertical",
default=0,
)
parser.add_argument(
"-tc",
"--text_color",
type=str,
nargs="?",
help="Define the text's color, should be either a single hex color or a range in the ?,? format.",
default="#282828",
)
parser.add_argument(
"-sw",
"--space_width",
type=float,
nargs="?",
help="Define the width of the spaces between words. 2.0 means twice the normal space width",
default=1.0,
)
parser.add_argument(
"-cs",
"--character_spacing",
type=int,
nargs="?",
help="Define the width of the spaces between characters. 2 means two pixels",
default=0,
)
parser.add_argument(
"-m",
"--margins",
type=margins,
nargs="?",
help="Define the margins around the text when rendered. In pixels",
default=(0, 0, 0, 0),
)
parser.add_argument(
"-fi",
"--fit",
action="store_true",
help="Apply a tight crop around the rendered text",
default=False,
)
parser.add_argument(
"-ft", "--font", type=str, nargs="?", help="Define font to be used"
)
parser.add_argument(
"-fd",
"--font_dir",
type=str,
nargs="?",
help="Define a font directory to be used",
)
parser.add_argument(
"-id",
"--image_dir",
type=str,
nargs="?",
help="Define an image directory to use when background is set to image",
default=os.path.join(os.path.split(os.path.realpath(__file__))[0], "images")
)
parser.add_argument(
"-ca",
"--case",
type=str,
nargs="?",
help="Generate upper or lowercase only. arguments: upper or lower. Example: --case upper",
default="upper"
)
parser.add_argument(
"-dt", "--dict", type=str, nargs="?", help="Define the dictionary to be used"
)
parser.add_argument(
"-ws", "--word_split",
action="store_true",
help="Split on words instead of on characters (preserves ligatures, no character spacing)",
default=False,
)
return parser.parse_args()
(2)实例应用
对于卡证类的识别,由于卡证的背景和字体都是固定的,所以可以通过指定字体和背景生成数据集。
以一个卡片的场景为例,
PAN 卡中包含两种字体:Mangal Bold 和 time new roman,所以可以下载对应字体的 ttf 文件,放到项目的font文件夹中,然后移除其它的字体文件,让程序仅根据这两种字体生成图片。
同时识别的字段中包含数字,字母和符号,同时人名没有明显的规律,所以训练集最好通过字符来生成。
所以可以设定如下参数:
1) 指定生成文本的单词由字母、数字和符号组合生成
--random_sequences: True
--include_letters: True
--include_numbers: True
--include_symbols: True
2) 设置一个图片的单词数在1~3之间(考虑到卡片中的字段不会超过3个词)
--length:3
--random:True
3) 照片有的时候由于拍照原因会倾斜,所以设定倾斜角度在(-10度~10度之间)
--skew_angle:10
--random_skew:True
4) 照片有的时候会拍得很模块,所以设置高斯模糊(0~0.1)
--blur:0
--random_blur:True
5) 背景基本上为纯色,采用高斯噪声作为背景
--background:0
其余的再设置一下字符间隔,图片的上下间隔和单词之间的间隔,得到的数据集如下所示:
从字体和排版上以及比较接近卡片上的文字了。作为改进,可以随机截掉文字的上下一小部分,这个在现实中也经常出现(文字检测不够准确),替换更加相似的背景等等。