首页 > 其他 > 详细

[201806]文本检测之SegLink

时间:2020-10-26 09:45:57      阅读:27      评论:0      收藏:0      [点我收藏+]

文本检测之SegLink

等 

简介

论文题目:Detecting Oriented Text in Natural Images by Linking Segments
论文地址:
代码实现:
先来说说文本检测存在的难点:1.文本行边界框的宽高比范围太广;2.语言格式的不统一(如中文文本行没有间隔,英文文本行有间隔);3.文本行的方向任意.
针对上述存在的困难,本文提出了seglink,它是在SSD目标检测方法的基础上进行改进,其基本思想:既然一次性检测整个文本行比较困难,就先检测局部片段,然后通过规则将所有的片段进行连接,得到最终的文本行,这样做的好处是可以检测任意长度的文本行

论文关键idea

  • 提出了文本行检测的两个基本组成元素:segment和link
  • 提出了基于SSD的改进版网络结构(全卷积网络结果)同时预测不同尺度的segments和link
  • 提出了两种link类型: 层内连接(within-layer link)和跨层连接(cross-layer link)
  • 可以处理多方向和任意长度的文本

Pipeline

整个实现过程包括两部分:首先检测segments,links,然后使用融合算法得到最终文本行.具体步骤如下:

  • 主干网络是沿用了SSD网络结构,并修改修改了最后的Pooling层,将其改为卷积层.具体来说:首先用VGG16作为base
    net,并将VGG16的最后两个全连接层改成卷积层.接着增加一些额外的卷积层,用于提取更深的特征,最后的修改SSD的Pooling层,将其改为卷积层
  • 提取不同层的feature map,文中提取了conv4_3, conv7, conv8_2, conv9_2, conv10_2, conv11.这里其实操作还是和SSD网络一样
  • 对不同层的feature map使用3*3的卷积层产生最终的输出(包括segment和link),不同特征层输出的维度是不一样的,因为除了conv4_3层外,其它层存在跨层的link.这里segment是text的带方向bbox信息(它可能是个单词,也可能是几个字符,总之是文本行的部分),link是不同bbox的连接信息(文章将其也增加到网络中自动学习).
  • 然后通过融合规则,将segment的box信息和link信息进行融合,得到最终的文本行.

具体的网络结构如下图:

技术分享图片

注意:上述结构图中conv8_2的输出应该是512个,因为这层是在conv7层上依次经过256个核大小为1,步长为1的卷积和512个核大小为3,步长为1的卷积核,估计是标错了.

具体实现细节

Segment检测

  • 文中segment其实就是带方向的bbox,这里相比SSD的bbox,增加了方向信息,具体表示为 技术分享图片
  • 关于default box的个数,本文每个feature map的每个位置只采用了一个aspect ratio=1的default box,而SSD中是一系列(1, 2, 3, 1/2, 1/3)
  • 关于default box的scale size,本文的是根据当前层的感受野来进行设置scale size,而SSD是通过人工设定的(详见对应的论文),具体的设置公式如下:
技术分享图片

其中分别表示输入图像的宽度和当前feature map的宽度

  • 预测的offsets里除了Δx, Δy, Δw, Δh, 多了一个Δθ,具体公式除了Δθ,其它的都是和SSD一样,这里方便,直接将公式贴出来:
技术分享图片技术分享图片

备注:这里感觉论文中计算 技术分享图片 的公式有点问题,应该是 技术分享图片 和 技术分享图片 ,,不然感觉原图和特征图对应不上(这里仅是个人猜测,望知道的大侠们指点指点)

总结来说,对于segments的预测包括:2个segment score和5个geometric offsets为 技术分享图片

Link检测

link主要是用于连接上述segment,对于link detection部分,主要分成层内link检测(within-layer)和跨层link检测(cross-layer)

  • 层内Link detection
    within-layer link,顾名思义就是在同一个feature map层,由于文中对feature map中每个位置只预测一个segment,所以对于层内的link,我们只需要考虑当前segment的8邻域:即判断每个segment与它周围的8连通邻域的segment的连接情况,每个link有两个分数,一个用是正分,一个是负分,正分用来表示二者是否属于同一个单词;负分表示二者是否属于不同单词,应该断开连接。所以,每个segment的link应该是8*2=16维的向量。具体公式如下:
技术分享图片
  • 跨层Link detection
    由于采用不同feature map,所以segment可能会被不同的feature map检测到,为了解决这种重复检测的冗余问题,文中提出了cross-layer Link.它主要是用于连续两层(注意这里前一层是后一层的邻居,但后一层不是前一层的邻居),所以只需要对conv7, conv8_2, conv9_2, conv10_2, conv11进行cross-layer link检测.
    论文中说这些feature map中是前一层特征图的size是后一层的特征图的size的两倍(因为有pooling或者是步长为2的卷积层),这些特性需要保证feature map的size是偶数,所以输入图像的宽和高必须是128的倍数.
    对于cross-layer link,对于feature map的每个位置需要预测2*4=8,这里4表示的是与上一层的4个邻域,就是对应前一层的感受野,具体公式如下:
