OCR,或光学字符识别,是最早的计算机视觉任务之一,因为在某些方面它不需要用到深度学习。因此,早在2012年深度学习热潮之前,OCR就有了各种不同的应用,有些甚至可以追溯到1914年 。
这使得很多人认为OCR的挑战已经“解决”,不再具有挑战性。另一种来自相似来源的观点认为OCR不需要深度学习,换句话说,对OCR使用深度学习是多余的。
任何从事计算机视觉或机器学习的人都知道,没有解决任务这回事,这种情况也没有什么不同。相反,OCR只能在非常特定的用例中产生非常好的效果,但是一般来说,它仍然被认为是具有挑战性的。
此外,对于某些不需要深度学习的OCR任务,确实有很好的解决方案。然而,要真正走向更好、更普遍的解决方案,深度学习将是必须的。
正如我的许多工作/写作,这也是开始作为客户做的项目。我被要求解决一个特定的OCR任务。
在完成这个任务的过程中以及之后,我得出了一些结论和见解,我很想和大家分享。另外,在对这个任务进行了密集的研究之后,我很难停下来把它扔掉,所以我继续研究,希望能得到一个更好更一般化的解决方案。
在这篇文章中,我将探索一些策略,方法和逻辑,用于处理不同的OCR任务,并将分享一些有用的方法。在最后一部分中,我们将使用代码解决一个实际问题。不幸的是,这不应该被认为是一篇详尽的论文,因为对于这种博客帖子来说,方法的深度、历史和广度都太宽泛了。
然而,和往常一样,我不会介意您对文章、数据集、资料库和其他相关博文的引用。
正如我之前提到的,OCR的含义不止一个。在最一般的意义上,它指的是从所有可能的图像中提取文本,无论是一本书的标准打印页面,还是一幅随意有涂鸦的图像(“ 自然环境下 ”)。在这期间,你可能会发现很多其他的任务,比如识别车牌,验证码,街道标志等等。
虽然每一种选择都有其自身的困难,但显然“自然环境下”的任务是最困难的。
左侧:印刷的文本。右侧:户外的文本
从这些例子中,可以得出OCR任务的一些属性:
另一个在实践中不是很难并有用的普通挑战是车牌识别。与大多数OCR任务一样,此任务需要检测牌照,然后识别它的字符。由于板的形状相对恒定,一些方法在实际识别数字之前使用简单的整形方法。以下是网络上的一些示例:
OpenALPR示例。汽车类型s abonus
由于互联网上充满了机器人,通常的做法是将它们与真人分开,这是视觉任务,特别是文本阅读,即CAPTCHA。许多这些文本是随机的和扭曲的,这会使得计算机很难阅读。我不确定开发CAPTCHA的人是否预测了计算机视觉的进步,但是今天的大多数文本CAPTCHAs并不是很难解决,特别是如果我们不是一次同时解决所有这些问题。
Facebook知道如何制作富有挑战性的CAPTCHA
Adam Geitgey提供了一个很好的教程来解决一些深度学习的CAPTCHA,其中包括再次合成人工数据。
OCR最常见的场景是打印OCR或pdf OCR。打印文档的结构化特性使得解析它们变得更加容易。大多数OCR工具(例如Tesseract)主要用于解决此任务,并取得了良好的效果。因此,我不会在这篇文章中详细阐述这项任务。
这是最具挑战性的OCR任务,因为它将所有一般计算机视觉挑战(例如噪声,照明和人工)引入OCR。此任务的一些相关数据集是coco-text和SVT数据集,它们再次从街景图像中提取文本。
COCO text example
SynthText不是数据集,也许甚至不是一项任务,但提高训练效率的一个好主意是生成人工数据。由于文本的平坦性,在图像上添加随机字符或单词看起来比任何其他对象更自然。
我们之前已经看到一些数据生成用于更容易的任务,如CAPTCHA和车牌。在野外生成文本有点复杂。该任务包括考虑图像的深度信息。幸运的是,SynthText是一个很好的工作,它接收带有上述注释的图像,并智能地生成单词(来自新闻组数据集)。
SynthText流程图:右上角是图像的分割,右下角是深度数据。左下角是图像的表面分析,根据文本将其分散在图像上。
为了使添加的文本看起来真实有用,SynthText库为每个图像采用两个掩模,一个是深度,另一个是分割。如果您想使用自己的图像,也应添加此数据。
虽然不是真正的OCR任务,但是不可能写出OCR后不包括Mnist示例。最著名的计算机视觉挑战并不会真正考虑OCR任务,因为它一次只包含一个字符(数字),而且只包含10位数字。但是,它可能暗示为什么OCR被认为是容易的。另外,在某些方法中,每个字母将被单独检测,然后Mnist(分类)模型变得相关。
正如我们所看到和暗示的那样,文本识别主要是两步任务。首先,您希望检测图像中的文本外观,可能是密集的(如打印文档中)或稀疏(如野外文本)。
在检测到行/字级别之后,我们可以从大量解决方案中再次选择,这些解决方案通常来自三种主要方法:
让我们来具体看看每一个:
1. 传统计算机视觉技术
如前所述,计算机视觉很长一段时间内解决了各种文本识别问题。你可以在网上找到很多例子:
经典CV方法通常声称:
显然,如果第二部分做得好,第三部分很容易使用模式匹配或机器学习(例如Mnist)。
然而,轮廓检测对于通用性非常具有挑战性。它需要大量的手动微调,因此在大多数问题中变得不可行。例如,让我们从这里应用一个简单的计算机视觉脚本来处理来自SVHN数据集的一些图像。首次尝试我们可能会取得非常好的结果:
但是当字符彼此靠近时,事情开始失效:
我已经找到了困难的方法,当你开始调整这些参数时,你可以减少这些错误,但不幸的是会导致其他错误。换句话说,如果你的任务不简单,那么这些方法就不适用。
2. 专业的深度学习
大多数成功的深度学习方法都具有普遍性。但是,考虑到上述属性,专用网络非常有用。
我将在这里研究一些突出方法的无穷无尽的样本,并将对提供它们的文章做一个非常快速的总结。与往常一样,每篇文章都以“任务X(文本识别)最近引起关注”开始,并继续详细描述他们的方法。仔细阅读这些文章将揭示这些方法是从以前的深度学习/文本识别工作中组合而成的。
结果也被全面描述,但由于设计上的许多差异(包括数据集中的微小差异),实际比较是不可能的。实际了解这些方法在您的任务中的表现的唯一方法是获取他们的代码(最好的情况是:找到官方repo,找到非官方但评价很高的repo,自己实施)并尝试使用您的数据。
因此,我们总是更喜欢带有较好文章的repo,如果可能的话甚至有demo。
EAST
EAST(高效准确的场景文本检测器)是一种简单而强大的文本检测方法。使用特殊的网络。
与我们将要讨论的其他方法不同,它仅限于文本检测(不是实际识别),但它的稳健性值得一提。另一个优点是它也被添加到open-CV库(从版本4开始),因此您可以轻松使用它(请参阅此处的教程)。
该网络实际上是众所周知的U-Net的一个版本,它有助于检测大小不同的特征。该网络的底层前馈主干(如文章中所示,见下图)可能不同,文中使用PVANet,但opencv实现使用Resnet。显然,它也可以预先训练(例如用imagenet)。与在U-Net中一样,特征是从网络中的不同级别提取的。
最后,网络允许两种类型的输出旋转边界框:带有旋转角度(2X2 + 1个参数)的标准边界框或“四边形”,它只是一个带有所有顶点坐标的旋转边界框。
如果真实生活结果如上图所示,识别文本将不会花费太多精力。然而,现实生活中的结果并不完美。
CRNN
卷积递归神经网络是2015年的一篇文章,该文章提出了一种混合(或三重混合?)端到端架构,旨在通过三步法捕捉文字。
这个想法如下:第一级是标准的完全卷积网络。网络的最后一层被定义为要特征层,并分为“特征列”。参见下图了解每个此类特征列如何表示文本中的某个部分。
然后,将特征列馈送到输出序列的深双向LSTM,用于查找字符之间的关系。
最后,第三部分是转录层。它的目标是采用凌乱的字符序列,其中一些字符是多余的而其他字符是空白的,并使用概率方法来统一并理解它。
这种方法称为CTC损失,可在此处阅读。该层可以与具有或不具有预定义词典一起使用,这可以促进单词的预测。
本文使用固定文本词典达到很高的准确率(> 95%),并且在没有固定文本词典的情况下成功率不同。
STN-net/SEE
SEE - 半监督端到端场景文本识别,是Christian Bartzi的作品。他和他的同事应用真正的端到端策略来检测和识别文本。他们使用非常弱的监督(他们称之为半监督,与通常的意思不同)。因为他们只使用文本注释训练网络(没有边界框)。这允许他们使用更多数据,但是使他们的训练过程非常具有挑战性,并且他们讨论了使其工作的不同技巧,例如不训练具有两行以上文本的图像(至少在训练的第一阶段)。
该论文的早期版本称为STN OCR。在最后的论文中,研究人员对他们的方法和表现进行了改进,并且他们在高质量的结果的基础上更加强调他们的方法的通用性。
SEE strategy
STN-OCR提示使用空间变换器(= STN,与最近的谷歌变压器无关)的策略。
他们训练两个级联网络,其中第一个网络,即变换器,学习图像上的变换,以输出更容易解释的子图像。
然后,另一个顶部有LSTM的前馈网络(嗯......似乎我们之前已经看过它)来识别文本。
研究强调了使用resnet(它们使用两次)的重要性,因为它为早期层提供了“强大”的传播。但是现在这种做法很受欢迎。
无论哪种方式,这都是一种有趣的尝试方法。
3.标准的深度学习方法
如标题所示,在检测到“单词”之后,我们可以应用标准的深度学习检测方法,例如SSD,YOLO和Mask RCNN。由于网上有大量信息,我不打算详细说明这些方法。
我必须说这是目前我最喜欢的方法,因为我喜欢深度学习的是“端到端”的哲学,你应用一个强大的模型,通过一些调整将解决几乎所有问题。在本文的下一部分中,我们将看到它实际上是如何工作的。
然而,SSD和其他检测模型在涉及密集的类似类时受到挑战,如此处所述。事实上,深度学习模型识别数字和字母比识别更具挑战性和精心设计的物体(如狗,猫或人类)要困难得多。它们达不到所需的准确度,因此,专业方法茁壮成长。
所以说完之后,是时候动手了,自己尝试一些模型。我们将尝试解决SVHN任务。SVHN数据包含三种不同的数据集:train,test和extra。差异不是100%明确,但是最大的额外数据集(约500K样本)包括以某种方式更容易识别的图像。因此,为了这个目的,我们将使用它。
需要做以下准备任务:
要有效地遵循该过程,您应该阅读以下说明以及从项目的repo运行ssd_OCR.ipynb。
准备开始了!
第1步:解析数据
喜欢与否,但在检测任务中没有“黄金”格式的数据表示。一些众所周知的格式是:coco,via,pascal,xml。还有更多。例如,SVHN数据集用不明确的.mat格式注释。对我们来说幸运的是,这个gist提供了一个灵活的read_process_h5脚本来将.mat文件转换为标准的json,你应该提前一步并将其转换为pascal格式,如下所示:
def json_to_pascal(json, filename): #filename is the .mat file
# convert json to pascal and save as csv
pascal_list = []
for i in json:
for j in range(len(i['labels'])):
pascal_list.append({'fname': i['filename']
,'xmin': int(i['left'][j]), 'xmax': int(i['left'][j]+i['width'][j])
,'ymin': int(i['top'][j]), 'ymax': int(i['top'][j]+i['height'][j])
,'class_id': int(i['labels'][j])})
df_pascal = pd.DataFrame(pascal_list,dtype='str')
df_pascal.to_csv(filename,index=False)
p = read_process_h5(file_path)
json_to_pascal(p, data_folder+'pascal.csv')
现在我们应该有一个更标准的pascal.csv文件,它将允许我们进步。如果转换速度很慢,您应该注意我们不需要所有数据样本。 ~10K就足够了。
第2步:查看数据
在开始建模过程之前,您最好对数据进行一些探索。我只提供了一个快速的健全测试功能,但我建议你做一些进一步的分析:
def viz_random_image(df):
file = np.random.choice(df.fname)
im = skimage.io.imread(data_folder+file)
annots = df[df.fname==file].iterrows()
plt.figure(figsize=(6,6))
plt.imshow(im)
current_axis = plt.gca()
for box in annots:
label = box[1]['class_id']
current_axis.add_patch(plt.Rectangle(
(box[1]['xmin'], box[1]['ymin']), box[1]['xmax']-box[1]['xmin'],
box[1]['ymax']-box[1]['ymin'], color='blue', fill=False, linewidth=2))
current_axis.text(box[1]['xmin'], box[1]['ymin'], label, size='x-large', color='white', bbox={'facecolor':'blue', 'alpha':1.0})
plt.show()
viz_random_image(df)
SVHN数据集的一个代表样本
对于以下步骤,我在repo中提供了utils_ssd.py,便于训练,加载权重等。一些代码来自SSD_Keras repo,它也被广泛使用。
第3步:选择策略
如前所述,我们有许多可能的方法来解决这个问题。在本教程中,我将采用标准的深度学习检测方法,并将使用SSD检测模型。我们将从使用SSD keras的实现。这是PierreLuigi的一个很好的实现。虽然它比 rykov8实现的GitHub星更少,但它似乎更新,并且更容易集成。当您选择要使用的项目时,这是一个非常重要的事情。其他不错的选择将是YOLO模型和Mask RCNN。
步骤4:加载并训练SSD模型
要使用repo,您需要验证您是否拥有SSD_keras repo,并填写json_config.json文件中的路径,以允许notebook查找路径。
从import开始:
import os
import sys
import skimage.io
import scipy
import json
with open('json_config.json') as f: json_conf = json.load(f)
ROOT_DIR = os.path.abspath(json_conf['ssd_folder']) # add here mask RCNN path
sys.path.append(ROOT_DIR)
import cv2
from utils_ssd import *
import pandas as pd
from PIL import Image
from matplotlib import pyplot as plt
%matplotlib inline
%load_ext autoreload
% autoreload 2
还有一些定义:
task = 'svhn'
labels_path = f'{data_folder}pascal.csv'
input_format = ['class_id','image_name','xmax','xmin','ymax','ymin' ]
df = pd.read_csv(labels_path)
模型配置:
class SVHN_Config(Config):
batch_size = 8
dataset_folder = data_folder
task = task
labels_path = labels_path
input_format = input_format
conf=SVHN_Config()
resize = Resize(height=conf.img_height, width=conf.img_width)
trans = [resize]
定义模型,加载权重
与大多数深度学习案例一样,我们不会从头开始训练,但我们会加载预先训练过的权重。在这种情况下,我们将加载SSD模型在COCO数据集上训练的权重,该数据集有80个类。显然,我们的任务只有10个类,因此我们将在加载权重后重建顶层以获得正确的输出数。我们在init_weights函数中执行此操作。旁注:在这种情况下,每个类(边界框坐标)的输出数量为44:4,而背景/无类别的输出数量为4。
learner = SSD_finetune(conf)
learner.get_data(create_subset=True)
weights_destination_path=learner.init_weights()
learner.get_model(mode='training', weights_path = weights_destination_path)
model = learner.model
learner.get_input_encoder()
ssd_input_encoder = learner.ssd_input_encoder
# Training schedule definitions
adam = Adam(lr=0.0002, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
ssd_loss = SSDLoss(neg_pos_ratio=3, n_neg_min=0, alpha=1.0)
model.compile(optimizer=adam, loss=ssd_loss.compute_loss)
定义数据加载器
train_annotation_file=f'{conf.dataset_folder}train_pascal.csv'
val_annotation_file=f'{conf.dataset_folder}val_pascal.csv'
subset_annotation_file=f'{conf.dataset_folder}small_pascal.csv'
batch_size=4
ret_5_elements={'original_images','processed_images','processed_labels','filenames','inverse_transform'}
train_generator = learner.get_generator(batch_size, trans=trans, anot_file=train_annotation_file,
encoder=ssd_input_encoder)
val_generator = learner.get_generator(batch_size,trans=trans, anot_file=val_annotation_file,
returns={'processed_images','encoded_labels'}, encoder=ssd_input_encoder,val=True)
步骤五:训练模型
现在模型准备就绪,我们将设置一些最后的训练相关定义,并开始训练
learner.init_training()
history = learner.train(train_generator, val_generator, steps=100,epochs=80)
作为反馈,我在训练脚本中包含了training_plot回调,以便在每个epoch后可视化随机图像。例如,这是第六epoch后的预测快照:
SSD_Keras repo在每个epoch后保存模型,因此您可以通过将weights_destination_path行更改为等于路径来稍后加载模型
weights_destination_path = <path>
如果你按照我的指示,你应该能够训练模型。ssd_keras提供了更多功能,例如数据扩充,不同的加载器和评估器。经过短暂的训练,我达到了大于80 mAP。
你达到了多高?
从tensorboard训练4X100X60样本
在这篇文章中,我们讨论了OCR领域的不同挑战和方法。在深度学习/计算机视觉中存在许多问题,它比起初看起来要多得多。我们已经看到了它的许多子任务,以及一些不同的方法来解决它。另一方面,我们已经看到,在没有太多麻烦的情况下达到初步结果并不是很难。
希望你喜欢!
想要继续查看该篇文章相关链接和参考文献?
戳链接或点击底部【阅读原文】:
http://ai.yanxishe.com/page/TextTranslation/1162
资源推荐
机器学习相关书籍有很多,然而符合现在的深度学习研究习惯和知识面的优秀的深度学习教科书就很少了。这本由Ian Goodfellow、Yoshua Bengio、Aaron Courville三位现代深度学习中流砥柱人物在2017年编著的深度学习教科书可以算是最适合深度学习学习者的课本,它涵盖了主要的数学知识、深度学习的基础知识和原理,以及最新的方法和认识,而且在技术的应用方面也有许多具体介绍。由于书籍封面是一幅令人迷幻的花园照片,它也被亲切地称为“花书”。
这里打包了中英两个版本的书、书籍封面,以及一个彩蛋。喜欢彩蛋的请在评论区给个赞哦 :-P