DBNet 解析

1. 背景介绍

文本检测分为基于回归和基于分割两种方法,DBNet 的原理是基于分割算法。对于一般分割算法流程:先通过网络输出文本分割的概率图,然后使用设定阈值将概率图转化为二值图,然后通过后处理得到检测结果(文本框坐标)。但是缺点在于阈值的选取非常关键。

DBNet 针对这个问题,提出可微分二值化的概念:即对每一个像素点进行自适应二值化,二值化阈值由网络学习得到,彻底将二值化这一步骤加入到网络里一起训练,这样最终的输出图对于阈值就会非常鲁棒。

自适应阈值

上图中蓝线部分就是传统的文本检测算法流程:

  • 首先,通过设置一个固定阈值将分割网络训练得到的概率图(segmentation map)转化为二值图(binarization map)。
  • 然后,使用一些启发式技术(例如像素聚类)将像素分组为文本实例。

上图红色部分就是 DBNet 的算法流程:

通过分割网络获取概率图和阈值图,阈值图是网络预测得出,并不是固定的值,这样就可以很好将背景与前景分离出来,但是这样的操作会给训练带来梯度不可微的情况,对此对于二值化提出了一个叫做 Differentiable Binarization来解决不可微的问题。

2. DBNet 算法整体架构

上图是 DBNet 模型网络结构示意图,主要分为 3 个模块:

  • 第一模块(1):左边的红框使用的是一个 FPN 结构,分为自底向上的卷积操作与自顶向下的上采样,以此来获取多尺度的特征。1 图下面部分是 3x3 的卷积操作,按照卷积公式分别获取原图大小比例的 1/2、1/4、1/8、1/16、1/32 的特征图;然后自顶向下进行上采样 x2,然后与自底向上生成的相同大小的特征图融合;融合之后再采用 3x3 的卷积消除上采样的混叠效应;最后对每层输出结果进行上采样,统一为 1/4 大小的特征图。
  • 第二模块(2):将 1/4 大小的特征图经过一系列卷积和转置卷积的机构获取概率图 P 和阈值图 T,可参考 FCN 网络结构,目的是生成与原图一样大小的特征图 P 和 T。
  • 第三模块(3):将特征图 P 和 T 经过 DB 方法(后续介绍)得到近似二值图。

经过上面三个模块,可以得到概率图、阈值图和近似二值图。在训练过程中,对这三个图进行监督学习,更新各个模块的参数。在推理过程中,直接使用概率图,然后使用固定阈值获取结果。

下面介绍可微二值化的发展流程:

