OCR光学字符识别 – 潘登同学的NLP笔记

文章目录

    • OCR光学字符识别 — 潘登同学的NLP笔记
  • 传统的OCR方法
    • 文字行提取
      • 基于切分的方法
      • 不依赖切分的方法
  • 深度学习的方法
    • 受控场景的文字检测
    • 非受控场景的文字检测
    • 基于序列学习的文字识别
  • CTC Loss
    • 数学表达

传统的OCR方法

OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程;即,针对印刷体字符,采用光学的方式将纸质文档中的文字转换成为黑白点阵的图像文件,并通过识别软件将图像中的文字转换成文本格式;

一个OCR识别系统,其目的很简单,只是要把影像作一个转换,使影像内的图形继续保存、有表格则表格内资料及影像内的文字,一律变成计算机文字,使能达到影像资料的储存量减少、识别出的文字可再使用及分析,当然也可节省因键盘输入的人力与时间。

从影像到结果输出,须经过影像输入、影像前处理、文字特征抽取、比对识别、最后经人工校正将认错的文字更正,将结果输出。

传统步骤

  • 输入图像预处理
    • 二值化:我们需要先对彩色图进行处理,使图片只前景信息与背景信息,可以简单的定义前景信息为黑色,背景信息为白色,这就是二值化图了;
    • 噪声去除:对于不同的文档,我们对噪声的定义可以不同,根据噪声的特征进行去噪;
    • 倾斜较正:由于一般用户,在拍照文档时,都比较随意,因此拍照出来的图片不可避免的产生倾斜,这就需要文字识别软件进行较正;
  • 识别过程
    • 版面分析:将文档图片分段落,分行的过程就叫做版面分析,由于实际文档的多样性,复杂性,因此,目前还没有一个固定的,最优的切割模型;
    • 字符切割:由于拍照条件的限制,经常造成字符粘连,断笔,因此极大限制了识别系统的性能,这就需要文字识别软件有字符切割功能;
    • 字符识别:这一研究,已经是很早的事情了,比较早有模板匹配,后来以特征提取为主,由于文字的位移,笔画的粗细,断笔,粘连,旋转等因素的影响,极大影响特征的提取的难度;
  • 后处理
    • 版面恢复:人们希望识别后的文字,仍然像原文档图片那样排列着,段落不变,位置不变,顺序不变地输出到word文档、pdf文档等,这一过程就叫做版面恢复;
    • 后处理、校对:根据特定的语言上下文的关系,对识别结果进行较正,就是后处理;

传统的OCR基于图像处理(二值化、连通域分析、投影分析等)和统计机器学习(Adaboost、SVM),过去20年间在印刷体和扫描文档上取得了不错的效果。传统的印刷体OCR解决方案整体流程如图所示。

OCR光学字符识别-编程之家

传统的OCR解决方案存在着以下不足:

  • 通过版面分析(连通域分析)和行切分(投影分析)来生成文本行,要求版面结构有较强的规则性且前背景可分性强(例如黑白文档图像、车牌),无法处理前背景复杂的随意文字(例如场景文字、菜单、广告文字等)。另外,二值化操作本身对图像成像条件和背景要求比较苛刻。
  • 通过人工设计边缘方向特征(例如方向梯度直方图)来训练字符识别模型,在字体变化、模糊或背景干扰时,此类单一的特征的泛化能力迅速下降。
  • 过度依赖于字符切分的结果,在字符扭曲、粘连、噪声干扰的情况下,切分的错误传播尤其突出。
  • 尽管图像预处理模块可有效改善输入图像的质量,但多个独立的校正模块的串联必然带来误差传递。另外由于各模块优化目标独立,它们无法融合到统一的框架中进行。

行切分,投影切分例子

输入图片
OCR光学字符识别-编程之家

二值图片
OCR光学字符识别-编程之家

水平投影
OCR光学字符识别-编程之家

行切分
OCR光学字符识别-编程之家

