大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全家桶1年46,售后保障稳定
一.概述
对于复杂场景的文字识别,首先要定位文字的位置,即文字检测。这一直是一个研究热点。
文本检测可以看成特殊的目标检测,但它有别于通用目标检测.在通用目标检测中,每个目标都有定义好的边界框,检测出的bbox与当前目标的groundtruth重叠率大于0.5就表示该检测结果正确.文本检测中正确检出需要覆盖整个文本长度,且评判的标准不同于通用目标检测,具体的评判方法参见(ICDAR 2017 RobustReading Competition),所以通用的目标检测方法并不适用文本检测。
CTPN是在ECCV 2016提出的一种文字检测算法。CTPN结合CNN与LSTM深度网络,能有效的检测出复杂场景的横向分布的文字。
二.关键idea
- 采用垂直anchor回归机制,检测小尺度的文本候选框
- 文本检测的难点在于文本的长度是不固定,可以是很长的文本,也可以是很短的文本.如果采用通用目标检测的方法,将会面临一个问题:**如何生成好的text proposal**.针对上述问题,作者提出了一个vertical anchor的方法,具体的做法是只预测文本的竖直方向上的位置,水平方向的位置不预测。与faster rcnn中的anchor类似,但是不同的是,vertical anchor的宽度都是固定好的了,论文中的大小是16个像素。而高度则从11像素到273像素(每次除以0.7)变化,总共10个anchor.
- 采用RNN循环网络将检测的小尺度文本进行连接,得到文本行.
- 采用CNN+RNN端到端的训练方式,支持多尺度和多语言,避免后处理
三.ctpn网络结构
假设输入N副Images:
- 首先VGG提取特征,获得大小为conv5 feature map。
- 之后在conv5上做3*3的滑动窗口,即每个点都结合周围3*3区域特征获得一个长度为3*3*C的特征向量。输出的feature map,该特征显然只有CNN学习到的空间特征。
- 再将这个feature map进行Reshape:
- 然后以Batch=NH且最大时间长度的数据流输入双向LSTM,学习每一行的序列特征。双向LSTM输出,再经Reshape恢复形状:该特征既包含空间特征,也包含了LSTM学习到的序列特征。
- 然后经过“FC”卷积层,变为的特征
- 最后经过类似Faster R-CNN的RPN网络,获得text proposals,如图2-b。
这里解释一下conv5 feature map如何从变为
在原版caffe代码中是用im2col提取每个点附近的9点临近点,然后每行都如此处理:
接着每个通道都如此处理:
而im2col是用于卷积加速的操作,即将卷积变为矩阵乘法,从而使用Blas库快速计算。到了tf,没有这种操作,所以一般是用conv2d代替im2col,即强行卷积 。
接下来,文章围绕下面三个问题展开:
- 为何使用双向LSTM
- 如何通过FC层输出产生图2-b中的Text proposals
- 如何通过Text proposals确定最终的文本位置,即文本线构造算法
补充说明:
CTPN的具体实现流程包含三个部分:**检测小尺度文本框**,**循环连接文本框**,**文本行边细化**.具体的实现步骤如下:
- 使用VGG16作为base net提取特征,得到conv5_3的特征作为feature map,大小是W×H×C
- 在上述的feature map上使用大小为3*3的滑动窗进行滑动,每个窗口都能得到一个长度为3×3×C的特征向量,每个滑动窗口中心都会预测k个相对于anchor的偏移
- 将上一步得到的特征输入到一个双向的LSTM中,得到长度为W×256的输出,然后接一个512的全连接层,准备输出。
- 输出层部分主要有三个输出。2k个vertical coordinate,因为一个anchor用的是中心位置的高(y坐标)和矩形框的高度两个值表示的,所以一个用2k个输出。(注意这里输出的是相对anchor的偏移)。2k个score,因为预测了k个text proposal,所以有2k个分数,text和non-text各有一个分数。k个side-refinement,这部分主要是用来精修文本行的两个端点的,表示的是每个proposal的水平平移量。
- 使用一个标准的非极大值抑制算法来滤除多余的text proposal。
- 最后使用基于图的文本行构造算法,将得到的一个一个的文本段合并成文本行。
四.双向LSTM
CNN学习的是感受野内的空间信息,LSTM学习的是序列特征。对于文本序列检测,显然既需要CNN抽象空间特征,也需要序列特征(毕竟文字是连续的)。
CTPN中使用双向LSTM,相比一般单向LSTM有什么优势?双向LSTM实际上就是将2个方向相反的LSTM连起来,如图r。
一般来说,双向LSTM都好于单向LSTM。还是看LSTM介绍文章中的例子:
我的手机坏了,我打算____一部新手机。
假设使用LSTM对空白部分填词。如果只看横线前面的词,“手机坏了”,那么“我”是打算“修”还是“买”还是“大哭一场”?双向LSTM能看到后面的词是“一部新手机“,那么横线上的词填“买“的概率就大得多了。显然对于文字检测,这种情况也依然适用。
五.全连接层与rpn网络
经过全连接层得到一个512维的feature map。
CTPN通过CNN和BLSTM学到一组“空间 + 序列”特征后,在”FC”卷积层后接入RPN网络。这里的RPN与Faster R-CNN类似,分为两个分支:
- 左边分支用于bounding box regression。由于fc feature map每个点配备了10个Anchor,同时只回归中心y坐标与高度2个值,所以rpn_bboxp_red有20个channels
- 右边分支用于Softmax分类Anchor
具体RPN网络与Faster R-CNN完全一样,所以不再介绍,只分析不同之处。
六.竖直Anchor定位文字位置
由于CTPN针对的是横向排列的文字检测,所以其采用了一组(10个)等宽度的Anchors,用于定位文字位置。Anchor宽高为:
需要注意,由于CTPN采用VGG16模型提取特征,那么conv5 feature map的宽高都是输入Image的宽高的1/16。
同时fc与conv5 width和height都相等。
如图6所示,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坐标与高度。
注意,与Faster R-CNN不同的是,这里Bounding box regression不修正Anchor中心x坐标和宽度。具体回归方式如下:
是预测得到的中心坐标和高度值,和是Anchor的中心y坐标和高度。是Ground Truth的中心坐标和高度值。
是回归预测的值与anchor的坐标变换,是Ground Truth与anchor的坐标变换。
最终需要最小化和的回归误差。可以选用smooth L1损失。
Anchor经过上述Softmax和y方向bounding box regeression处理后,会获得图7所示的一组竖直条状text proposal。后续只需要将这些text proposal用文本线构造算法连接在一起即可获得文本位置。
在论文中,作者也给出了直接使用Faster R-CNN RPN生成普通proposal与CTPN LSTM+竖直Anchor生成text proposal的对比,如图8,明显可以看到CTPN这种方法更适合文字检测。
七.文本线构造算法
在上一个步骤中,已经获得了一串或多串text proposal,接下来就要采用文本线构造办法,把这些text proposal连接成一个文本检测框。
为了说明问题,假设某张图有图9所示的2个text proposal,即蓝色和红色2组Anchor,CTPN采用如下算法构造文本线:
- 按照水平x坐标排序Anchor
- 按照规则依次计算每个Anchor 的,组成
- 通过建立一个Connect graph,最终获得文本检测框
下面详细解释。假设每个Anchor index如绿色数字,同时每个Anchor Softmax score如黑色数字。
文本线构造算法通过如下方式建立每个Anchor 的:
正向寻找:
- 沿水平正方向,寻找和水平距离小于50的候选Anchor
- 从候选Anchor中,挑出与竖直方向的Anchor
- 挑出符合条件2中Softmax score最大的
再反向寻找:
- 沿水平负方向,寻找和水平距离小于50的候选Anchor
- 从候选Anchor中,挑出与竖直方向的Anchor
- 挑出符合条件2中Softmax score最大的
注:这里的是垂直方向的一维IOU。
最后对比和:
- 如果,则这是一个最长连接,那么设置
- 如果,说明这不是一个最长的连接(即该连接肯定包含在另外一个更长的连接中)。
举例说明,如图10,Anchor已经按照x顺序排列好,并具有图中的Softmax score(这里的score是随便给出的,只用于说明文本线构造算法):
然后,这样就建立了一个的Connect graph(其中N是正Anchor数量)。遍历Graph:
这样就通过Text proposals确定了文本检测框。
八.文本行的side-refinement
因为这里规定了回归出来的box的宽度是16个像素,所以会导致一些位置上的误差,这时候就是Side-refinement发挥作用的时候 了。定义的式子如下:
其中带*表示为GroundTruth.。表示回归出来的左边界或者右边界,表示anchor中心的横坐标,是固定的宽度16像素。所以O的定义相当于是一个缩放的比例,帮助我们去拉伸回归之后的box的结果,从而更好地符合实际文本的位置。对比图如下,红色框是使用了side-refinement的,而黄色框是没有使用side-refinement方法的结果:
九.Loss
loss的表达式如下:
CTPN整体包含了3个loss,分类的Ls,边框回归的Lv,边框左右的回归的偏移Lo
Ls为传统的softmax_cross_entropy_loss,其中,i表示所有预测的anchor中的第i个,,Ns为归一化参数,表示所有的anchor的总和。
Lv使用的smooth_L1_loss,其中,j表示所有IOU>0.5的anchor中的第j个,Nv为归一化参数,表示所有的anchor和groudtruth的IOU>0.5的anchor数总和。λ1为多任务的平衡参数,λ1=1.0。
Lo也是使用的smooth_L1_loss,其中,k表示边界anchor中的第k个,即预测和groundtruth相距32个像素的边界anchor的集合。Nv为归一化参数,表示所有边界anchor数总和。λ1为多任务的平衡参数,λ1=2.0。
十.代码分析
测试和训练的输入图片大小:
测试:
测试的时候和faster的机制一样,也是短边大于600,长边小于1200,这样的按比例缩放的机制。
def resize_im(im, scale, max_scale=None):
f=float(scale)/min(im.shape[0], im.shape[1])
if max_scale!=None and f*max(im.shape[0], im.shape[1])>max_scale:
f=float(max_scale)/max(im.shape[0], im.shape[1])
return cv2.resize(im, None,None, fx=f, fy=f,interpolation=cv2.INTER_LINEAR), f
训练:
训练的时候,为了可以走batch,所以是对一个batch内的图片,取最大的宽,高,其余小于该宽高的图片的其他位置补0,这样进行操作的。
def im_list_to_blob(ims):
"""Convert a list of images into a network input.
Assumes images are already prepared (means subtracted, BGR order, ...).
"""
max_shape = np.array([im.shape for im in ims]).max(axis=0)
num_images = len(ims)
blob = np.zeros((num_images, max_shape[0], max_shape[1], 3),
dtype=np.float32)
for i in range(num_images):
im = ims[i]
blob[i, 0:im.shape[0], 0:im.shape[1], :] = im
return blob
Anchor合并机制:
ctpn需要将每一个anchor回归的框进行合并,从而生成最终的文本框。
合并的主要原则,
(1)水平的最大连接距离为MAX_HORIZONTAL_GAP=50个像素
(2)垂直方向的一维IOU是否满足大于MIN_V_OVERLAPS=0.7比例
(3)两个相邻的anchor的高度是否满足小于MIN_SIZE_SIM=0.7比例
然后基于上述的原则,首先从左往右扫描一遍,将满足条件的anchor合并,然后从右往左扫描一遍,将满足条件的anchor合并。
其中,从左往右扫描的代码,
def get_successions(self, index):
box = self.text_proposals[index]
results = []
for left in range(int(box[0]) + 1, min(int(box[0]) + TextLineCfg.MAX_HORIZONTAL_GAP + 1, self.im_size[1])):
adj_box_indices = self.boxes_table[left]
for adj_box_index in adj_box_indices:
if self.meet_v_iou(adj_box_index, index):
results.append(adj_box_index)
if len(results) != 0:
return results
return results
从右往左扫描的代码,
def get_precursors(self, index):
box = self.text_proposals[index]
results = []
for left in range(int(box[0]) - 1, max(int(box[0] - TextLineCfg.MAX_HORIZONTAL_GAP), 0) - 1, -1):
adj_box_indices = self.boxes_table[left]
for adj_box_index in adj_box_indices:
if self.meet_v_iou(adj_box_index, index):
results.append(adj_box_index)
if len(results) != 0:
return results
return results
一维高度IOU的代码,和高度差距小于0.7的代码,
def meet_v_iou(self, index1, index2):
def overlaps_v(index1, index2):
h1 = self.heights[index1]
h2 = self.heights[index2]
y0 = max(self.text_proposals[index2][1], self.text_proposals[index1][1])
y1 = min(self.text_proposals[index2][3], self.text_proposals[index1][3])
return max(0, y1 - y0 + 1) / min(h1, h2)
def size_similarity(index1, index2):
h1 = self.heights[index1]
h2 = self.heights[index2]
return min(h1, h2) / max(h1, h2)
return overlaps_v(index1, index2) >= TextLineCfg.MIN_V_OVERLAPS and \
size_similarity(index1, index2) >= TextLineCfg.MIN_SIZE_SIM
最后附上一段tensorflow版本的ctpn网络结构代码,非常清楚明了。
def model(image):
image = mean_image_subtraction(image) # vgg网络数据预处理,图像三个通道减去均值
with slim.arg_scope(vgg.vgg_arg_scope()):
conv5_3 = vgg.vgg_16(image)
rpn_conv = slim.conv2d(conv5_3, 512, 3) # 对于vgg16的conv5_3进行3*3的滑动卷积操作
lstm_output = Bilstm(rpn_conv, 512, 128, 512, scope_name='BiLSTM') # 得到双向lstm的结果
bbox_pred = lstm_fc(lstm_output, 512, 10 * 4, scope_name="bbox_pred") # 10*4的bbox的预测值
cls_pred = lstm_fc(lstm_output, 512, 10 * 2, scope_name="cls_pred") # 10*2的正负anchor分类score
# transpose: (1, H, W, A x d) -> (1, H, WxA, d)
cls_pred_shape = tf.shape(cls_pred)
cls_pred_reshape = tf.reshape(cls_pred, [cls_pred_shape[0], cls_pred_shape[1], -1, 2])
cls_pred_reshape_shape = tf.shape(cls_pred_reshape)
cls_prob = tf.reshape(tf.nn.softmax(tf.reshape(cls_pred_reshape, [-1, cls_pred_reshape_shape[3]])),
[-1, cls_pred_reshape_shape[1], cls_pred_reshape_shape[2], cls_pred_reshape_shape[3]],
name="cls_prob")
return bbox_pred, cls_pred, cls_prob
十一.总结
优点:
CTPN对于检测的边框在上下左右4个点上都比较准确,这点比EAST要好。
缺点:
(1)CTPN只可以检测水平方向的文本,竖直方向的话就会出现一个字一个字断开的想象。倾斜角度的话需要修改后处理anchor的连接方式,但是应该会引入新的问题。
(2)CTPN由于涉及到anchor合并的问题,何时合并,何时断开,这是一个问题。程序使用的是水平50个像素内合并,垂直IOU>0.7合并。或许由于BLSTM的引入,导致断开这个环节变差。所以对于双栏,三栏的这种文本,ctpn会都当做一个框处理,有时也会分开处理,总之不像EAST效果好。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/200778.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...