一、OCR 简介
文字识别也是图像领域一个常见问题。然而,对于自然场景图像,首先要定位图像中的文字位置,然后才能进行文字的识别。
所以一般来说,从自然场景图片中进行文字识别,需要包括2个步骤:
- 文字检测(CTPN):解决的问题是哪里有文字,文字的范围有多少
- 文字识别(CRNN):对定位好的文字区域进行识别,主要解决的问题是每个文字是什么,将图像中的文字区域进转化为字符信息。
OCR 的作用: 提取图像中的文字,并转换成文本形式,供后续文字处理任务(例如:NLP)使用。
二、CTPN(基于连接预选框网络的文本检测)
2.1 CTPN 简介
文本检测本质上也属于物体检测,但是文本却跟常规的物体有较大区别。
CTPN 的提出,基于:
- 文本通常都是从左往右写的(水平),并且字之间的宽度都大致相同。
- 固定宽度,来检测文本高度即可,但是如何应对变长序列呢?
- 本质上还是 RPN 方法(可参考
faster-RCNN
),可将检测到的框拼在一起。
基本思路:既然宽度是可变、不确定的,那么就按照固定的高度进行检测,看看图像中有哪些区域是连续出现了一片同样高度特征的区域,并且其边缘符合文字的特点,就将其圈出来。
该模型主要是对图片中的文本行进行准确定位,其基本做法是直接在卷积获得的 feature map
(特征图)上生成的一系列适当尺寸的文本 proposals
(预选框)进行文本行的检测。
Note: CTPN 模型实际上是在 feature map
上生成 proposals
,而不是在原图像上生成!
类似于 分治法 的思想:把⽂本检测的任务拆分,第⼀步检测⽂本框中的⼀部分,判断它是不是⽂本的⼀部分,当对⼀幅图⾥所有⼩⽂本框都检测之后,将属于同⼀⽂本框中的⼩⽂本框合并,合并之后就可以得到⼀个完整的、⼤的⽂本框了,也就完成了⽂本的检测任务。
2.2 CTPN模型创新点
CTPN 的创新点主要由以下三点:
- 将⽂本检测任务转化为⼀连串⼩尺度⽂本框的检测(类似于
分治法
思想)- 引⼊ RNN 提升⽂本检测效果
- Side_refinement(边界优化)提升⽂本框边界预测精准度。每两个相近的 proposal(也就是候选区)组成⼀个 pair ,合并不同的 pair 直到⽆法合并为⽌
2.3 CTPN 与 RPN 网络结构的差异(参考1)
如上图所示,左图为 RPN,右图为 CTPN 的网络结构。可以看到,CTPN 本身就是 RPN,唯一不同的是加入了双向 LSTM 获取时序方向的信息,使得模型可以序列性的预测文本的小片。
当然这里的不同之处主要有以下几点:
- 双向 LSTM 对文本行方向编码
- 标签构造方式不同:CTPN 使用水平方向的切片框作为回归目标。
2.4 CTPN 网络结构
2.4.1 CTPN 整体思路
VGG 提取特征,BLSTM 融合上下文信息,基于 RPN完成检测。
2.4.2 CTPN 细节流程
step 1: 输⼊ 3×600(h)×900(w)
的图像,首先通过 BackBone 架构网络 VGG16
进行特征的提取,得到 conv5_3
(VGG16 第 5 个 block 的第三个卷积层)的特征作为 feature map
,⼤⼩为 512×38×57
;
Note: 由于 VGG16
的卷积网络中经过 4 个池化层累计的 stride 为 16。也就是 Conv5 层输出的 feature map
中一个像素对应原图的 16 像素。
step 2: 在这个 feature map
上做滑窗,窗⼝⼤⼩是 3×3
,即 512×38×57
变为 4608×38×57
( 512 按 3×3 卷积展开);
step 3: 将每⼀⾏的所有窗⼝对应的特征输⼊到 RNN(BiLSTM)
中,每个 LSTM
层是 128
个隐层,即 57×38×4608 变为 57×38×128,Reverse-LSTM 同样得到的是 57×38×128,合并后最终得到结果为 256×38×57
;
step 4: 将 RNN
的结果输⼊到 FC
层(全连接层),FC
层是⼀个 256×512
的矩阵参数,得到 512×38×57
的结果;[256 -> 512]
step 5: FC
层特征输⼊到三个分类或者回归层中。第⼀个 2k vertical coordinate
和第三个 k side-refinement
是⽤来回归 k
个 anchor
的位置信息(可以简单理解为是要确定字符位置的⼩的矩形框,上⾯示意图中的红⾊⼩⻓框,宽度固定,默认为16),第⼆个 2k scores
表示的是 k
个 anchor
的类别信息(是字符或不是字符);[ 输出层联合预测 k 个锚点的⽂本、⾮⽂本分数,y 轴坐标(包括坐标和⾼度)和 边缘调整偏移 ]
step 6: 使⽤⽂本构造的算法,将得到的细⻓的矩形框,将其合并成⽂本的序列框。其中⽂本构造算法的主要的思路为:每两个相近的候选区组成⼀个 pair
,合并不同的 pair
直到⽆法再合并为⽌。
2.5 如何通过FC层输出产生Text proposals?
CTPN 通过 CNN 和 BLSTM 学到一组 “空间 + 序列”
特征后,在 FC 后接入 RPN
网络。这里的 RPN 与 Faster R-CNN 类似,分为两个分支:
- 左边分支用于 Bounding Box Regression。由于 FC Feature map 每个点配备了 10 个 Anchor,同时只回归中心 y 坐标与高度2个值,所以 RPN_bbox_pred 有 20 个 Channels;
- 右边分支用于 Softmax 分类 Anchor。
2.6 竖直Anchor定位文字位置
由于 CTPN 针对的是横向排列的文字检测,所以其采用了一组(10个)等宽度的 Anchors,用于定位文字位置。Anchor 宽高为:
需要注意,由于 CTPN 采用 VGG16 模型提取特征,那么 Conv5 Feature map 的宽高都是输入 Image 的宽高的 1/16。同时 FC 与 Conv5 width 和 height 都相等。如下图所示,CTPN 为 FC Feature map 每一个点都配备 10 个上述 Anchors。
这样设置 Anchors 是为了:
- 保证在 x 方向上,Anchor 覆盖原图每个点且不相互重叠。
- 不同文本在 y 方向上高度差距很大,所以设置 Anchors 高度为 11-283,用于覆盖不同高度的文本目标。
注意:Anchor 大小为什么对应原图尺度,而不是 conv5/fc 特征尺度?
这是因为 Anchor 是目标的候选框,经过后续
分类+位置修正
获得目标在原图尺度的检测框。那么这就要求 Anchor 必须是对应原图尺度!除此之外,如果 Anchor 大小对应 conv5/FC 尺度,那就要求 Bounding box regression 把很小的框回归到很大,这已经超出 Regression 小范围修正框的设计目的。
获得 Anchor 后,与 Faster R-CNN 类似,CTPN 会做如下处理:
- Softmax 判断 Anchor 中是否包含文本,即选出 Softmax Score 大的正 Anchor;
- Bounding box regression 修正包含文本的 Anchor 的中心 y 坐标与高度。
v c , v h v_c,v_h vc,vh 是回归预测的坐标; c y a , h a c_{y}^a,h^a cya,ha 是 Anchor 的中心 y 坐标和高度, v c ∗ , v h ∗ v_{c}^*, v_{h}^* vc∗,vh∗ 是 Ground Truth
Anchor 经过上述 Softmax 和 bounding box regeression 处理后,会获得下图所示的一组竖直条状 text proposal
。后续只需要将这些 text proposal 用文本线构造算法连接在一起即可获得文本位置。
Anchor 选择:
- 正锚点:与任何实际边界框具有 >0.7 的 IoU 重叠;或 与实际边界框具有最⾼的 IoU 重叠
- 负锚点:与所有实际边界框具有 <0.5 的 IoU 重叠
2.7 文本线构造算法
⽂本⾏构建很简单,通过将那些 text/no-text score > 0.7
的连续的 text proposals
相连接即可。
⽂本⾏的构建规则如下(分前向和后向两部分):
先向前走,对于 X i X_i Xi,基于重合度 0.7(2K得分值) 与位置距离(50像素)找到 score 值最大的 X j X_{j} Xj,接下来再反向走(规则不变),比较两次得分值大小来判断序列。
为了说明问题,假设某张图有上图所示的 2 个 text proposal
,即蓝色和红色 2 组 Anchor,CTPN 采用如下算法构造文本线:
- 按照水平 x 坐标排序 Anchor;
- 按照规则依次计算每个 Anchor b o x i box_i boxi 的 pair ( b o x j box_j boxj ),组成 pair ( b o x i , b o x j box_i,box_j boxi,boxj);
- 通过 pair ( b o x i , b o x j box_i,box_j boxi,boxj) 建立一个 Connect graph,最终获得文本检测框.
下面详细解释。假设每个 Anchor index 如绿色数字,同时每个 Anchor Softmax score 如黑色数字。
文本线构造算法通过如下方式建立每个 Anchor b o x i box_i boxi 的 pair ( b o x i , b o x j box_i,box_j boxi,boxj ):
正向寻找:
- 沿水平正方向,寻找和 b o x i box_i boxi 水平距离
< 50
的候选 Anchor;- 从候选 Anchor 中,挑出与 b o x i box_i boxi 竖直方向
overlapv > 0.7
的 Anchor;- 挑出符合条件 2 中
Softmax score
最大的 b o x j box_j boxj
再反向寻找:
- 沿水平负方向,寻找和 b o x j box_j boxj 水平距离
< 50
的候选 Anchor;- 从候选 Anchor 中,挑出与 b o x j box_j boxj 竖直方向
overlapv > 0.7
的 Anchor;- 挑出符合条件 2 中
Softmax score
最大的 b o x k box_k boxk
最后对比scorei和scorek:
- 如果
score i >= score k
,则这是一个最长连接,那么设置Graph(i,j)=True
;- 如果
scorei < scorek
,说明这不是一个最长的连接(即该连接肯定包含在另外一个更长的连接中)。
举例说明,如上图,Anchor 已经按照 x 顺序排列好,并具有图中的 Softmax score
(这里的 score
是随便给出的,只用于说明文本线构造算法):
- 对于
i=3
的box3
,向前寻找 50 像素,满足overlapv > 0.7
且score
最大的是box7
,即j=7
;
box7
反向寻找,满足overlapv > 0.7
且 score 最大的是box3
,即k=3
。
由于score3 >= score3
,pair( box3,box7)
是最长连接,那么设置Graph(3,7)=True
- 对于
box4
正向寻找得到box7
;box7
反向寻找得到box3
,但是score4 < score3
,即pair( box4,box3)
不是最长连接,包含在pair( box3,box7)
中。
然后,这样就建立了一个 N * N
的 Connect graph(其中 N 是正 Anchor 数量)。遍历 Graph:
Graph(0,3)=True
且Graph(3,7)=True
,所以Anchor index 1→3→7
组成一个文本,即蓝色文本区域。Graph(6,10)=True
且Graph(10,12)=True
,所以Anchor index 6→10→12
组成另外一个文本,即红色文本区域。
这样就通过 Text proposals
确定了文本检测框。
2.8 CTPN 总结
- 由于加入 LSTM,所以 CTPN 对水平文字检测效果超级好。
- 因为 Anchor 设定的原因,CTPN 只能检测横向分布的文字,小幅改进加入水平 Anchor 即可检测竖直文字。但是由于框架限定,对不规则倾斜文字检测效果非常一般。
- CTPN加入了 BLSTM 学习文字的序列特征,有利于文字检测。但是引入 LSTM 后,在训练时很容易梯度爆炸,需要小心处理
CTPN 模型最大的亮点是引入RNN
来进行检测。先用CNN
得到深度特征,然后用固定宽度的anchor
(固定宽度的,细长的矩形框)来检测文本区域,将同一行anchor
对应的特征串成序列,然后输入到RNN
当中,再用全连接层
来做分类或回归,最后将小的候选框进行合并,从而得到了文本所在的完整区域。这种把 RNN 和 CNN 无缝结合的方法有效地提高了检测精度。
三、CTPN 完整检测训练过程
做个训练记录,如有需要相关代码,请联系我!!!
3.1 数据准备
step 1: 数据获取:⼿动标注,获得 8 个点坐标(或者矩形框,获得 4 个坐标),得到 xml ⽂件;
step 2: xml⽂件转换成 txt ⽂件,只包含 8 个坐标信息,以 ,
分割;(xml2txt.py)
step 3: draw_small_box:画出 16px 的⼩框;(draw_small_box.py)
step 4: 得到⼩框 gt:将⼩框的坐标写⼊新的 txt ⽂件中,命名为 label_gt;(small_box_gt.py)
step 5: 验证⼩框 gt 是否正确;(small_gt_val.py)
3.2 数据增强
1. img_transform.py
2. data_aug.py
3.3 setup
因为 python 实现 nms 和 bbox 的速度太慢,所以使⽤ setup 编译 cpython 来实现
cd utils/bbox
chmod +x make.sh
./make.sh
3.4 issue
3.4.1 ImportError: No module named bbox.bboxs
解决:./make.sh
⽣成两个 so
⽂件,然后添加 __init__.py