OCR光学字符识别-编程之家

文字行提取

传统OCR采取自上而下的切分式,但它只适用于版面规则背景简单的情况。该领域还有另外两类思路

  • 自底向上的生成式方法。该类方法通过连通域分析或最大稳定极值区域(MSER)等方法提取候选区域,然后通过文字/非文字的分类器进行区域筛选,对筛选后的区域进行合并生成文字行,再进行文字行级别的过滤,如图所示。该类方法的不足是,一方面流程冗长导致的超参数过多,另一方面无法利用全局信息
    OCR光学字符识别-编程之家
  • 基于滑动窗口的方法。该类方法利用通用目标检测的思路来提取文字行信息,利用训练得到的文字行/词语/字符级别的分类器来进行全图搜索。原始的基于滑动窗口方法通过训练文字/背景二分类检测器,直接对输入图像进行多尺度的窗口扫描。检测器可以是传统机器学习模型(Adaboost、Random Ferns),也可以是深度卷积神经网络

文字行识别流程
传统OCR将文字行识别划分为字符切分和单字符识别两个独立的步骤,尽管通过训练基于卷积神经网络的单字符识别引擎可以有效提升字符识别率,但切分对于字符粘连、模糊和形变的情况的容错性较差,而且切分错误对于识别是不可修复的。因此在该框架下,文本行识别的准确率主要受限于字符切分。假设已训练单字符识别引擎的准确率p=99%,字符切分准确率为q= 95%,则对于一段长度为L的文字行,其识别的平均准确率为P= (pq)的L次方,其中L=10时,P=54.1%

由于独立优化字符切分提升空间有限,因此有相关方法试图联合优化切分和识别两个任务。现有技术主要可分为基于切分的方法(Segmentation-Based)和不依赖切分的方法(Segmentation- Free)两类方法。

基于切分的方法

该类方法还是保留主动切分的步骤,但引入了动态合并机制,通过识别置信度等信息来指导切分,如图所示。

OCR光学字符识别-编程之家

  • 过切分模块将文字行在垂直于基线方向上分割成碎片,使得其中每个碎片至多包含一个字符。通常来说,过切分模块会将字符分割为多个连续笔划。过切分可以采用基于规则或机器学习的方法。规则方法主要是直接在图像二值化的结果上进行连通域分析和投影分析来确定候补切点位置,通过调整参数可以控制粒度来使得字符尽可能被切碎。基于规则的方法实现简单,但在成像/背景复杂的条件下其效果不好。机器学习方法通过离线训练鉴别切点的二类分类器,然后基于该分类器在文字行图像上进行滑窗检测
  • 动态合并模块将相邻的笔划根据识别结果组合成可能的字符区域,最优组合方式即对应最佳切分路径和识别结果。直观来看,寻找最优组合方式可转换为路径搜索问题,对应有深度优先和广度优先两种搜索策略。深度优先策略在每一步选择扩展当前最优的状态,因此全局来看它是次优策略,不适合过长的文字行。广度优先策略在每一步会对当前多个状态同时进行扩展,比如在语音识别领域广泛应用的Viterbi解码和Beam Search。但考虑到性能,Beam Search通常会引入剪枝操作来控制路径长度,剪枝策略包含限制扩展的状态数(比如,每一步只扩展TopN的状态)和加入状态约束(比如,合并后字符形状)等;
  • 由于动态合并会产生多个候选路径,所以需要设计合适的评价函数来进行路径选择。评价函数的设计主要从路径结构损失和路径识别打分两方面出发。路径结构损失主要从字符形状特征方面衡量切分路径的合理性,路径识别打分则对应于特定切分路径下的单字平均识别置信度和语言模型分。
  • 该方案试图将字符切分和单字符识别融合在同一个框架下解决,但由于过分割是独立的步骤,因此没有从本质上实现端到端学习;

不依赖切分的方法

该类方法完全跨越了字符切分,通过滑动窗口或序列建模直接对文字行进行识别

