文字识别也是图像领域一个常见问题。然而,对于自然场景图像,首先要定位图像中的文字位置,然后才能进行识别。
所以一般来说,从自然场景图片中进行文字识别,需要包括 2 个步骤:
- 文字检测:解决的问题是哪里有文字,文字的范围有多少
- 文字识别:对定位好的文字区域进行识别,主要解决的问题是每个文字是什么,将图像中的文字区域进转化为字符信息。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图1](/uploads/projects/ixxw@ai/2bc6c963900934f11ab5a84e9ce55230.jpeg)
图 1 文字识别的步骤
文字检测类似于目标检测,即用 box 标识出图像中所有文字位置。对于文字检测不了解的读者,请参考本专栏文章:
本文的重点是如何对已经定位好的文字区域图片进行识别。 假设之前已经文字检测算法已经定位图中的 “subway” 区域(红框),接下来就是文字识别。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图2](/uploads/projects/ixxw@ai/c08a9b5a1d0b8a757bf3e825275520c8.jpeg)
图 2 文字检测定位文字图像区域
基于 RNN 文字识别算法主要有两个框架:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图3](/uploads/projects/ixxw@ai/90a5a8bd9271e3e0ba5451798f808f10.jpeg)
图 3 基于 RNN 文字识别 2 种基本算法框架
- CNN+RNN+CTC(CRNN+CTC)
- CNN+Seq2Seq+Attention
本文主要介绍第一种框架 CRNN+CTC,对应 TensorFlow 1.15 实现代码如下。本文介绍的 CRNN 网络结构都基于此代码。另外该代码已经支持不定长英文识别。
需要说明该代码非常简单,只用于原理介绍,也无法识别中文。
CRNN 基本网络结构
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图4](/uploads/projects/ixxw@ai/30534a0a7a3d1ceb37b96b8fd6a4e822.jpeg)
图 4 CRNN 网络结构(此图按照本文给出的 github 实现代码画的)
整个 CRNN 网络可以分为三个部分:
假设输入图像大小为
,注意提及图像都是
形式。
- Convlutional Layers
这里的卷积层就是一个普通的 CNN 网络,用于提取输入图像的 Convolutional feature maps,即将大小为
的图像转换为
大小的卷积特征矩阵,网络细节请参考本文给出的实现代码。
- Recurrent Layers
这里的循环网络层是一个深层双向 LSTM 网络,在卷积特征的基础上继续提取文字序列特征。对 RNN 不了解的读者,建议参考:
所谓深层 RNN 网络,是指超过两层的 RNN 网络。对于单层双向 RNN 网络,结构如下:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图9](/uploads/projects/ixxw@ai/e6c4db8be8de2dabaab7bb8f7b017f1e.png)
图 5 单层双向 RNN 网络
而对于深层双向 RNN 网络,主要有 2 种不同的实现:
tf.nn.bidirectional_dynamic_rnn
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图10](/uploads/projects/ixxw@ai/f766c3f2e281064eb51deb3778e5c1a2.png)
图 6 深层双向 RNN 网络
tf.contrib.rnn.stack_bidirectional_dynamic_rnn
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图11](/uploads/projects/ixxw@ai/f6ee6a9e7dbd1dd60004b47afe21b8dd.png)
图 7 stack 形深层双向 RNN 网络
在 CRNN 中显然使用了第二种 stack 形深层双向结构。
由于 CNN 输出的 Feature map 是
大小,所以对于 RNN 最大时间长度
(即有 25 个时间输入,每个输入
列向量有
)。
- Transcription Layers
将 RNN 输出做 softmax 后,为字符输出。
关于代码中输入图片大小的解释:
在本文给出的实现中,为了将特征输入到 Recurrent Layers,做如下处理:
所以在处理输入图像的时候,建议在保持长宽比的情况下将高缩放到
,这样能够尽量不破坏图像中的文本细节(当然也可以将输入图像缩放到固定宽度,但是这样由于破坏文本的形状,肯定会造成性能下降)。
考虑训练 Recurrent Layers 时的一个问题:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图17](/uploads/projects/ixxw@ai/6a715de8976d0ed42d0005f1c9d20512.jpeg)
图 8 感受野与 RNN 标签的关系
对于 Recurrent Layers,如果使用常见的 Softmax cross-entropy loss,则每一列输出都需要对应一个字符元素。那么训练时候每张样本图片都需要标记出每个字符在图片中的位置,再通过 CNN 感受野对齐到 Feature map 的每一列获取该列输出对应的 Label 才能进行训练,如图 9。
在实际情况中,标记这种对齐样本非常困难(除了标记字符,还要标记每个字符的位置),工作量非常大。另外,由于每张样本的字符数量不同,字体样式不同,字体大小不同,导致每列输出并不一定能与每个字符一一对应。
当然这种问题同样存在于语音识别领域。例如有人说话快,有人说话慢,那么如何进行语音帧对齐,是一直以来困扰语音识别的巨大难题。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图18](/uploads/projects/ixxw@ai/4cc4896187edf8a21ddb12cf5e945b74.jpeg)
图 9
所以 CTC 提出一种对不需要对齐的 Loss 计算方法,用于训练网络,被广泛应用于文本行识别和语音识别中。
Connectionist Temporal Classification(CTC) 详解
在分析过程中尽量保持和原文符号一致。
整个 CRNN 的流程如图 10。先通过 CNN 提取文本图片的 Feature map,然后将每一个 channel 作为
的时间序列输入到 LSTM 中。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图20](/uploads/projects/ixxw@ai/6ef1b97a5f6862c2cb31fc74af87718f.jpeg)
图 10 CRNN+CTC 框架
这里约定下文中的时间序列
都从
开始,即
。 为了说明问题,我们定义:
- CNN Feature map
Feature map 定义为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图24](/uploads/projects/ixxw@ai/800e44f2f3771eafa69cad69973002bf.png)
Feature map 的每一列作为一个时间片输入到 LSTM 中。设 Feature map 大小为
(图 10 中
,
)。其中
每一列
为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图30](/uploads/projects/ixxw@ai/5f19368a68064a4deb607634aa45db5b.png)
- LSTM
LSTM 的每一个时间片后接 softmax,输出
是一个后验概率矩阵,定义为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图32](/uploads/projects/ixxw@ai/424432648c1838f0c658d0b46a5073d6.png)
其中,
的每一列
为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图35](/uploads/projects/ixxw@ai/5b6402f6aa6d4c2634d747c4f73dffa1.png)
其中
代表需要识别的字符集合长度。由于
是概率,所以服从概率假设:
对
每一列进行
操作,即可获得每一列输出字符的类别。
那么 LSTM 可以表示为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图41](/uploads/projects/ixxw@ai/501f31dc21aee897ccf3ff687040af65.png)
其中
代表 LSTM 的参数。LSTM 在输入和输出间做了如下变换:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图43](/uploads/projects/ixxw@ai/b34734c9d8db14b6150ebb54d6509acc.png)
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图44](/uploads/projects/ixxw@ai/319a6207622af48fcf1a801ce4d1b898.png)
图 11
- 空白 blank 符号
如果要进行
的 26 个英文字符识别,考虑到有的位置没有字符,定义插入 blank 的字符集合:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图46](/uploads/projects/ixxw@ai/9ecafdd51c08fc6e0d79c04b577b4808.png)
其中 blank 表示当前列对应的图像位置没有字符(下文以
符号表示 blank)。
- 关于
变换
定义变换
如下(原文是大写的
,知乎没这个符号):
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图51](/uploads/projects/ixxw@ai/96f575de4ba281389cc0d597e7ace018.png)
其中
是上述加入 blank 的长度为
的字符集合,经过
变换后得到原始
,显然对于
的最大长度有
。
举例说明,当
时:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图59](/uploads/projects/ixxw@ai/ece222abd793339d4fd740c4f926d398.png)
对于字符间有 blank 符号的则不合并:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图60](/uploads/projects/ixxw@ai/68dea422628b490a8735c3a00ade3eb0.png)
当获得 LSTM 输出
后进行
变换,即可获得输出结果。显然 变换不是单对单映射,例如对于不同的
都可获得英文单词 state。同时
成立。
那么 CTC 怎么做?
对于 LSTM 给定输入
的情况下,输出为
的概率为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图68](/uploads/projects/ixxw@ai/6ff3fdb3514eccca24bc436bd5e650d7.png)
其中
代表所有经过
变换后是
的路径
。
其中,对于任意一条路径
有:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图74](/uploads/projects/ixxw@ai/4b9abfd532efb3c6b9e77f54f84f3319.png)
注意这里的
中的
,下标
表示
路径的每一个时刻;而上面
的下标表示不同的路径。两个下标含义不同注意区分。
*注意上式
成立有条件,此项不做进一步讨论,有兴趣的读者请自行研究。
如对于
的路径
来说:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图83](/uploads/projects/ixxw@ai/73d686266dd8db3d6cd6d5afb635e642.png)
实际情况中一般手工设置
,所以有非常多条
路径,即
非常大,无法逐条求和直接计算
。所以需要一种快速计算方法。
CTC 的训练目标
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图88](/uploads/projects/ixxw@ai/1925f2c418237e8a6966f96ba8957ca6.png)
图 14
CTC 的训练过程,本质上是通过梯度
调整 LSTM 的参数
,使得对于输入样本为
时使得
取得最大。
例如下面图 14 的训练样本,目标都是使得
时的输出
变大。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图95](/uploads/projects/ixxw@ai/ad3333785ea9247f0e8e0894a62714f4.png)
图 14
CTC 借用了 HMM 的 “向前—向后”(forward-backward) 算法来计算
要计算
,由于有 blank 的存在,定义路径
为在路径
每两个元素以及头尾插入 blank。那么对于任意的
都有
(其中
)。如:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图103](/uploads/projects/ixxw@ai/e0390817740fdd547a1ab91bf432328f.png)
显然
,其中
是路径的最大长度,如上述例子中
。
定义所有经
变换后结果是
且在
时刻结果为
(记为
)的路径集合为
。
求导:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图113](/uploads/projects/ixxw@ai/ff0acb7a2f70508cdcacd3426a0e80f6.png)
注意上式中第二项与
无关,所以:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图115](/uploads/projects/ixxw@ai/8474a4660774ac54c2137b364817a209.png)
而上述
就是恰好与概率
相关的路径,即
时刻都经过
(
)。
举例说明,还是看上面的例子
(这里的下标
代表不同的路径):
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图123](/uploads/projects/ixxw@ai/bd5f0a93226e52134ec16c30be60769b.jpeg)
图 15
蓝色路径
:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图125](/uploads/projects/ixxw@ai/618e43e04d7dca79bd30b0faa77a48c8.png)
红色路径
:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图127](/uploads/projects/ixxw@ai/fd3bf90962073026c4a205a8abfbaa6e.png)
还有
没有画出来。
而
在
时恰好都经过
(此处下标代表路径
的
时刻的字符)。所有类似于
经过
变换后结果是
且在
的路径集合表示为
。
观察
。记
蓝色为
,
红色路径为
,
可以表示:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图145](/uploads/projects/ixxw@ai/0f61df2181800352e945a1ec199d16ac.png)
那么
可以表示为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图147](/uploads/projects/ixxw@ai/ba89370edff7e67b9e1c976750abb779.png)
计算:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图148](/uploads/projects/ixxw@ai/af03a48b914d9a303cbe82f5f422994f.png)
为了观察规律,单独计算
。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图150](/uploads/projects/ixxw@ai/f005eeba1c8bc20490639c9591f91c8c.jpeg)
不妨令:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图151](/uploads/projects/ixxw@ai/364342cc69cb2dd0f4d09044caa094c6.png)
那么
可以表示为:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图153](/uploads/projects/ixxw@ai/3991b6350059cbe7b7d2b08a5cc1e3a8.png)
推广一下,所有经过
变换为
且
的路径(即
)可以写成如下形式:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图158](/uploads/projects/ixxw@ai/0eb4ff97c20921af0759a0d30960736b.png)
进一步推广,所有经过
变换为
且
的路径(即
)也都可以写作:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图163](/uploads/projects/ixxw@ai/8a86d496db3adde22883befd6e03c3de.png)
所以,定义前向递推概率和
:
对于一个长度为
的路径
,其中
代表该路径前
个字符,
代表后
个字符。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图171](/uploads/projects/ixxw@ai/15bc42719a1d7143cea955091f56b3ca.png)
其中
表示前
个字符
经过
变换为的
的前半段子路径。
代表了
时刻经过
的路径概率中
概率之和,即前向递推概率和。
由于当
时路径只能从 blank 或
开始,所以
有如下性质:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图184](/uploads/projects/ixxw@ai/76911067232e62e0fabefb7f2d13363c.jpeg)
如上面的例子中
,
,
。对于所有
路径,当
时只能从 blank 和
字符开始。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图191](/uploads/projects/ixxw@ai/7b5191b60239698b63cdc2bd09435ace.jpeg)
图 16
图 16 是
时经过压缩路径后能够变为
的所有路径
。观察图 15 会发现对于
有如下递推关系:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图196](/uploads/projects/ixxw@ai/cb3104186ae05742813dcceb5d19d7ee.png)
也就是说,如果
时刻是字符
,那么
时刻只可能是字符
三选一,否则经过
变换后无法压缩成
。
那么更一般的:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图203](/uploads/projects/ixxw@ai/cbfeb86ef565e2fb0c6747da8fa3b222.png)
同理,定义反向递推概率和
:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图205](/uploads/projects/ixxw@ai/a9c16adbfbcd0dd1ff4c75022a40e029.png)
其中
表示后
个字符
经过
变换为的
的后半段子路径。
代表了
时刻经过
的路径概率中
概率之和,即反向递推概率和。
由于当
时路径只能以 blank 或
结束,所以有如下性质:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图217](/uploads/projects/ixxw@ai/5ecf493e6a20893f0508cf945e15cb43.jpeg)
如上面的例子中
,
,
,
。对于所有
路径,当
时只能以
(blank 字符)或
字符结束。
观察图 15 会发现对于
有如下递推关系
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图227](/uploads/projects/ixxw@ai/256f870b6aa6e52dee3fce280df83a54.png)
与
同理,对于
有如下递推关系:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图230](/uploads/projects/ixxw@ai/de8b98f5473243ad1a961f1276a414a8.png)
那么 forward 和 backward 相乘有:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图231](/uploads/projects/ixxw@ai/f116f1e176dd36b62483829ebc723d4b.png)
或:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图232](/uploads/projects/ixxw@ai/83da3a79f73c8b478ede6c799c8a5181.png)
注意,
可以通过图 16 的关系对应,如
,
。
对比
:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图237](/uploads/projects/ixxw@ai/f774fe55965815b1b6f2fdcf0ecc141f.png)
可以得到
与 forward 和 backward 递推公式之间的关系:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图239](/uploads/projects/ixxw@ai/4acee5cc65d7fd9b27d5eb2a552b7e9a.png)
* 为什么有上式
成立呢?
回到图 15,为了方便分析,假设只有
共 4 条在
时刻经过字符
且
变换为
的路径,即 :
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图246](/uploads/projects/ixxw@ai/97b042944f6854766ffeaa04b981298f.jpeg)
那么此时(注意虽然表示路径用
加法,但是由于
和
两件独立事情同时发生,所以
路径的概率
是乘法):
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图252](/uploads/projects/ixxw@ai/4048cbb0a188544c15a8ef6603c86526.png)
则有:
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图253](/uploads/projects/ixxw@ai/7f258ffd1cadc9a5644db2cb73f92bcc.png)
训练 CTC
对于 LSTM,有训练集合
,其中
是图片经过 CNN 计算获得的 Feature map,
是图片对应的 OCR 字符 label(label 里面没有 blank 字符)。
现在我们要做的事情就是:通过梯度
调整 LSTM 的参数
,使得对于输入样本为
时有
取得最大。所以如何计算梯度才是核心。
单独来看 CTC 输入(即 LSTM 输出)
矩阵中的某一个值
(注意
与
含义相同,都是在
时
的概率):
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图267](/uploads/projects/ixxw@ai/5895247b029f33ce0653f04c61950653.png)
上式中的
是通过递推计算的常数,任何时候都可以通过递推快速获得,那么即可快速计算梯度
,之后按照梯度训练即可。
CTC 编程接口
在 Tensorflow 中官方实现了 CTC 接口:
tf.nn.ctc_loss(labels,inputs,sequence_length,preprocess_collapse_repeated=False,ctc_merge_repeated=True,ignore_longer_outputs_than_inputs=False,time_major=True)
在 Pytorch 中需要使用针对框架编译的 warp-ctc:https://github.com/SeanNaren/warp-ctc
2020.4 更新,目前 Pytorch 已经有 CTC 接口:
torch.nn.CTCLoss(blank=0,reduction='mean',zero_infinity=False)
CTC 总结
CTC 是一种 Loss 计算方法,用 CTC 代替 Softmax Loss,训练样本无需对齐。CTC 特点:
- 引入 blank 字符,解决有些位置没有字符的问题
- 通过递推,快速计算梯度
看到这里你也应该大致了解 MFCC+CTC 在语音识别中的应用了(图 17 来源)。
![[转]一文读懂CRNN CTC文字识别 - 知乎 - 图270](/uploads/projects/ixxw@ai/984395ac9b6c1f7bd7d10caf87b37c0b.jpeg)
图 17 MFCC+CTC 在语音识别中的应用
CRNN+CTC 总结
这篇文章的核心,就是将 CNN/LSTM/CTC 三种方法结合:
- 首先 CNN 提取图像卷积特征
- 然后 LSTM 进一步提取图像卷积特征中的序列特征
- 最后引入 CTC 解决训练时字符无法对齐的问题
即提供了一种 end2end 文字图片识别算法,也算是方向的简单入门。
特别说明
一般情况下对一张图像中的文字进行识别需要以下步骤
- 定位文稿中的图片,表格,文字区域,区分文字段落(版面分析)
- 进行文本行识别(识别)
- 使用 NLP 相关算法对文字识别结果进行矫正(后处理)
本文介绍的 CRNN 框架只是步骤 2 的一种识别算法,其他非本文内容。CTC 你学会 (fei) 了么?
