文字识别也是图像领域一个常见问题。然而,对于自然场景图像,首先要定位图像中的文字位置,然后才能进行识别。

所以一般来说,从自然场景图片中进行文字识别,需要包括 2 个步骤:

  • 文字检测:解决的问题是哪里有文字,文字的范围有多少
  • 文字识别:对定位好的文字区域进行识别,主要解决的问题是每个文字是什么,将图像中的文字区域进转化为字符信息。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图1

图 1 文字识别的步骤

文字检测类似于目标检测,即用 box 标识出图像中所有文字位置。对于文字检测不了解的读者,请参考本专栏文章:

本文的重点是如何对已经定位好的文字区域图片进行识别。 假设之前已经文字检测算法已经定位图中的 “subway” 区域(红框),接下来就是文字识别。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图2

图 2 文字检测定位文字图像区域

基于 RNN 文字识别算法主要有两个框架:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图3

图 3 基于 RNN 文字识别 2 种基本算法框架

  1. CNN+RNN+CTC(CRNN+CTC)
  2. CNN+Seq2Seq+Attention

本文主要介绍第一种框架 CRNN+CTC,对应 TensorFlow 1.15 实现代码如下。本文介绍的 CRNN 网络结构都基于此代码。另外该代码已经支持不定长英文识别。

需要说明该代码非常简单,只用于原理介绍,也无法识别中文。

CRNN 基本网络结构

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图4

图 4 CRNN 网络结构(此图按照本文给出的 github 实现代码画的)

整个 CRNN 网络可以分为三个部分:

假设输入图像大小为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图5
,注意提及图像都是 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图6
形式。

  • Convlutional Layers

这里的卷积层就是一个普通的 CNN 网络,用于提取输入图像的 Convolutional feature maps,即将大小为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图7
的图像转换为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图8
大小的卷积特征矩阵,网络细节请参考本文给出的实现代码。

  • Recurrent Layers

这里的循环网络层是一个深层双向 LSTM 网络,在卷积特征的基础上继续提取文字序列特征。对 RNN 不了解的读者,建议参考:

所谓深层 RNN 网络,是指超过两层的 RNN 网络。对于单层双向 RNN 网络,结构如下:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图9

图 5 单层双向 RNN 网络

而对于深层双向 RNN 网络,主要有 2 种不同的实现:

  1. tf.nn.bidirectional_dynamic_rnn

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图10

图 6 深层双向 RNN 网络

  1. tf.contrib.rnn.stack_bidirectional_dynamic_rnn

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图11

图 7 stack 形深层双向 RNN 网络

在 CRNN 中显然使用了第二种 stack 形深层双向结构。

由于 CNN 输出的 Feature map 是[转]一文读懂CRNN CTC文字识别 - 知乎 - 图12
大小,所以对于 RNN 最大时间长度 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图13
(即有 25 个时间输入,每个输入 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图14
列向量有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图15
)。

  • Transcription Layers

将 RNN 输出做 softmax 后,为字符输出。

关于代码中输入图片大小的解释:

在本文给出的实现中,为了将特征输入到 Recurrent Layers,做如下处理:

所以在处理输入图像的时候,建议在保持长宽比的情况下将高缩放到 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图16
,这样能够尽量不破坏图像中的文本细节(当然也可以将输入图像缩放到固定宽度,但是这样由于破坏文本的形状,肯定会造成性能下降)。

考虑训练 Recurrent Layers 时的一个问题:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图17

图 8 感受野与 RNN 标签的关系

对于 Recurrent Layers,如果使用常见的 Softmax cross-entropy loss,则每一列输出都需要对应一个字符元素。那么训练时候每张样本图片都需要标记出每个字符在图片中的位置,再通过 CNN 感受野对齐到 Feature map 的每一列获取该列输出对应的 Label 才能进行训练,如图 9。