滑窗识别借鉴了滑动窗口检测的思路,基于离线训练的单字识别引擎,对文字行图像从左到右进行多尺度扫描,以特定窗口为中心进行识别。在路径决策上可采用贪心策略或非极大值抑制(NMS)策略来得到最终的识别路径。下图给出了滑窗识别的示意流程。可见滑窗识别存在两个问题:滑动步长的粒度过细则计算代价大,过粗则上下文信息易丢失;无论采用何种路径决策方案,它们对单字识别的置信度依赖较高。
OCR光学字符识别-编程之家

序列学习起源于手写识别、语音识别领域,因为这类问题的共同特点是需要对时序数据进行建模。尽管文字行图像是二维的,但如果把从左到右的扫描动作类比为时序,文字行识别从本质上也可归为这类问题。通过端到端的学习,摒弃矫正/切分/字符识别等中间步骤,以此提升序列学习的效果,这已经成为当前研究的热点;(深度学习的方法)

深度学习的方法

深度学习的方法可以归结为两步

  • 目标检查
  • 文字识别

OCR光学字符识别-编程之家

根据版面是否有先验信息(卡片的矩形区域、证件的关键字段标识)以及文字自身的复杂性(如水平文字、多角度),图像可划分为受控场景(如身份证、营业执照、银行卡)和非受控场景(如菜单、门头图),如图所示

OCR光学字符识别-编程之家

考虑到这两类场景的特点不同,我们借鉴不同的检测框架。由于受控场景文字诸多约束条件可将问题简化,因此利用在通用目标检测领域广泛应用的Faster R-CNN框架进行检测。而对于非受控场景文字,由于形变和笔画宽度不一致等原因,目标轮廓不具备良好的闭合边界,我们需要借助图像语义分割来标记文字区域与背景区域。

受控场景的文字检测

对于受控场景(如身份证),我们将文字检测转换为对关键字目标(如姓名、身份证号、地址)或关键条目(如银行卡号)的检测问题。基于Faster R-CNN的关键字检测流程如图所示。为了保证回归框的定位精度,同时提升运算速度,我们对原有框架和训练方式进行了微调

  • 考虑到关键字或关键条目的类内变化有限,网络结构只采用了3个卷积层
  • 训练过程中提高正样本的重叠率(IOU)阈值
  • 根据关键字或关键条目的宽高比范围来适配RPN层Anchor的宽高比

OCR光学字符识别-编程之家

Faster R-CNN框架由RPN(候选区域生成网络)和RCN(区域分类网络)两个子网络组成。RPN通过监督学习的方法提取候选区域,给出的是无标签的区域和粗定位结果。RCN引入类别概念,同时进行候选区域的分类和位置回归,给出精细定位结果。训练时两个子网络通过端到端的方式联合优化。下图以银行卡卡号识别为例,给出了RPN层和RCN层的输出

OCR光学字符识别-编程之家

对于人手持证件场景,由于证件目标在图像中所占比例过小,直接提取微小候选目标会导致一定的定位精度损失。为了保证高召回和高定位精度,可采用由粗到精的策略进行检测。首先定位卡片所在区域位置,然后在卡片区域范围内进行关键字检测,而区域定位也可采用Faster R-CNN框架,如图所示。

OCR光学字符识别-编程之家

非受控场景的文字检测

对于菜单、门头图等非受控场景,由于文字行本身的多角度且字符的笔画宽度变化大,该场景下的文字行定位任务挑战很大。由于通用目标检测方法的定位粒度是回归框级,此方法适用于刚体这类有良好闭合边界的物体。然而文字往往由一系列松散的笔画构成,尤其对于任意方向或笔画宽度的文字,仅以回归框结果作为定位结果会有较大偏差。另外刚体检测的要求相对较低,即便只定位到部分主体(如定位结果与真值的重叠率是50%),也不会对刚体识别产生重大影响,而这样的定位误差对于文字识别则很可能是致命的。

