『OCR_detection』PSENet


前言

论文: Shape Robust Text Detection with Progressive Scale Expansion Network
代码: https://github.com/whai362/PSENet

  • 自然场景的文本检测是当前深度学习的重要应用,在之前的文章中已经介绍了基于深度学习的文本检测模型 CTPN、CRAFT、Seglink、EAST(见文章:『OCR_detection』CTPN『OCR_detection』CRAFT『OCR_detection』Seglink『OCR_detection』EAST)。
  • 形状鲁棒性文本检测面临的挑战主要有两个方面:1)现有的基于四边形边界盒的文本检测方法很难找到任意形状的文本,很难完全封闭在矩形中;2)大多数基于像素的分割检测器可能不会将彼此非常接近的文本实例分开。为了解决这些问题,我们提出了一种新的渐进尺度扩展网络(PSEnet),它是一种基于分割的检测器,对每个文本实例都有多个预测。这些预测对应于通过将原始文本实例缩小到不同的尺度而产生的不同的“内核”。因此,最终的检测可以通过我们的渐进尺度扩展算法进行,该算法将最小尺度的核逐步扩展到最大和完全形状的文本实例。由于这些极小核间存在较大的几何边缘,因此我们的方法能够有效地区分相邻文本实例,并且对任意形状具有鲁棒性。

一、网络结构

在 ImageNet 数据集上预训练的 ResNet+FPN 作为特征提取的网络结构。

  1. 首先,将高层特征和低层特征 融合 后得到 (P2, P3, P4, P5) 四个特征层,其中每个特征层的 channel 数量为 256。
  2. 之后,将四个特征层 concat 得到 F (将低层次的特征和高层次的分割实例特征进行融合,最后得到与原图尺寸相同的输出 F), 其中 F = C(P2,P3,P4,P5) = P2 || Upx2(P3) || Upx4(P4) || Upx8(P5) ,其中的 || 就代表 concat。x2,x4,x8 分别代表 2 倍、4 倍和 8 倍的上采样。F 的维度大小为 [ B, C, H, W ],其中 C 的大小为确定的 kernel_num, 将 F 送入 Conv(3,3)-BN-ReLU 层,并将特征层的 channel 数量变为 256。
  3. 最后,将 F 送入多个 Conv(1,1)-Up-Sigmod 层来得到 n 个分割结果 S 1 , S 2 , … , S n S_1,S_2,…,S_n S1,S2,,Sn,其中的 Up 代表上采样。sigmoid 来预测 n 个 mask

注: S 1 , S 2 , S 3 , ⋯ , S n S_1,S_2,S_3,⋯,S_n S1,S2,S3,,Sn 是图像文字的分割结果,他们的不同点在于他们分割出的文字区域大小不同。例如 S1 给出的是最小的文字区域分割结果, 是预测的图片中目标文字的核心区域(并不是全部范围),而 Sn 给出的是最大的文字区域分割结果(理想下就是 GroundTruth)

二、后处理算法(渐进式扩展算法)

  • 实际文章中 n=6,但是为了更方便解释,这里假设 n=3,即网络最终输出了 3 张分割结果
  • 假设用了 3 个不同尺度的 kernel(如图a,e,f),其中 S 1 S_1 S1 (上图a)代表最小 kernel 的分割结果,它内部有四个连通域 C={c1,c2,c3,c4},CC 操作得到 S1 中的四个连通域,得图 b(四个连通区域使用不同颜色标记)。
  • 现在我们已经得到了图 b 中的四个连通域(小 kernel,不同文本行之间的 margin 很大,很容易区分开),且我们已知 S 2 S_2 S2 中的 kernel 是比 S 1 S_1 S1 中的 kernel 大的,也就是可以说 S 2 S_2 S2 中的 kernel 是包含 S 1 S_1 S1 中的 kernel 的。现在我们的任务就是将属于 S 2 S_2 S2 中的 kernel 的但不属于 S 1 S_1 S1 中的 kernel 的像素点(即图 g 左图中的灰色的部分,蓝色和橘色部分分别表示 S 1 S_1 S1 中的两个连通域)进行分配。
  • 如图所示,在灰色区域( S 2 S_2 S2 的 kernel 范围)内,将 b 图所找到的连通域的每个 pixel 以 BFS 的方式,逐个向上下左右扩展,即相当于把 S 1 S_1 S1 中预测的文本行的区域逐渐变宽(或者换种说法:对于 S 2 S_2 S2 中 kernel 的每个像素点,把它们都分别分配给 S 1 S_1 S1 中的某个连通域)。这里还有一个问题,如图 g 右图所示,图中值为 2 的点为冲突点,例子中的两个连通域都可能扩展到这个pixel,论文中对这种冲突的解决方法就是“先到先得”原则,这对最后的结果没什么影响。后面的 S 3 S_3 S3 同理,最终我们抽取图 d 中不同颜色标注的连通区域作为最后的文本行检测结果。
  • 如何分离靠的很近的文字块?
    直接用语义分割来检测文字又会遇到新的问题:很难分离靠得很近的文字块。因为语义分割只关心每个像素的分类问题,所以即使文字块的一些边缘像素分类错误对 loss 的影响也不大。
    解决方案:引入 kernel,文字块的核心,利用 kernel 可以有效的分离靠的很近的文字块。
  • 如何通过 kernel 来构建完整的文字块?
    kernel 只是文字块的核心,并不是完整的文字块,不能作为最终的检测效果。
    方法: 基于 广度优先搜索 的 渐近扩展算法 来构建完整的文字块
    核心思想: 从每个 kernel 出发,利用广度优先搜索来不断地合并周围的像素,使得 kernel 不断地扩展,最后得到完整的文字块