标准二值化
在传统的图像分割算法中,我们获取概率图后,会使用标准二值化(Standard Binarize)方法进行处理,将低于阈值的像素点置0,高于阈值的像素点置1,公式如下:
B_{i, j}=\left\{\begin{array}{c} 1, \text { if } P_{i, j}>=t \\ 0, \text { otherwise } \end{array}\right.
t 是预先设置的阈值,(i,j)代表的是像素点坐标位置。可见标准的二值化是不可微的,所以也就无法放入到网络中进行优化学习。

可微二值化
可微二值化就是将标准二值化中的阶跃函数进行了近似,公式如下所示:
\hat{\boldsymbol{B}}=\frac{1}{1+e^{-k\left(P_{i, j} - T_{i, j}\right)}}
可微二值化本质上是一个 带系数 k 的 sigmoid 函数,取值范围为(0,1),k 是膨胀因子(经验型设置为50)。P_{i,j} 是指概率图像素点,T_{i,j} 是指阈值图像素点。

标准二值化和可微二值化的对比如下图 (a) 所示,SB 曲线代表标准二值化,DB 代表可微二值化,可以看到曲线变得更为平滑,也就是可微:

除了可微之外,DB 方法也会改善算法的性能,在反向传播是梯度的计算上进行观察。当使用交叉熵损失(y=1代表文字区域)时,正负样本的 loss 分别为 l_+l_-,公式如下:
交叉熵损失函数:L=-[y \log \hat{B}+(1-y) \log (1-\hat{B})] \\正样本(y=1)损失:l_{+}=-\log \left(\frac{1}{1+e^{-k\left(P_{i, j}-T_{i, j}\right)}}\right) \\负样本(y=0)损失:l_{-}=-\log \left(1-\frac{1}{1+e^{-k\left(P_{i, j}-T_{i, j}\right)}}\right)

对输入 x = P_{i, j}-T_{i, j} 求偏导,则会得到:
\frac{\delta l_{+}}{\delta x}=-k f(x) e^{-k x}, \\ \frac{\delta l_{-}}{\delta x}=-k f(x) ,

可以看到 增强因子k 对于错误预测对梯度的影响变大了,从而可以促进模型的优化过程产生更为清晰的预测结果。对于x>0时,按照(a)图属于正样本(文字区域),x<0时属于负样本(非文字区域)。
上图(b)是指 l_{+} 的导数曲线,如果发生漏报(正样本被预测为负样本x<0),图(b)小于 0 的部分导数非常大,证明损失也是非常大的,则更能清晰的进行梯度回传。同理,图(c)代表 l_{-}的导数曲线,当发生误报(负样本被预测为正样本x>0),导数也是非常大的,损失也比较大。

3. 真实标签生成

DB 网络中,训练过程中网络有 3 个输出:概率图、阈值图和近似二值图:

  • 概率图:图中每个像素点的值为该位置属于文本区域的概率。
  • 阈值图:图中每个像素点的值为该位置的二值化阈值。
  • 近似二值图:由概率图和阈值图通过 DB 算法计算得到,图中像素的值为 0 或 1。

概率图的标签 G_{s}和阈值图标签G_{d},DB 网络参考 PSENet 中的方法,使用扩张和收缩的方式构建阈值图和概率图。在该方法中,对于一幅文字图像,文本区域的每个多边形使用一组线段G=\left\{S_{k}\right\}_{k=1}^{n} 来进行描述, n 为线段个数。

概率图标签G_{s}
对于概率图和近似二值图来说,使用收缩的方式构建标签(Vatti clipping算法),收缩的偏移量D由多边形的周长 L 和面积 A 计算得到,公式如下:其中, r 是收缩因子,实验中经验设置为 0.4 。
D=\frac{A\left(1-r^{2}\right)}{L}

阈值图标签G_{d}

  • 首先使用G_{s}计算过程中的偏移量 D 进行多边形的扩充。得到G_{s}G_{d}之间的区域。
  • 计算之间区域到原始框的距离,并得到最近边(长方形就是 4 条边)的距离。最外面的大框线上区域和最里面的小框线上区域计算为 D ,原始框位置的距离为 0。
  • 进行第一次的归一化(除以D),这样距离控制到 [0,1] 之间,并且最中间的区域越接近0,越里面和越外面的区域越接近1。然后使用 1-X 操作,让越中心的距离为 1,越边缘的距离为 0。(这样图片显示就是中间亮两头暗)。
  • 最终再进行缩放,比如归一化到 [0.3,0.7] 的值。

4. 损失函数

由于在训练阶段输出 3 个预测图(大小与原图一致),所以在损失函数中,也需要有对应的真实标签去构建 3 部分损失函数。总的损失函数的公式定义如下:
L=L_{b}+\alpha \times L_{s}+\beta \times L_{t}
其中,L为总的损失,L_{b}为近似二值图的损失,使用 Dice 损失;L_{s}为概率图损失,使用带 OHEM 的 Dice 损失;L_{t}为阈值图损失,使用预测值和标签间的 𝐿1 距离。其中,𝛼 和 𝛽 为权重系数。
接下来分析这 3 个loss:
1)首先是Dice Loss,Dice Loss是比较预测结果跟标签之间的相似度,常用于二值图像分割。
diceloss =1-\frac{2 \times intersection area }{total area}
2)其次是MaskL1 Loss,是计算预测值和标签间的𝐿1距离
3)最后是Balance Loss,是带OHEM的Dice Loss,目的是为了改善正负样本不均衡的问题。OHEM 为一种特殊的自动采样方式,可以自动的选择难样本进行loss的计算,从而提升模型的训练效果。

Dice Loss (迁移)
dice loss=1-\frac{2|X \bigcap Y|}{|X|+|Y|}
其中 |X∩Y| 是X和Y之间的交集,|X|和|Y|分表表示X和Y的元素的个数,其中,分子的系数为2,是因为分母存在重复计算X和Y之间的共同元素的原因。
同时,一般会加入平滑因子,防止分子分母全为0。对于分割任务而言,|X| 和 |Y| 代表分割的 ground truth 和 predict_mask。

计算步骤:

  • 首先,使用预测图 predict_mask 和 ground truth 之间的点乘。


  • 逐元素相乘的结果元素的相加和。


  • 计算|X|和|Y|,这里可以采用直接元素相加,也可以采用元素平方求和的方法:


5. 模型训练流程