为了实现足够精细的定位,我们利用语义分割中常用的全卷积网络(FCN)来进行像素级别的文字/背景标注,整体流程如图所示

OCR光学字符识别-编程之家

多尺度全卷积网络通过对多个阶段的反卷积结果的融合,实现了全局特征和局部特征的联合,进而达到了由粗到精的像素级别标注,适应于任意非受控场景(门头图、菜单图片);

基于多尺度全卷积网络得到的像素级标注,通过连通域分析技术可得到一系列连通区域(笔划信息)。但由于无法确定哪些连通域属于同一文字行,因此需要借助单链聚类技术来进行文字行提取。至于聚类涉及的距离度量,主要从连通域间的距离、形状、颜色的相似度等方面提取特征,并通过度量学习自适应地得到特征权重和阈值,如图所示

OCR光学字符识别-编程之家

基于序列学习的文字识别

我们将整行文字识别问题归结为一个序列学习问题。利用基于双向长短期记忆神经网络(Bi-directional Long Short-term Memory,BLSTM)的递归神经网络作为序列学习器,来有效建模序列内部关系。为了引入更有效的输入特征,我们采用卷积神经网络模型来进行特征提取,以描述图像的高层语义。此外在损失函数的设计方面,考虑到输出序列与输入特征帧序列无法对齐,我们直接使用结构化的Loss(序列对序列的损失),另外引入了背景(Blank)类别以吸收相邻字符的混淆性。

整体网络结构分为三层:卷积层、递归层和翻译层,如图所示。其中卷积层提取特征;递归层既学习特征序列中字符特征的先后关系,又学习字符的先后关系;翻译层实现对时间序列分类结果的解码.

OCR光学字符识别-编程之家
对于输入的固定高度h0= 36的图像(宽度任意,如W0 = 248),我们通过CNN网络结构提取特征,得到9×62×128的特征图,可将其看作一个长度为62的时间序列输入到RNN层。RNN层有400个隐藏节点,其中每个隐藏节点的输入是9×128维的特征,是对图像局部区域的描述。考虑到对应于某个时刻特征的图像区域,它与其前后内容都具有较强的相关性,所以我们一般采用双向RNN网络,如图所示

OCR光学字符识别-编程之家

而这个图中核心的就是bilstm的输出除了普通的字符之外,还有_来表示空白区域,两个_之间重复出现的往往需要被过滤器合并,满足过滤器前的所有正样本有很多种,总的来说,就是一个类似曼哈顿道路的序列路径(类似,但绝对不是) 总之,生成这样的道路可以根据规则生成,而在计算关键的CTC loss的时候,往往采用的是动态规划的思想进行计算

OCR光学字符识别-编程之家

双向RNN后接一个全连接层,输入为RNN层(在某个时刻)输出的特征图,输出为该位置是背景、字符表中文字的概率。全连接层后接CTC(联结主义时间分类器)作为损失函数。在训练时,根据每个时刻对应的文字、背景概率分布,得到真值字符串在图像中出现的概率P(ground truth),将-log(P(ground truth))作为损失函数。在测试时,CTC可以看作一个解码器,将每一时刻的预测结果(当前时刻的最大后验概率对应的字符)联合起来,然后去掉空白和重复的模式,就形成了最终的序列预测结果,如图所示

OCR光学字符识别-编程之家

从上图中也可以看出,对应输入序列中的每个字符,LSTM输出层都会产生明显的尖峰,尽管该尖峰未必对应字符的中心位置。换句话说,引入CTC机制后,我们不需要考虑每个字符出现的具体位置,只需关注整个图像序列对应的文字内容,最终实现深度学习的端到端训练与预测。

基于上述序列学习框架,我们给出了在不同场景下的文字行识别结果,如图18所示。其中前两行的图片为验证码场景,第三行为银行卡,第四行为资质证件,第五行为门头图,第六行为菜单。可以看到,识别模型对于文字形变、粘连、成像的模糊和光线变化、背景的复杂等都有较好的健壮性。

