OCR系统主要依赖于特征的提取以及字符本身的区分性,OCR分为线下的和在线的。线下的就是输入图片进行扫描,线上的就是手写文字,线上的识别依赖于笔触速度、方向角、轨迹点等等,尽管字符被区分开来,但是线上被认为复杂度较大。
OCR技术在1940年就已经出现了。最开始,研究者使用SVM、决策树、随机森林、KNN等等来做机器学习的OCR,后来延展到CNN、RNN、LSTM的深度学习方法。
目前两种主流的深度学习OCR方案是CNN+Seq2Seq+attention(CRNN)和CRNN.
OCR是一个很宽泛的方向,具体采用什么方案需要看应用场景:
CNN+Seq2Seq+attention(CRNN)
CNN固然是为了提取图片中的特征
Seq2Seq是将feature_map按列划分之后,输入到以下网络当中,该网络分为encoder阶段和decoder阶段,编码阶段将所有列信息都融合成一个context向量,然后在解码阶段逐个解析出每个字符,直到解析出停止符结束。
Seq2Seq是RNN的一种变种。
我们先来看看普通的RNN的结构:
每一个隐层的输出都要作为下一个切片样本的输入。
显而易见,LSTM的输出大小跟网络结构的设计有关,长度是固定的,但是这对于不定长的OCR识别来说不太合适。
Seq2Seq的结构如下图所示:
下面是一个机器翻译的例子,可以看出,解码到停止符就结束。
Seq2Seq很好地解决了不定长的问题。
但是这样也存在一个问题,在encoder阶段将所有的列向量信息融合成一个context,如果带解析的行中字符个数很多,那这个context需要很长,所以context无法做到定长包含所有长度的句子的信息,那么应该怎样做呢?
那么我们就让decoder阶段的每个隐层不同程度获得encoder阶段每个隐层的输入,不同程度也就意味着不同系数,这就是attention机制。
很显然,当我们将context固定长度后,必然context放不下所有的信息,我们需要多个分支,不仅从上一个隐层获得信息,还要从encoder的每个隐层或多或少加入一些信息。
关于attention机制,Attention Mechanism最早引入至自然语言中是为解决机器翻译中随句子长度(超过50)增加其性能显著下降的问题,现已广泛应用于各类序列数据的处理中。我们可以从下图中看出,每一个decorder的隐层的输入都有来自encoder每一个隐层的部分(当然也有包含上一隐层传递过来的信息),但是这些系数应该怎么确定呢?这些系数越大,也就是所谓的attention越强。
为了更好滴说明:定义
h
‾
s
)
\overline h_s)
hs)代表Encoder状态,
h
t
h_t
ht代表Decoder状态,
h
~
t
\widetilde h_t
h
t 代表Attention Layer输出的最终Decoder状态.
h
‾
s
)
\overline h_s)
hs) 和
h
~
t
\widetilde h_t
h
t是 [
n
h
n_h
nh,1] 大小的向量。
Decoder的
h
~
t
\widetilde h_t
h
t 时刻隐藏层状态 [公式] 对Encoder每一个隐藏层状态
h
‾
s
)
\overline h_s)
hs) 权重
a
t
(
s
)
a_t(s)
at(s)数值:
其次,利用权重
a
t
(
s
)
a_t(s)
at(s) 计算所有隐藏层状态
h
‾
s
)
\overline h_s)
hs)加权之和
c
t
c_t
ct ,即生成新的大小为 [
n
h
n_h
nh,1]的Context状态向量:
接下来将
c
t
c_t
ct与原始Decoder隐藏层
t
t
t时刻状态
h
t
h_t
ht拼接在一起.
最后,对加入“注意力”的Decoder状态
h
~
t
\widetilde h_t
h
t乘以
W
h
o
W_{ho}
Who矩阵即可获得输出:
CNN+LSTM+CTCloss
也就是CRNN+CTC.
LSTM在其他博客中介绍过,就是双向循环神经网络,重点来讲讲CTCloss.
为什么要使用CTCloss?
当然,我们可以创建一个带有文本行图像的数据集,然后为图像的每个水平位置指定相应的字符,如下图所示。然后,我们可以训练一个NN输出一个字符-为每个水平位置得分。
但是,这种幼稚的解决方案存在两个问题:
1.在字符级别注释数据集非常耗时(而且很无聊)。
2.我们仅获得字符分数,因此需要进一步处理才能从中获得最终文本。一个字符可以跨越多个水平位置,例如,我们可以得到“ ttooo”,因为“ o”是一个宽字符,如图2所示。我们必须删除所有重复的“ t”和“ o”。但是,如果公认的文本本来是“太”怎么办?然后删除所有重复的“ o”会得到错误的结果。如何处理呢?
CTC为我们解决了两个问题:
我们只需要告诉CTC损失函数图像中出现的文本即可。因此,我们将忽略图像中字符的位置和宽度。
无需进一步处理识别的文本。
CTC如何运作
如前所述,我们不想在每个水平位置(从现在开始称为时间步长)注释图像。NN训练将以CTC损失函数为指导。我们只将NN的输出矩阵和相应的GT文本输入到CTC损失函数。但是如何知道每个字符出现在哪里?好吧,它不知道。相反,它将尝试图像中GT文本的所有可能的对齐方式,并获取所有分数的总和。这样,如果对齐分数上的总和具有较高的值,则GT文本的分数将较高。
编码文字
存在一个问题,即如何对重复字符进行编码。通过引入伪字符(称为空格,但不要将其与“真实”空格(即空格字符)混淆)来解决该问题。在下文中,此特殊字符将表示为“-”。我们使用一种聪明的编码方案来解决重复字符问题:对文本进行编码时,我们可以在任意位置插入任意多个空格,在解码时将其删除。但是,我们必须在重复字符之间插入空格,例如“ he ll o”。此外,我们可以根据需要重复每个字符。
我们来看下面的例子:
如您所见,该模式还使我们能够轻松地创建同一文本的不同对齐方式,例如“ to”和“ too”以及“ -to”都代表相同的文本(“ to”),但与图像的对齐方式不同。训练NN输出编码文本(在NN输出矩阵中编码)。
损失计算
我们需要计算训练样本(图像和GT文本对)的损失值,以训练NN。您已经知道NN输出一个矩阵,其中包含每个时间步长每个字符的分数。简约矩阵如图3所示:有两个时间步长(t0,t1)和三个字符(“ a”,“ b”和空白“-”)。每个时间步的字符得分总和为1。
此外,您已经知道,损失是通过将GT文本的所有可能对齐方式的所有分数相加得出的,因此,文本在图像中的位置并不重要。
一个路线(或路径)的分数(通常在文献中称为)是通过将相应的字符得分相乘得出的。在上面显示的示例中,路径“ aa”的得分为0.4·0.4 = 0.16,而“ a-”的得分为0.4·0.6 = 0.24,而“ -a”的得分为0.6·0.4 = 0.24。为了获得给定GT文本的分数,我们对与该文本对应的所有路径的分数求和。假设示例中的GT文本为“ a”:我们必须计算所有可能的长度为2的路径(因为矩阵具有2个时间步长),分别是:“ aa”,“ a-”和“ -a” 。我们已经计算了这些路径的分数,因此我们只需要对它们求和就可以得到0.4·0.4 + 0.4·0.6 + 0.6·0.4 = 0.64。如果将GT文本假定为“”,我们看到只有一个相应的路径,即“-”,其总得分为0.6·0.6 = 0.36。
给定NN产生的输出矩阵,我们现在能够计算出训练样本的GT文本的概率。目标是训练NN,以便为正确的分类输出高概率(理想情况下,值为1)。因此,我们将训练数据集的正确分类概率的乘积最大化。由于技术原因,我们将其重新表述为一个等效问题:最小化训练数据集的损失,其中损失是对数概率的负和。如果您需要单个样本的损失值,则只需计算概率,取对数,然后在结果前加上负号即可。为了训练NN,相对于NN参数(例如,卷积核的权重)的损失的梯度被计算并且用于更新参数。
解码
当我们拥有训练有素的NN时,我们通常希望使用它来识别以前看不见的图像中的文本。或更笼统地说:给定NN的输出矩阵,我们要计算最可能的文本。您已经知道一种计算给定文本分数的方法。但是这次,我们没有得到任何文本,实际上,这正是我们正在寻找的文本。如果只有几个时间步骤和字符,尝试所有可能的文本都可以,但是对于实际用例,这是不可行的。
一种简单且非常快速的算法是最佳路径解码,它包括两个步骤:
1.它通过在每个时间步中采用最可能的字符来计算最佳路径。
2.它首先删除重复的字符,然后从路径中删除所有空格,从而撤消了编码。剩下的代表已识别的文本。
下图中显示了一个示例。字符为“ a”,“ b”和“-”(空白)。有5个时间步骤。让我们将最佳路径解码器应用于此矩阵:t0的最可能字符是“ a”,t1和t2的情况相同。空白字符在t3时得分最高。最后,“ b”最有可能在t4出现。这给了我们路径“ aaa-b”。我们删除重复的字符,这将产生“ ab”,然后从剩余路径中删除所有空格,这将为我们提供文本“ ab”,我们将其输出为可识别的文本。
当然,最佳路径解码只是一个近似值。很容易构造出错误结果的示例:如果对图3中的矩阵进行解码,则会得到“”作为可识别的文本。但是我们已经知道“”的概率仅为0.36,而“ a”的概率为0.64。但是,在实际情况下,近似算法通常会给出良好的结果。有更高级的解码器,例如波束搜索解码,前缀搜索解码或令牌传递,它们也使用有关语言结构的信息来改善结果。
结论
除此之外,还有一些早期的OCR算法,可以分为基于纹理的、基于模板匹配的、基于连通域的,也一并介绍一下:
CC
全称connected components. 其步骤可以分为候选框生成,候选框归一化,无字符区域过滤。
1.连通域提取(CC)
采用MSER算法,因为这是一个省时高效的算法,
MSER全称叫做最大稳定极值区域,其原理是:
主要是基于分水岭的思想进行检测。分水岭算法思想来源于地形学,将图像当作自然地貌,图像中每一个像素的灰度值表示该点的海拔高度,每一个局部极小值及区域称为集水盆地,两个集水盆地之间的边界则为分水岭,如下图:
MSER的处理过程是这样的,对一幅灰度图像取不同的阈值进行二值化处理,阈值从0至255递增,这个递增的过程就好比是一片土地上的水面不断上升,随着水位的不断上升,一些较低的区域就会逐渐被淹没,从天空鸟瞰,大地变为陆地、水域两部分,并且水域部分在不断扩大。在这个“漫水”的过程中,图像中的某些连通区域变化很小,甚至没有变化,则该区域就被称为最大稳定极值区域。在一幅有文字的图像上,文字区域由于颜色(灰度值)是一致的,因此在水平面(阈值)持续增长的过程中,一开始不会被“淹没”,直到阈值增加到文字本身的灰度值时才会被“淹没”。该算法可以用来粗略地定位出图像中的文字区域位置。
依我的理解,就是文字连通域需要像素连通,且像素值基本相同。也就是提取出像素值基本稳定的连通域。从0到255增加的步骤,也是要求文字是黑色(255)。
经过opencv的算法,可以得到很多像素值稳定的连通域,获取最小矩形框,得到的如下图所示:
有很多重复的框,经过NMS处理后得到:
接下来建立训练集,判断该连通域框是否包含文字;
然后对于是文本的区域和不是文本的框进行Adboost分类;
然后对于字符的类别进行SVM分类;
当然这只是用于简单场景,复杂场景下效果不OK.
简单的膨胀腐蚀拟合轮廓
当然,只针对白底黑色或者黑底白字,复杂背景完全无法检测。