在实际情况中,标记这种对齐样本非常困难(除了标记字符,还要标记每个字符的位置),工作量非常大。另外,由于每张样本的字符数量不同,字体样式不同,字体大小不同,导致每列输出并不一定能与每个字符一一对应。

当然这种问题同样存在于语音识别领域。例如有人说话快,有人说话慢,那么如何进行语音帧对齐,是一直以来困扰语音识别的巨大难题。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图18

图 9

所以 CTC 提出一种对不需要对齐的 Loss 计算方法,用于训练网络,被广泛应用于文本行识别和语音识别中。

Connectionist Temporal Classification(CTC) 详解

在分析过程中尽量保持和原文符号一致。

整个 CRNN 的流程如图 10。先通过 CNN 提取文本图片的 Feature map,然后将每一个 channel 作为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图19
的时间序列输入到 LSTM 中。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图20

图 10 CRNN+CTC 框架

这里约定下文中的时间序列 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图21
都从 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图22
开始,即 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图23
。 为了说明问题,我们定义:

  • CNN Feature map

Feature map 定义为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图24

Feature map 的每一列作为一个时间片输入到 LSTM 中。设 Feature map 大小为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图25
(图 10 中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图26
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图27
)。其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图28
每一列 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图29
为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图30

  • LSTM

LSTM 的每一个时间片后接 softmax,输出 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图31
是一个后验概率矩阵,定义为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图32

其中, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图33
的每一列 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图34
为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图35

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图36
代表需要识别的字符集合长度。由于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图37
是概率,所以服从概率假设:[转]一文读懂CRNN CTC文字识别 - 知乎 - 图38

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图39
每一列进行 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图40
操作,即可获得每一列输出字符的类别。

那么 LSTM 可以表示为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图41

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图42
代表 LSTM 的参数。LSTM 在输入和输出间做了如下变换:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图43
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图44

图 11

  • 空白 blank 符号

如果要进行 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图45
的 26 个英文字符识别,考虑到有的位置没有字符,定义插入 blank 的字符集合:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图46

其中 blank 表示当前列对应的图像位置没有字符(下文以[转]一文读懂CRNN CTC文字识别 - 知乎 - 图47
符号表示 blank)。

  • 关于[转]一文读懂CRNN CTC文字识别 - 知乎 - 图48
    变换

定义变换 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图49
如下(原文是大写的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图50
,知乎没这个符号):

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图51

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图52
是上述加入 blank 的长度为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图53
的字符集合,经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图54
变换后得到原始 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图55
,显然对于[转]一文读懂CRNN CTC文字识别 - 知乎 - 图56
的最大长度有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图57

举例说明,当 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图58
时:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图59

对于字符间有 blank 符号的则不合并:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图60

当获得 LSTM 输出[转]一文读懂CRNN CTC文字识别 - 知乎 - 图61
后进行[转]一文读懂CRNN CTC文字识别 - 知乎 - 图62
变换,即可获得输出结果。显然 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图63 变换不是单对单映射,例如对于不同的[转]一文读懂CRNN CTC文字识别 - 知乎 - 图64
都可获得英文单词 state。同时 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图65
成立。

那么 CTC 怎么做?

对于 LSTM 给定输入 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图66
的情况下,输出为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图67
的概率为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图68

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图69
代表所有经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图70
变换后是 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图71
的路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图72

其中,对于任意一条路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图73
有:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图74

注意这里的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图75
中的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图76
,下标 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图77
表示 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图78
路径的每一个时刻;而上面 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图79
的下标表示不同的路径。两个下标含义不同注意区分。

*注意上式 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图80
成立有条件,此项不做进一步讨论,有兴趣的读者请自行研究。

如对于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图81
的路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图82
来说:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图83

实际情况中一般手工设置 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图84
,所以有非常多条 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图85
路径,即 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图86
非常大,无法逐条求和直接计算 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图87
。所以需要一种快速计算方法。

CTC 的训练目标

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图88

图 14

