简介

CRNN 全称为 Convolutional Recurrent Neural Network,是一种卷积循环神经网络结构,主要用于端到端地对不定长的文本序列进行识别,不用先对单个文字进行切割,而是将文本识别转化为时序依赖的序列学习问题,就是基于图像的序列识别。
CRNN可以用于解决基于图像的序列识别问题,特别是场景文字识别问题

构成

整个CRNN网络结构包含三部分,从下到上依次为:

  • CNN(卷积层),使用深度CNN,对输入图像提取特征,得到特征图;
  • RNN(循环层),使用双向RNN(BLSTM)对特征序列进行预测,对序列中的每个特征向量进行学习,并输出预测标签(真实值)分布;
  • CTC loss(转录层),使用 CTC 损失,把从循环层获取的一系列标签分布转换成最终的标签序列。

CNN

卷积层的结构图:
在这里插入图片描述

一共有四个最大池化层,但是最后两个池化层的窗口尺寸由 2x2 改为 1x2,也就是图片的高度减半了四次,而宽度则只减半了两次,因为文本图像多数都是高较小而宽较长,所以其feature map也是这种高小宽长的矩形形状,如果使用1×2的池化窗口可以尽量保证不丢失在宽度方向的信息,更适合英文字母识别(比如区分i和l)。

CRNN 还引入了BatchNormalization模块,加速模型收敛,缩短训练过程。

输入图像为灰度图像(单通道);高度为32,这是固定的,图片通过 CNN 后,高度就变为1,这点很重要;宽度为160,宽度也可以为其他的值,但需要统一,所以输入CNN的数据尺寸为 (channel, height, width)=(1, 32, 160)。

CNN的输出尺寸为 (512, 1, 40)。即 CNN 最后得到512个特征图,每个特征图的高度为1,宽度为40。
在这里插入图片描述

Map-to-Sequence

不能直接把 CNN 得到的特征图送入 RNN 进行训练的,需要进行一些调整,根据特征图提取 RNN 需要的特征向量序列
在这里插入图片描述

需要从 CNN 模型产生的特征图中提取特征向量序列,每一个特征向量(如上图中的一个红色框)在特征图上按列从左到右生成,每一列包含512维特征,这意味着第 i 个特征向量是所有的特征图第 i 列像素的连接,这些特征向量就构成一个序列。

由于卷积层,最大池化层和激活函数在局部区域上执行,因此它们是平移不变的。因此,特征图的每列(即一个特征向量)对应于原始图像的一个矩形区域(称为感受野),并且这些矩形区域与特征图上从左到右的相应列具有相同的顺序。特征序列中的每个向量关联一个感受野。

具体来讲,这40个序列向量,分别以stride=4,与原图相对应,用来对原图的相关区域进行分类。
在这里插入图片描述
这些特征向量序列就作为循环层的输入,每个特征向量作为 RNN 在一个时间步(time step)的输入。

图解

在这里插入图片描述

RNN

因为 RNN 有梯度消失的问题,不能获取更多上下文信息,所以 CRNN 中使用的是 LSTM,LSTM 的特殊设计允许它捕获长距离依赖。

LSTM 是单向的,它只使用过去的信息。然而,在基于图像的序列中,两个方向的上下文是相互有用且互补的。将两个LSTM,一个向前和一个向后组合到一个双向LSTM中。此外,可以堆叠多层双向LSTM,深层结构允许比浅层抽象更高层次的抽象。

这里采用的是两层各256单元的双向 LSTM 网络:
在这里插入图片描述
通过上面一步,我们得到了40个特征向量,每个特征向量长度为512,在 LSTM 中一个时间步就传入一个特征向量进行分类,这里一共有40个时间步。

我们知道一个特征向量就相当于原图中的一个小矩形区域,RNN 的目标就是预测这个矩形区域为哪个字符,即根据输入的特征向量,进行预测,得到所有字符的softmax概率分布,这是一个长度为字符类别数的向量,作为CTC层的输入。