以 PaddleOCR (https://github.com/PaddlePaddle/PaddleOCR/blob/0791714b91/deploy/lite/readme.md) 为例,模型的训练流程分为以下部分:

  • 数据预处理:主要包括解析图片、真实标签处理、随机裁剪和图片增强。
  • 模型结构部分:分为 Backbone、Neck、Head 部分。
  • loss 部分:分为 概率图 L_{s}、阈值图 L_{t}、近似二值图 L_{b}
  • metric 部分:经过后处理之后进行评价指标的计算。

5.1 数据预处理
数据预处理包含以下部分:
DecodeImage 解析图片、DetLabelEncode 解析 label 文件、IaaAugment 进行数据增强、EastRandomCropData 随机裁剪(裁剪到指定 size (960, 960, 3))、MakeBorderMap 阈值图真实标签生成(batch, 960, 960)、MakeShrinkMap 概率图标签生成(batch, 960, 960)、NormalizeImage 归一化、ToCHWImage 纬度变化为(3, 960, 960)、KeepKeys 指定格式。

经过数据预处理 image 字段的大小变为(batch, 3, 960, 960)。

5.2 模型前向传播
模型依次经过 backbone(MobileNetV3)--> neck(DBFPN) --> head(DBHead) 步骤,纬度变化如下所示:

输入的 images size: [2, 3, 960, 960],batch 为 2
backbone 输出1:backbone behind [2, 16, 240, 240],尺寸对应 1/4
backbone 输出2:backbone behind [2, 24, 120, 120],尺寸对应 1/8
backbone 输出3:backbone behind [2, 56, 60, 60],尺寸对应 1/16
backbone 输出4:backbone behind [2, 480, 30, 30],尺寸对应 1/32
neck 输出:neck behind [2, 96, 240, 240],输出 1/4
head 输出:head behind [2, 3, 960, 960],与原图大小一致,分别代表 shrink_maps, threshold_maps, binary_maps,大小都为 [960, 960]

5.3 后处理
后处理的逻辑流程如下:

  • 首先,对概率图进行固定阈值处理,得到分割图。
  • 对分割图计算轮廓,遍历每个轮廓,去除太小的预测;对每个轮廓计算包围矩形,然后计算该矩形的预测score。
  • 对矩形进行反向shrink操作,得到真实矩形大小;最后还原到原图 size 就可以了。

下面对 python 代码进行解析:
pred 是模型的输出,shape 为 (1,height, weight),训练阶段都为(1,960,960),推理阶段则不一定。

查看 boxes_from_bitmap 的代码:

def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height):
        '''
        _bitmap: single map with shape (1, H, W),
                whose values are binarized as {0, 1}
        '''

        bitmap = _bitmap
        height, width = bitmap.shape
        
        # findContours 获取轮廓,如长方形获取四点顶点坐标
        outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST,
                                cv2.CHAIN_APPROX_SIMPLE)
        # py2、py3 不同版本的情况
        if len(outs) == 3:
            img, contours, _ = outs[0], outs[1], outs[2]
        elif len(outs) == 2:
            contours, _ = outs[0], outs[1]
        
        # 文本框最大数量
        num_contours = min(len(contours), self.max_candidates)

        boxes = []
        scores = []
        for index in range(num_contours):
            contour = contours[index]
            #  计算最小包围矩,获取四个坐标点,左上为起点(顺时针)
            points, sside = self.get_mini_boxes(contour)
            # 长方形中宽高最小值过滤
            if sside < self.min_size:
                continue
            points = np.array(points)
            # 利用 points 内部预测概率值,计算出一个score,作为实例的预测概率
            score = self.box_score_fast(pred, points.reshape(-1, 2))
            # score 得分的过滤
            if self.box_thresh > score:
                continue
            # shrink反向还原,之前概率图进行了缩放,故还原
            box = self.unclip(points).reshape(-1, 1, 2)
            box, sside = self.get_mini_boxes(box)
            if sside < self.min_size + 2:
                continue
            box = np.array(box)
            
            # 还原到原始坐标,反向还原之后,还需要还原到原始图片(原始图片在预处理时被缩放处理)
            box[:, 0] = np.clip(
                np.round(box[:, 0] / width * dest_width), 0, dest_width)
            box[:, 1] = np.clip(
                np.round(box[:, 1] / height * dest_height), 0, dest_height)
            boxes.append(box.astype(np.int16))
            scores.append(score)
        return np.array(boxes, dtype=np.int16), scores

def get_mini_boxes(self, contour):
        # 返回点集 cnt 的最小外接矩形:# 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
        bounding_box = cv2.minAreaRect(contour)
        # 排序,最终以左上的坐标为起点,顺时针排列四个坐标点
        points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])

        index_1, index_2, index_3, index_4 = 0, 1, 2, 3
        if points[1][1] > points[0][1]:
            index_1 = 0
            index_4 = 1
        else:
            index_1 = 1
            index_4 = 0
        if points[3][1] > points[2][1]:
            index_2 = 2
            index_3 = 3
        else:
            index_2 = 3
            index_3 = 2

        box = [
            points[index_1], points[index_2], points[index_3], points[index_4]
        ]
        return box, min(bounding_box[1])

6. 模型推理流程

如果不考虑 label,则其处理逻辑和训练逻辑有一点不一样,其把图片统一 resize 到指定的长度进行预测。

数据预处理
数据预处理没有训练阶段的数据增强、随机裁剪和生成标签部分,但是会存在一个 resize 的操作,将宽高设置为 32 的倍数。

DetResizeForTest 步骤如下:
1)对图片进行等比例的放缩,设置最大的尺寸为 960。如 3 张图片分别为 (720,1280)、(230,230)、(1150,720)。
2)对图片进行放缩,以最大边为准(操作960的直接放缩到 960),缩小至能被 32 整除的最大尺寸。则输出:(512,960,3)、(224,224,3)、(960,576,3)。由于宽高都为 32 的倍数,则放缩的比例不统一,但尽可能相差不大。

后续的操作就与训练阶段一致,通过网络结构,然后进行后处理获得结果。

7. 参考文献

  1. Real-time Scene Text Detection with Differentiable Binarization
  2. PaddlePaddle DBNet 介绍
  3. DBNet 详解
  4. DBNet论文阅读笔记(附论文和源码地址)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容