基于 广度优先搜索(BFS) 由三个步骤组成:

step 1: 从具有最小尺度的核 S1 开始(在此步骤中可以区分实例,不同实例有不同的连通域);
step 2: 通过逐步在较大的核中加入更多的像素来扩展它们的区域;
step 3: 完成直到发现最大的核。

三、标签的生成

  • 首先从网络结构中可以看出,需要生成不同尺度的 kernel,这是需要有标签的图像来进行训练的。因为网络输出有 n 个分割结果,所以对于一张输入图片来说 groundtruth 也要有 n 个。这里 groundtruth 就是简单的将标定的文本框进行不同尺度的缩小
  • 为了生成训练时不同尺寸 kernels 所对应的 groundtruths,作者采用 Vatti clipping algorithm 将原始多边形 P n P_n Pn 缩放 d i d_i di 个像素从而得到 P i P_i Pi,其中每个缩放的 P i P_i Pi 都是使用 0/1 的二进制 mask 来表示分割后的标签的。
  • 需要缩小的像素通过右边式子得到: d i = A r e a ( P n ) × ( 1 − r i 2 ) P e r i m e t e r ( P n ) d_{i} = \frac{Area(P_{n})\times(1-r_{i}^{2})}{Perimeter(P_{n})} di=Perimeter(Pn)Area(Pn)×(1ri2), 其中 r i = 1 − ( 1 − m ) ( n − i ) n − 1 r_{i} = 1 - \frac{(1-m)(n-i)}{n-1} ri=1n1(1m)(ni)
    Note:
    • r i r_{i} ri 表示缩小的比例
    • m: 最小的缩放比例,是一个超参数,取值范围为(0,1]。本文取得是 m=0.5
    • n: 最终输出多少个尺度的分割结果,即 kernel 的数量

四、损失函数 loss

  • 损失由完整的 mask 的 loss 加上腐蚀后的 mask 的 loss,其中两项是加权相加:
    L = λ L c + ( 1 − λ ) L s L = \lambda L_{c} + (1-\lambda)L_{s} L=λLc+(1λ)Ls
    其中, L c L_{c} Lc 表示没有进行缩放时候的损失函数,即相对于原始大小的groundtruth的损失函数, L s L_{s} Ls 表示的是相对于缩放后的框的损失函数。
  • 由于正常状态下非文本区域远大于文本区域,所以使用二分类的交叉熵损失会使得结果更加偏向于非文本区域,故 PSE 模型使用的是 dice cofficient :
    D ( S i , G i ) = 2 × ∑ x , y ( S i , x , y × G i , x , y ) ∑ x , y S i , x , y 2 + ∑ x , y G i , x , y 2 D(S_i, G_{i})=\frac{2\times\sum_{x, y}(S_{i, x, y}\times G_{i, x, y})}{\sum_{x, y}S_{i, x, y}^{2} + \sum_{x, y}G_{i, x, y}^{2}} D(Si,Gi)=x,ySi,x,y2+x,yGi,x,y22×x,y(Si,x,y×Gi,x,y)
    Note:
    • S x , y S_{x, y} Sx,y 为预测实例中像素点 (x, y) 的值,
    • G x , y G_{x, y} Gx,y 为 label 中像素点 (x, y) 的值。
      在这里插入图片描述

五、OHEM 算法的思想