OCR光学字符识别-编程之家

CTC Loss

举个例子

  • 如果我们的标签序列,就是真实的数据“水煮肉片22元”,长度设为L,加入blank空格之后,长度为多少?

“_水_煮_肉_片_2_2_元_”
2*L+1

  • 最终将预测的序列:

    1. 连续相同的字符去重!
    2. 去除空字符!
  • 所以我们要想我们的预测序列可以经过上述的去重去空格得到正确答案,所以我们是不是在训练模型的时候,就要给RNN准备各种可能的路径~

各种可能的路径是不是要根据之前的“_水_煮_肉_片_2_2_元_”来构建

  • 为了最终去重去空格可以不会错,那么我们在构建各种可能的路径时候就需要加入一些约束条件!
    1. 方向只能向下和向右(向下其实是指斜向下)
    2. 相同的字符之间要有一个空字符(这是指22这个中间必须要有一个_,而不是不能出现‘22_2’这种情形)
    3. 非空字符不能被跳过(不能少字)
    4. 起点必须从前两个字符开始
    5. 终点必须在结尾两个字符结束

CTC Loss求解的是所有可能路径的对数概率之和最大,用到动态规划的思想;动态规划需要初始条件和状态转移方程

符号记法s表示状态(字符),t表示时刻,f表示状态概率函数, p表示该状态的输出概率(bilstm的输出概率)

  • 情况一: 第s个字符是blank(因为上一个blank后面不能跨过字符接blank)
    ft(s)=(ft−1(s)+ft−1(s−1))∗p(s)f_t(s) = (f_{t-1}(s) + f_{t-1}(s-1)) * p(s) ft(s)=(ft1(s)+ft1(s1))p(s)
  • 情况二:第s个字符和第s-2个字符相同(因为两个相同字符间必须接blank)
    ft(s)=(ft−1(s)+ft−1(s−1))∗p(s)f_t(s) = (f_{t-1}(s) + f_{t-1}(s-1)) * p(s) ft(s)=(ft1(s)+ft1(s1))p(s)
  • 其他情况
    ft(s)=(ft−1(s)+ft−1(s−1)+ft−1(s−2))∗p(s)f_t(s) = (f_{t-1}(s) + f_{t-1}(s-1) + f_{t-1}(s-2)) * p(s) ft(s)=(ft1(s)+ft1(s1)+ft1(s2))p(s)

最后把fT(S)f_T(S)fT(S)fT(S−1)f_T(S-1)fT(S1)的和最大化即可

CTC算法特性

  • 条件独立性,CTC loss最开始是用在语言识别的,后来才用于OCR

  • 比如一个人去说“三个A”,输出有两个,AAA,triple A; 它不会学到任何语言模型的知识,更像n-gram的统计语言模型

  • CTC算法不需要训练数据对齐(给规则即可)

  • CTC要求对齐的方式是单调的(水煮肉片 22元22元 水煮肉片是不同的),像机器翻译的任务就不适合用CTC Loss

  • CTC要求输入和输出是多对一的关系,有的任务是需要严格一对一关系,比如词性标注也不适合用CTC Loss

  • CTC要求输出要比输入短(如63个时刻,7个输出)

数学表达

LCTC(X,W)=−log⁡∑C:K(C)=Wp(C∣X)=−log⁡∑C:K(C)=W∏t=1Tp(ct∣X)\begin{aligned} L_{CTC}(X,W) &= -\log \sum_{C:K(C)=W}p(C|X) \\ &= -\log \sum_{C:K(C)=W}\prod_{t=1}^Tp(c_t|X) \end{aligned} LCTC(X,W)=logC:K(C)=Wp(CX)=logC:K(C)=Wt=1Tp(ctX)
其中,WWW表示满足条件的所有序列形式,T表示时刻数,p(ct∣X)p(c_t|X)p(ctX)则是LSTM给出的概率值