技术分享图片

总结来说:对于conv4_3层,其link输出的维度为2*8=16;对于conv7, conv8_2, conv9_2, conv10_2, conv11其输出的link维度为2*8+2*4=24
下图是within-layer link和cross-layer link的邻域可视化图:

技术分享图片

网络预测输出的维度

综合上述segment检测和link检测,对于每个feature map,其最后预测的维度如下:

技术分享图片

上图中2表示是或不是字的二类分类分数,5表示位置信息x, y, w, h, θ,16表示8个同层的neighbor的连接或者不连接2种情况,8表示前一层的4个neighbor的连接与不连接情况

对于conv4_3:其预测输出维度为: 技术分享图片 ,因为该层没有cross-layer link
对于conv7, conv8_2, conv9_2, conv10_2, conv11,其预测输出维度为: 技术分享图片
为了更好地理解segment和link,我采用不同的颜色来可视化,每个颜色框表示文中所说的segment,而相邻的颜色框则表示link.具体的segment和link的可视化图如下:

技术分享图片

Combining Segments with Links算法

  • 首先通过人工设定的 α 和β(这两个值是采用网格搜索找到最优),对网络预测的segments和links进行滤除
  • 将每个segment看成node,link看成edge,建立图模型,再用DFS(depth first search)找到连通分量,每个连通分量包含一系列segments(用B表示),用下面的Alg1进行融合输出单词的box
  • Alg1算法其实就是一个平均的过程.先计算所有的segment的平均θ作为文本行的θ,再根据已求的θ为已知条件,求出最可能过每个segment的直线(线段,这里线段就是以segment最左和最右的为边界),以线段中点作为word的中心点(x,y),最后用线段长度加上首尾segment的平均宽度作为word的宽度,用所有segment的高度的平均作为word的高度。
技术分享图片

训练

  • 如何生成Segments和Links的groundtruth
    要想训练当前网络,必须生成相应groundtruth,包括default box的label,偏移(x, y, w, h, θ),层内link及跨层link的label.
    • 那么如何确定default box为正样本还是负样本呢?
      具体分两种情况,1).当前图像只有一个文本行,这时候判断当前default box为正样本必须同时满足a).default box的中心在当前文本行内;b).default box的size与文本行的高度比必须满足: 技术分享图片 ,若不满组上述两个条件,则当前default box为负样本;2).对于当前图像包含多个文本行,若不满足前面的两个条件,则default box为负样本,否则default box为正样本,并与 技术分享图片 最小的文本行匹配.
    • 那么如何确定offset?
      首先要知道offset只对正样本有效,也就是说负样本不需要计算.其具体做法是向将文本行的bbox与default box进行那个水平对齐(就是将文本行的bbox进行旋转,这里旋转逆时针的,之所以说是逆时针方向,是因为当前的角度为负,具体结合图像来理解);然后对文本行box进行裁剪,保留default box与文本行相交的部分(注意,这里垂直方向需要延伸,简单地说裁剪后的宽度保持default box与文本行box相交的部分,高度要保持文本行的高度);最后再绕default box的中心点进行顺时针旋转,即转回到原来的角度,这样就会得到一个裁剪后的带角度的bbox,它就是groundtruth segment.这样做的目的是当前网络检测是segment,而非整个文本行.网络要学习的偏移实际上就是default box相对于裁剪后的bbox的偏移,具体的细节流程图如下:
技术分享图片
    • 如何确定within-layer link和cross-layer link的label是positive还是negative?
      要想使link的label为positive,必须满足:1).两个default boxes connect it;2).两个default box必须属于统一文本行
  • 训练目标函数

网络的损失函数包括三部分:segment classification损失(softmax),offsets regression损失(L1 regression),link classification损失(softmax),具体公式如下:

技术分享图片

其中控制全中因子λ1 和 λ2设置为1

  • 关于数据增广和Online Hard Negative Mining,做法和SSD中一致,这里就不展开细说了
  • 训练参数
    1.pretrain:使用SynthText,在前60k次迭代时,学习率=0.001;在剩余的30k次迭代,学习率=0.0001
    2.finetune: 使用真实数据,学习率=0.0004
    3.使用SGD+moment=0.9
    4.训练图像大小=384*384
    5.batch size=32
    6,grid search step=0.1

在ICDAR2015上的结果评测

技术分享图片

使用Seglink训练自己的数据

  • 将自己的数据转换成训练所需的数据
    需要将自己的数据转换成tfrecord格式.这里我采用的是python2,如果你采用的python3,则需要修改第90行为labels_text.append(b"text"),并在第92行前增加一行img_name=str.encode(img_name),不然会提示str not byte的错误信息