在线难例挖掘(online hard example mining),根据输入样本的损失进行筛选,筛选出难例,表示对分类和检测影响较大的样本,然后将筛选得到的这些样本应用在随机梯度下降中训练。具体到该模型中,选取所有正样本(主要是正样本本来就偏少,所以就全取)以及困难样本,过滤掉 easy 的负样本。被选中的像素点取值为 1,未选中的取值为 0 。

六、训练自己的数据集

6.1 数据准备

step 1: 数据获取:手动标注,获得 8 个点坐标(或者矩形框,获得 4 个坐标),得到 xml 文件;[ PSE也可以检测多边框图,即点坐标可以不止 8 个 ]
step 2: xml 文件转换成 txt 文件,只包含 8 个坐标信息,以 , 分割;(xml2txt.py)
step 3: draw_small_box:画出 16px 的小框;验证小框是否正确 (draw_small_box.py) [ PSE 不需要此步骤,需要的话也仅仅是为了验证框的标注正确性 ]

6.2 数据增强

1. img_transform.py
2. data_aug.py

6.3 pse_general 参数设置

  • –arch: resnet50
  • –batchsize: 8
  • –lr: 0.001
  • –imgsize: 640
  • –n_epoch: 50
  • –resume: 中间训练结束,继续训练的起始位
  • –pretrain: 加载预训练模型,仅相当于导入初始权重
  • –schedule: [ 10, 25, 40 ] 学习率衰减位置

Note: 训练速度太慢,可以调整学习率改变的位置
输入图像维度为:[ B, 3, H, W ] 经过特征融合,上采样过程 得到 feature map : [ B, C, H, W ],其中 C 的大小为确定的 kernel_num,设置为7。
对于一个文本实例,有几个对应的内核,每个内核与原始的整个文本实例共享相似的形状,并且它们都位于相同的中心点但在比例上不同。

6.4 issue

  1. 当 long_size 设置到 2240+ 时速度慢,但是设置小一点的时候出现检测框往上飘的情况?

  2. 当出现 ImportError: libopencv_dnn.so.3.4: cannot open shared object file: No such file or directory 的错误时,将 opencv-3.4.6 复制过来,然后执行

    export LD_LIBRARY_PATH=/mnt/libo/opencv-3.4.6/build/lib/:$LD_LIBRAR
    

6.5 编译遇到的一些问题

6.5.1 python3 版本的编译注意事项:

step1: 修改 common.h

/*
#include <python2.7/Python.h>
#include <python2.7/frameobject.h>
#include <python2.7/pythread.h>
*/

#include <python3.6m/Python.h>
#include <python3.6m/frameobject.h>
#include <python3.6m/pythread.h>

step2: 修改 init.py

import subprocess
import os
import numpy as np
import time

BASE_DIR = os.path.dirname(os.path.realpath(__file__))
# print(BASE_DIR)

# import sys
# sys.path.append('./')

# if subprocess.call(['make', '-C', BASE_DIR]) != 0:
#     raise RuntimeError('Cannot compile pse: {}'.format(BASE_DIR))

# if subprocess.call(['mingw32-make', '-C', BASE_DIR]) != 0:
#     raise RuntimeError('Cannot compile pse: {}'.format(BASE_DIR))

from .adaptor import pse as cpse
def pse(polys, min_area):
    # start = time.time()
    ret = np.array(cpse(polys, min_area), dtype='int32')
    # end = time.time()
    # print (end - start), 's'
    return ret

step 3: 修改 Makefile

CXXFLAGS = -I include  -std=c++11 -O3 -I /root/miniconda3/envs/libo_py3/include
# CXXFLAGS = -I include  -std=c++11 -O3(-I/D:/software/Anaconda3/include/python3.6m)

DEPS = lanms.h $(shell find include -xtype f)
CXX_SOURCES = adaptor.cpp include/clipper/clipper.cpp

OPENCV = `pkg-config --cflags --libs opencv`
# INCLUDES = -I/D:/software/Anaconda3/include
# LIBS = -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml
# LIBDIRS = -L/D:/software/Anaconda3/lib

LIB_SO = adaptor.so

$(LIB_SO): $(CXX_SOURCES) $(DEPS)
	$(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(CXX_SOURCES) --shared -fPIC $(OPENCV)

clean:
	rm -rf $(LIB_SO)

step 4: 此刻应该可以编译通过了

cd pse
make

6.5.2 SSL: CERTIFICATE_VERIFY_FAILED

当在加载 ResNet 预训练模型的时候,若报错:urllib.error.URLError: urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] 则在 fpn_resnet.py 中添加以下代码即可解决

import ssl
ssl._create_default_https_context = ssl._create_unverified_context
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

libo-coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值