除此之外,还有一种表示所有路径可能的形式

  • αt(s)\alpha_t(s)αt(s)表示从开始到时刻t,状态s的所有路径的概率
    αt(s)=∑π∈K(π1:t)=l1:s∏t′=1tyπt′t′\alpha_t(s) = \sum_{\pi \in K(\pi_{1:t})=l_{1:s}} \prod_{t'=1}^ty_{\pi_t'}^{t'} αt(s)=πK(π1:t)=l1:st=1tyπtt
  • βt(s)\beta_t(s)βt(s)表示从时刻t,状态s到结束的所有路径的概率
    βt(s)=∑π∈K(πt:T)=ls:∣l∣∏t′=1tyπt′t′\beta_t(s) = \sum_{\pi \in K(\pi_{t:T})=l_{s:|l|}} \prod_{t'=1}^ty_{\pi_t'}^{t'} βt(s)=πK(πt:T)=ls:lt=1tyπtt
    很容易证明
    αt(s)βt(s)yst=∑π∈K−1(l):πt=syst∏t=1Tyπttylst=∑π∈K−1(l):πt=s∏t=1Tyπtt\frac{\alpha_t(s)\beta_t(s)}{y_{s}^t} = \frac{\sum_{\pi\in K^{-1}(l): \pi_t=s} y_{s}^t \prod_{t=1}^Ty_{\pi_t}^t}{y_{l_s}^t} = \sum_{\pi\in K^{-1}(l): \pi_t=s} \prod_{t=1}^Ty_{\pi_t}^t ystαt(s)βt(s)=ylstπK1(l):πt=systt=1Tyπtt=πK1(l):πt=st=1Tyπtt
    p(l∣x)=∑π∈K−1(l)p(π∣x)=∑π∈K−1(l)∏t=1Tyπtt=∑π∈K−1(l):πt=sαt(s)βt(s)yπttp(l|x) = \sum_{\pi\in K^{-1}(l)} p(\pi|x) = \sum_{\pi\in K^{-1}(l)} \prod_{t=1}^Ty_{\pi_t}^t = \sum_{\pi\in K^{-1}(l): \pi_t=s}\frac{\alpha_t(s)\beta_t(s)}{y_{\pi_t}^t} p(lx)=πK1(l)p(πx)=πK1(l)t=1Tyπtt=πK1(l):πt=syπttαt(s)βt(s)
    p(l∣x)p(l|x)p(lx)可以理解为所有路径之和

现在我们要做的事情就是:通过梯度∂p(l∣x)∂ytk\frac{\partial{p(l|x)}}{\partial{y_t^k}}ytkp(lx)调整LSTM的参数w,使得对于输入样本为x∈K−1(z)x\in K^{-1}(z)xK1(z)时有 p(l∣x)p(l|x)p(lx) 取得最大。所以如何计算梯度才是核心
∂p(l∣x)∂ytk=∂∑π∈K−1(l):πt=sαt(s)βt(s)yπtt∂ytk=−∑π∈K−1(l):πt=sαt(s)βt(s)yπtt(ytk)−2\frac{\partial{p(l|x)}}{\partial{y_t^k}} = \frac{\partial{\sum_{\pi\in K^{-1}(l): \pi_t=s}\frac{\alpha_t(s)\beta_t(s)}{y_{\pi_t}^t}}}{\partial{y_t^k}} = -\frac{{\sum_{\pi\in K^{-1}(l): \pi_t=s}\frac{\alpha_t(s)\beta_t(s)}{y_{\pi_t}^t}}}{{(y_t^k)^{-2}}} ytkp(lx)=ytkπK1(l):πt=syπttαt(s)βt(s)=(ytk)2πK1(l):πt=syπttαt(s)βt(s)

注意 这些符号记法不是关键,核心就是动态规划的思想,只要能推出完整的动规路径,就很容易去追踪导数…