CTC 的训练过程,本质上是通过梯度 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图89
调整 LSTM 的参数 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图90
,使得对于输入样本为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图91
时使得 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图92
取得最大。

例如下面图 14 的训练样本,目标都是使得 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图93
时的输出 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图94
变大。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图95

图 14

CTC 借用了 HMM 的 “向前—向后”(forward-backward) 算法来计算 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图96

要计算 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图97
,由于有 blank 的存在,定义路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图98
为在路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图99
每两个元素以及头尾插入 blank。那么对于任意的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图100
都有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图101
(其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图102
)。如:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图103

显然 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图104
,其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图105
是路径的最大长度,如上述例子中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图106

定义所有经 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图107
变换后结果是 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图108
且在 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图109
时刻结果为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图110
(记为[转]一文读懂CRNN CTC文字识别 - 知乎 - 图111
)的路径集合为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图112

求导:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图113

注意上式中第二项与 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图114
无关,所以:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图115

而上述 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图116
就是恰好与概率 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图117
相关的路径,即 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图118
时刻都经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图119
([转]一文读懂CRNN CTC文字识别 - 知乎 - 图120
)。

举例说明,还是看上面的例子 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图121
(这里的下标 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图122
代表不同的路径):

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图123

图 15

蓝色路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图124

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图125

红色路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图126

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图127

还有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图128
没有画出来。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图129
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图130
时恰好都经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图131
(此处下标代表路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图132
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图133
时刻的字符)。所有类似于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图134
经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图135
变换后结果是 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图136
且在 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图137
的路径集合表示为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图138

观察 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图139
。记 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图140
蓝色为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图141
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图142
红色路径为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图143
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图144
可以表示:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图145

那么 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图146
可以表示为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图147

计算:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图148

为了观察规律,单独计算 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图149

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图150

不妨令:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图151

那么[转]一文读懂CRNN CTC文字识别 - 知乎 - 图152
可以表示为:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图153

推广一下,所有经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图154
变换为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图155
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图156
的路径(即 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图157
)可以写成如下形式:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图158

进一步推广,所有经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图159
变换为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图160
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图161
的路径(即 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图162
)也都可以写作:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图163

所以,定义前向递推概率和 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图164

对于一个长度为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图165
的路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图166
,其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图167
代表该路径前 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图168
个字符, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图169
代表后 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图170
个字符。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图171

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图172
表示前 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图173
个字符 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图174
经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图175
变换为的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图176
的前半段子路径。 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图177
代表了 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图178
时刻经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图179
的路径概率中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图180
概率之和,即前向递推概率和。

由于当 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图181
时路径只能从 blank 或 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图182
开始,所以 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图183
有如下性质:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图184

如上面的例子中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图185
, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图186
, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图187
。对于所有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图188
路径,当 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图189
时只能从 blank 和 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图190
字符开始。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图191

图 16

图 16 是 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图192
时经过压缩路径后能够变为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图193
的所有路径 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图194
。观察图 15 会发现对于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图195
有如下递推关系:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图196

也就是说,如果 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图197
时刻是字符 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图198
,那么 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图199
时刻只可能是字符 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图200
三选一,否则经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图201
变换后无法压缩成 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图202

那么更一般的:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图203

同理,定义反向递推概率和 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图204

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图205

其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图206
表示后 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图207
个字符 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图208
经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图209
变换为的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图210
的后半段子路径。 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图211
代表了 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图212
时刻经过 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图213
的路径概率中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图214
概率之和,即反向递推概率和。

由于当 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图215
时路径只能以 blank 或 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图216
结束,所以有如下性质:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图217

如上面的例子中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图218
, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图219
, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图220
, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图221
。对于所有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图222
路径,当 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图223
时只能以 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图224
(blank 字符)或 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图225
字符结束。

观察图 15 会发现对于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图226
有如下递推关系

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图227

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图228
同理,对于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图229
有如下递推关系:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图230

那么 forward 和 backward 相乘有:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图231

或:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图232

注意, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图233
可以通过图 16 的关系对应,如 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图234
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图235

对比 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图236
:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图237

可以得到 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图238
与 forward 和 backward 递推公式之间的关系:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图239

* 为什么有上式 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图240
成立呢?

回到图 15,为了方便分析,假设只有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图241
共 4 条在 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图242
时刻经过字符 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图243
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图244
变换为 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图245
的路径,即 :

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图246

那么此时(注意虽然表示路径用 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图247
加法,但是由于 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图248
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图249
两件独立事情同时发生,所以 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图250
路径的概率 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图251
是乘法):

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图252

则有:

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图253

训练 CTC

对于 LSTM,有训练集合 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图254
,其中 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图255
是图片经过 CNN 计算获得的 Feature map, [转]一文读懂CRNN CTC文字识别 - 知乎 - 图256
是图片对应的 OCR 字符 label(label 里面没有 blank 字符)。

现在我们要做的事情就是:通过梯度[转]一文读懂CRNN CTC文字识别 - 知乎 - 图257
调整 LSTM 的参数[转]一文读懂CRNN CTC文字识别 - 知乎 - 图258
,使得对于输入样本为[转]一文读懂CRNN CTC文字识别 - 知乎 - 图259
时有 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图260
取得最大。所以如何计算梯度才是核心。

单独来看 CTC 输入(即 LSTM 输出) [转]一文读懂CRNN CTC文字识别 - 知乎 - 图261
矩阵中的某一个值 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图262
(注意 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图263
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图264
含义相同,都是在 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图265
[转]一文读懂CRNN CTC文字识别 - 知乎 - 图266
的概率):

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图267

上式中的 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图268
是通过递推计算的常数,任何时候都可以通过递推快速获得,那么即可快速计算梯度 [转]一文读懂CRNN CTC文字识别 - 知乎 - 图269
,之后按照梯度训练即可。

CTC 编程接口

在 Tensorflow 中官方实现了 CTC 接口:

  1. tf.nn.ctc_loss(
  2. labels,
  3. inputs,
  4. sequence_length,
  5. preprocess_collapse_repeated=False,
  6. ctc_merge_repeated=True,
  7. ignore_longer_outputs_than_inputs=False,
  8. time_major=True
  9. )

在 Pytorch 中需要使用针对框架编译的 warp-ctc:https://github.com/SeanNaren/warp-ctc

2020.4 更新,目前 Pytorch 已经有 CTC 接口:

  1. torch.nn.CTCLoss(blank=0,reduction='mean',zero_infinity=False

CTC 总结

CTC 是一种 Loss 计算方法,用 CTC 代替 Softmax Loss,训练样本无需对齐。CTC 特点:

  • 引入 blank 字符,解决有些位置没有字符的问题
  • 通过递推,快速计算梯度

看到这里你也应该大致了解 MFCC+CTC 在语音识别中的应用了(图 17 来源)。

[转]一文读懂CRNN CTC文字识别 - 知乎 - 图270

图 17 MFCC+CTC 在语音识别中的应用

CRNN+CTC 总结

这篇文章的核心,就是将 CNN/LSTM/CTC 三种方法结合:

  • 首先 CNN 提取图像卷积特征
  • 然后 LSTM 进一步提取图像卷积特征中的序列特征
  • 最后引入 CTC 解决训练时字符无法对齐的问题

即提供了一种 end2end 文字图片识别算法,也算是方向的简单入门。

特别说明

一般情况下对一张图像中的文字进行识别需要以下步骤

  1. 定位文稿中的图片,表格,文字区域,区分文字段落(版面分析)
  2. 进行文本行识别(识别)
  3. 使用 NLP 相关算法对文字识别结果进行矫正(后处理)

本文介绍的 CRNN 框架只是步骤 2 的一种识别算法,其他非本文内容。CTC 你学会 (fei) 了么?

https://zhuanlan.zhihu.com/p/43534801