#coding=utf-8 
################################################
# convert my own dataset to tfrecords
# the label format:x1,yx,x2,y2,x3,y3,x4,y4
# 2018.05.31 add 
################################################
import tensorflow as tf
import numpy as np
import os
import cv2
from dataset_utils import int64_feature, float_feature, bytes_feature, convert_to_example
# global variable define
IMAGE_EXTENSION=["jpg","png","bmp","JPG","JPEG","jpeg"]
def load_file(file_path, ext_name=IMAGE_EXTENSION):
    """
    load all imgs from the given path
    2018.05.31 add 
    """
    files_paths = []
    for root, dirs, files in os.walk(file_path):
        for tmp_file in files:
            if tmp_file.split(".")[-1] in ext_name:
                files_paths.append(os.path.join(root, tmp_file))
    return files_paths
def read_img(path):
    """
    read img from a given path
    2018.05.31 add 
    """
    img=cv2.imread(path)
    if img is None:
        return None,None,None
     return img,float(img.shape[0]),float(img.shape[1])
def convert_to_tfrecords(file_path,text_path,out_path):
    """
    convert my own data to tfrecords
    :param file_path:  the file path of img
    :param text_path:  the txt ptah of label
    :param out_path:  the output path
    2018.05.31 add 
    """
    output_dir = os.path.dirname(out_path)
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    img_paths=load_file(file_path)
    print("the total img nums is:{}".format(len(img_paths)))
    assert len(img_paths)>0,"pls input the right file_path"
    with tf.python_io.TFRecordWriter(out_path) as tfrecord_writer:
        for idx,path in enumerate(img_paths):
            with tf.gfile.FastGFile(path, ‘rb‘) as f:
                image_data = f.read()
    # read img
    img,h,w=read_img(path)
    if img is None:
        print("the current img is empty")
        continue
    # read txt info
    oriented_bboxes=[]
    bboxes=[]
    labels=[]
    labels_text=[]
    ignored=[]
    img_name=path.split("/")[-1][:-4]
    txt_path=os.path.join(text_path,img_name+".txt")
    print("txt_path:{}".format(txt_path))
    with open(txt_path,"r") as f:
        lines=f.readlines()
    for line in lines:
        line_array=np.array(line.strip().split(",")).astype(float)
        oriented_box=line_array/([w,h]*4)
        oriented_bboxes.append(oriented_box)
        xy_list = np.reshape(oriented_box, (4, 2))
        xmin = xy_list[:,0].min()
        xmax = xy_list[:,0].max()
        ymin = xy_list[:,1].min()
        ymax = xy_list[:,1].max()
        bboxes.append([max(0.,xmin), max(0.,ymin), min(xmax,1.), min(ymax,1.)])
        ignored.append(0)
        # might be wrong here, but it doesn‘t matter because the label is not going to be used in detection
        labels_text.append("text")
        labels.append(1)
        example = convert_to_example(image_data, img_name, labels, ignored, labels_text, bboxes,
                                         oriented_bboxes, img.shape)
        tfrecord_writer.write(example.SerializeToString())
# main
if __name__=="__main__":
    convert_to_tfrecords("/path/to/img","/prth/to/txt/","/path/to/myown_dataset_train.tfrecord") 
  • 训练部分结果
    这里由于时间问题,我的模型还在训练,下面展示的是中间结果模型的测试结果(后续更新最终模型的检测结果)
技术分享图片技术分享图片技术分享图片技术分享图片


备注:这里我训练的图像大小是384*384的,如果用512*512结果应该会更好一点

总结及困惑

总结

  • 与CTPN方法相比,SegLink引入了带方向的bbox(即文中说的segment(x, y, w, h, θ)),它可以检测任意方向的文本行,而CTPN主要用于检测水平的文本行,当然如果将垂直anchor改成水平anchor,也可以检测垂直方向的文本行
  • 与EAST方法相比,SegLink利用不同的feature map分别进行预测,而EAST是将不同的feature map层先进行合并再预测.如果两个网络都采用基于VGG16作为base net,应该效果差别不大(仅是个人猜测,未经过验证).
  • α和β这两个阈值是通过网格搜索的方法求得,网格搜索是一种暴力的模型超参数优化技术,这里采用0.1step进行超参数穷举搜索
  • 不能检测很大的文本,这是因为link主要是用于连接相邻的segments,而不能用于检测相距较远的文本行
  • 不能检测形变或者曲线文本,这是因为segments combining算法在合并的时候采用的是直线拟合.这里可以通过修改合并算法,来检测变形或曲线文本

困惑

  • 在确定default box是正样本还是负样本时,对于当前图像包含多个文本行,default box与当前图像哪个文本行匹配,文中说的是最小,这里不太能理解

本人文笔粗浅,本文仅是个人的理解,若有理解错误的地方,望大家指正

编辑于 2018-06-06

[201806]文本检测之SegLink

原文:https://www.cnblogs.com/cx2016/p/13876707.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!