因为每个时间步都会有一个输入特征向量 x T x^T xT,输出一个所有字符的概率分布 y T y^T yT ,所以输出为 40 个长度为字符类别数的向量构成的后验概率矩阵。
在这里插入图片描述
然后将这个后验概率矩阵传入转录层

ctcloss

这一层为转录层,转录是将 RNN 对每个特征向量所做的预测转换成标签序列的过程。数学上,转录是根据每帧预测找到具有最高概率组合的标签序列。

OCR识别的难点在于怎么处理不定长序列对齐的问题。OCR可建模为时序依赖的文本图像问题,然后使用CTC(Connectionist Temporal Classification, CTC)的损失函数来对 CNN 和 RNN 进行端到端的联合训练。

序列合并机制

将 RNN 输出的序列翻译成最终的识别结果,RNN进行时序分类时,不可避免地会出现很多冗余信息,比如一个字母被连续识别两次,这就需要一套去冗余机制

推理过程

以一张图像输入为例(batch_size为1),对CRNN的整个过程进行输入输出尺寸的描述
在这里插入图片描述

  • 首先要将图片Resize到[200,32]大小,200为图片宽度,这个参数与要预测的字符长度息息相关,经过CNN提取特征(这里的CNN可以任意设置,较常用的是VGG,当然是截取VGG的一部分,要保证输出的特征图高度为1)
  • 如果原有的VGG无法保证图片输入到输入恰好使得高为1,可以手动加一层卷积,特征图的宽此时变为50,相应的如果一开始resize输入的宽较大,这里的特征图的宽也会较大;这里的维度发生了一些变化,是为了便于输入到RNN以及后续ctcloss的调用,上图虚线可以看出,特征图的这个50可以认为是对应原图的50份纵向分割,也就是这张图片要被从左到右预测的次数,将其作为一个时序输入RNN
  • RNN一般使用双向LSTM网络,序列的前向信息和后向信息都有助于序列的预测,输出的时候,也就是RNN最后的嵌入层的输出维度为总共要预测的字符数+1(blank),最后的输出可以认为是一种概率,最后进行解码即可
编解码过程
  • 图像resize的宽在CNN输出的特征图的宽度对应了预测的时序,也就是挨着图像自左向右预测的次数,显然分隔的次数越多,就越不会漏掉其中某个字符,当然大多数情况下是预测多了的,比如图片中写的是”book“,我们的预测可能就是“bbbbbbooooooooooooookkkk”。
  • “book”作为标签如何去在网络的输出进行表示呢?
    做一个码本,将字符用索引0-25表示。比如要预测26个英文字母,那“cat”就可以表示成[2,0,19],预测(序列长度为10)就可能是[2,2,2,0,0,0,0,19,19,19]或者[2,2,0,0,0,0,0,0,0,19]。问题在于,预测的输出是明显是多于实际标签的,中间的重复怎么知道最后该保留一个还是多个呢,比如"book",如果按照上述规则得到的可是"bok"。
  • 用一个占位符"-“来处理这个问题,解决到底留几个重复字符,注意,如果我们的预测中有"-"这个字符,要注意与占位符区别开,这和处理空格问题一样,在实际操作中可以使用其他符号暂时代替“-”或者空格进行码本制作,以避免码本无法表示,待解码之后统一替换即可。在编码的时候,所有的重复字符都要用”-“隔开,这样编码肯定是没有疑问了,解码的时候,凡是相同字符间没有”-"的,统统只要一个。
  • 将“-”放在码本的0号位,预测26个英文字符就用索引1-26表示,如果我们有输出[2,2,0,0,0,15,15,0,15,11],则解码为“book”,如果是[0,0,2,15,15,15,15,0,0,11],则解码为“bok”。

代码实现

#编码过程,lexicon为字符标签,character为码本
label = [self.characters.find(c) for c in lexicon]

#解码过程,只解码一个输出列表,若解码矩阵,可分解出单个样本后进行调用
char_list = []
for i in range(len(str_index)):
if str_index[i] != 0 and (not (i > 0 and str_index[i - 1] == str_index[i])):
  char_list.append(characters[str_index[i]])
return ''.join(char_list)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