1.OCR--文本检测算法FCENet

news2024/11/14 5:32:25

文章目录

    • 1.简介
    • 2.主要工作
      • 2.1 傅里叶轮廓嵌入(Fourier Contour Embedding)
      • 2.2 `FCE`模型
      • 3.代码实现
    • 参考资料


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


论文:Fourier Contour Embedding for Arbitrary-Shaped Text Detection

1.简介

这篇文章是华南理工大学的Yiqin Zhu在2021年04月份发表的有关OCR中做文本检测的工作。一般OCR工作分两步,一步是对文本区域进行检测,先得到文本区域,然后再将检测的文本区域转化成文本

文本检测的复杂性在于文本区域的步规则性和多样性,常用的在图像空间域做文本检测方法有掩码,像素的笛卡尔或极坐标坐标轮廓点。使用掩码来做需要对图像进行像素级分类后处理时间较长,使用轮廓像素点在处理弯曲文本区域时略显无力。文本检测的方法可粗略的分成基于分割的方法和基于回归的方法。

作者提出的方法在频域对文本区域做检测,使用Fourier Contour Embedding(FCE)方法来表示任意行状的文本轮廓。FCENet模型中使用了骨干网络(backbone),特征金字塔往略(Feature Pyramid Network),带傅里叶逆变换(Inverse Fourier Transform,IFT)的后处理,和非极大值抑制。FCENet最大的创新在于其使用神经网络直接对文本轮廓点的傅里叶变换做预测,然后再使用IFT求得最后的文本轮廓。

使用 k k k表示傅里叶级数的项数,可以看到随着 k k k的增加,引入了更多的高频信号,轮廓刻画的就更准确。

在这里插入图片描述

从上图(a)中可以看到,当 k = 125 k=125 k=125时所刻画的轮廓已经十分接近真实的图像了。图(b)和图©中红色轮廓线分别是使用TextRayFCENet来做文本检测所得到的结果,绿色的是真实的文本轮廓线,可以看到对弯曲的文本,FCENet检测效果更好。

2.主要工作

2.1 傅里叶轮廓嵌入(Fourier Contour Embedding)

对于图像中使用像素坐标点 ( x , y ) (x,y) (x,y)表示的轮廓,可以使用复数函数 f : R ↦ C f:\mathbb{R}\mapsto \mathbb{C} f:RC来表示, f f f定义成关于实值 t , t ∈ [ 0 , 1 ] t,t\in[0,1] t,t[0,1]变化的形式,

f ( t ) = x ( t ) + i y ( t ) f(t) = x(t) + iy(t) f(t)=x(t)+iy(t)

其中 i i i是复数的虚部单元, ( x ( t ) , y ( t ) ) (x(t),y(t)) (x(t),y(t))表示 t t t时刻轮廓线上的某个空间坐标点, f f f表示封闭轮廓时, f ( t ) = f ( t + 1 ) f(t)=f(t+1) f(t)=f(t+1)

到这里轮廓线上点的坐标 ( x , y ) (x,y) (x,y)就被表示成了一系列的复数 x + i y x+iy x+iy, 我们知道使用傅里叶变换可将函数从时域变换到频率域,因此,同样可以对复数做傅里叶变换和反变换。

若已经知道 f ( t ) f(t) f(t)的傅里叶变换,那么可以通过傅里叶反变换(Inverse Fourier Transformation,IFT)来求 f ( t ) f(t) f(t),使用傅里叶反变换来表示 f ( t ) f(t) f(t)为:

f ( t ) = f ( t , c ) = ∑ k = − ∞ + ∞ c k e 2 π i k t f(t) = f(t, \mathbf{c}) = \sum_{k=-\infty}^{+\infty}{\mathbf{c}_ke^{2\pi i k t}} f(t)=f(t,c)=k=+cke2πikt

其中, k ∈ Z k\in\mathbb{Z} kZ整数表示频率, c k c_k ck是对应频率的傅里叶级数的系数 c k c_k ck是复数,傅里叶级数的每一项 c k e 2 π i k t \mathbf{c}_ke^{2\pi ikt} cke2πikt可以看成以初始方向向量 c k \mathbf{c}_k ck固定频率 k k k表示的圆周运动,如此轮廓就可以看成是无数个圆周运动的叠加。

在这里插入图片描述

图像中的轮廓曲线一般很难得到其函数表示 f f f,因此可以在轮廓上离散采样 N N N个点来表示轮廓(上图中的绿色点),第 n n n个轮廓点可以使用 f ( n N ) , n ∈ [ 1 , . . . , N ] f(\frac{n}{N}),n\in[1,...,N] f(Nn),n[1,...,N]来表示,那么傅里叶系数 c k \mathbf{c}_k ck可写成:

c k = 1 N ∑ n = 1 N f ( n N ) e − 2 π i k n N \mathbf{c}_k=\frac{1}{N}\sum_{n=1}^{N}f(\frac{n}{N})e^{-2\pi ik\frac{n}{N}} ck=N1n=1Nf(Nn)e2πikNn

利用欧拉公式, c k \mathbf{c}_k ck可写成复数形式:

c k = u k + i v k \mathbf{c}_k = u_k+iv_k ck=uk+ivk
u k , v k u_k,v_k uk,vk分别表示复数的实部和虚部。

k = 0 k=0 k=0时, c 0 = u 0 + i v 0 = 1 N ∑ n = 1 N f ( n N ) \mathbf{c}_0=u_0+iv_0=\frac{1}{N}\sum\limits_{n=1}^{N}f(\frac{n}{N}) c0=u0+iv0=N1n=1Nf(Nn),因此 c 0 c_0 c0表示轮廓的中心。对于任意的轮廓 f f f,使用傅里叶变换可将其压缩表示成 2 ( 2 K + 1 ) 2(2K+1) 2(2K+1)的向量 [ u − K , v − K , . . . , u 0 , v 0 , . . . , u K , v K ] [u_{-K},v_{-K},...,u_0,v_0,...,u_K,v_K] [uK,vK,...,u0,v0,...,uK,vK],这个向量也被称之为傅里叶签名向量(Fourier Signature Vector)

傅里叶轮廓嵌入算法(Fourier Contour Embedding)算法分两步,第一步是对轮廓进行采样离散化,譬如在轮廓上均匀采样400个点 N = 400 N=400 N=400,可以得到一个包含400个点的轮廓采样点序列 [ f ( 1 N ) , . . . , f ( 1 ) ] [f(\frac{1}{N}),...,f(1)] [f(N1),...,f(1)],通过这一步采样,可以使得FCE算法支持更多的数据集合,因为不同数据集标注的文本区域轮廓点的数目并不一样。第二步就是进行傅里叶变换和反变换,求目标值和文本区域的检测框坐标。

采样策略:

  • 采样起始点 f ( 0 ) f(0) f(0)为,过轮廓中心点 ( u 0 , v 0 ) (u_0,v_0) (u0,v0)的水平线与轮廓右侧的交点
  • 采样方向,顺时针
  • 匀速,每两个采样点的距离相同

可以通过一段代码来看:

import numpy as np
import matplotlib.pyplot as plt

x = np.array(list(range(10, 210, 10)))
y = np.array([10]*len(x))
x = x[:,None]
y = y[:,None]
pnts = np.concatenate((x,y), axis=-1)

y = np.array(list(range(20, 120, 10)))
x = np.array([200]*len(y))
x[5] = 220
x[6] = 214
x[7] = 207
x[8] = 203

x = x[:,None]
y = y[:,None]
tmp_pnts = np.concatenate((x,y), axis=-1)
pnts = np.vstack((pnts, tmp_pnts))

x = np.array(list(range(190, 0, -10)))
y = np.array([110]*len(x))
x = x[:,None]
y = y[:,None]
tmp_pnts = np.concatenate((x,y), axis=-1)
pnts = np.vstack((pnts, tmp_pnts))

y = np.array(list(range(100, 10, -10)))
x = np.array([10]*len(y))
x = x[:,None]
y = y[:,None]
tmp_pnts = np.concatenate((x,y), axis=-1)
pnts = np.vstack((pnts, tmp_pnts))

pt1 = pnts[:24,:]
pt2 = pnts[24:,:]
pts = np.vstack((pt2, pt1))

complex_pts = pts[:, 0] + pts[:, 1]*1j
ft_pts = np.fft.fft(complex_pts)

approx_ft_pts = np.zeros_like(ft_pts)
# 信号主要由高频和低频组成,中间频率所占很少
approx_ft_pts[:6] = ft_pts[:6] # 低频信号恢复大致轮廓
approx_ft_pts[-5:] = ft_pts[-5:] #高频信号恢复准确轮廓
appox_pts = np.fft.ifft(approx_ft_pts)

# help(plt.plot)
plt.figure(num=1, figsize=(4.5,3), dpi=200)
x = pts[:, 0]
y = pts[:, 1]
plt.plot(x,y,'g-', label='ground truth', linewidth=1 )
x = [e.real for e in appox_pts]
y = [e.imag for e in appox_pts]

plt.plot(x,y,'r-', label='approx truth', linewidth=1 )
plt.legend()
plt.show()

在这里插入图片描述

上图中,绿色的表示实际形状,红色的表示经傅里叶变换和反变换后,只保留 K = 5 K=5 K=5个频率的分量而得到的轮廓尺寸,可以看到这能极大的压缩表示轮廓的参数数量,同时还能对轮廓进行较好的近似。

2.2 FCE模型

在这里插入图片描述

FCENet的结构同常规的检测模型,其backbone由使用可变形卷积DCNResNet50组成,FPN用来提取多尺度的特征,检测头使用以上介绍的傅里叶方法实现。

如上图,检测头由分类和回归两个分支组成,分类分支中,输出的结果通道数是4,前两个通道表示的是每个像素是否是文本区域(Text Region,TR)的概率,后两个通道表示的是每个像素是否是文本中心区域(Text Center Region)的概率,分类分支相当于是分割得到文本区域,然后求文本区域的轮廓中心

回归分支的通道数 22 22 22,表示的是取傅里叶展开的自由度 k = 5 k=5 k=5,取前5个高频和低频及 k = 0 k=0 k=0 11 11 11个复数傅里叶系数,复数使用 ( u k , v k ) (u_k,v_k) (uk,vk)来表示,因此总共 22 22 22个变量,通过使用傅里叶逆变换求得最后的检测结果。

3.代码实现

见mmocr: mmocr/models/textdet/postprocessors/fce_postprocessor.py

class FCEPostprocessor(BaseTextDetPostProcessor):
     def __init__(self, **args)...
     def _get_text_instances_single(self, pred_result: Dict, scale: int):
          """Get text instance predictions from one feature level.

          Args:
               pred_result (dict): A dict with keys of ``cls_res``, ``reg_res``
                    corresponding to the classification result and regression
                    result computed from the input tensor with the same index.
                    They have the shapes of :math:`(1, C_{cls,i}, H_i, W_i)` and
                    :math:`(1, C_{out,i}, H_i, W_i)`.
               scale (int): Scale of current feature map which equals to
                    img_size / feat_size.

          Returns:
               result_polys (list[ndarray]): A list of polygons after postprocess.
               result_scores (list[ndarray]): A list of scores after postprocess.
          """

          cls_pred = pred_result['cls_res']
          tr_pred = cls_pred[0:2].softmax(dim=0).data.cpu().numpy()
          tcl_pred = cls_pred[2:].softmax(dim=0).data.cpu().numpy()

          reg_pred = pred_result['reg_res'].permute(1, 2, 0).data.cpu().numpy()
          x_pred = reg_pred[:, :, :2 * self.fourier_degree + 1]
          y_pred = reg_pred[:, :, 2 * self.fourier_degree + 1:]

          score_pred = (tr_pred[1]**self.alpha) * (tcl_pred[1]**self.beta)
          tr_pred_mask = (score_pred) > self.score_thr
          tr_mask = fill_hole(tr_pred_mask)

          tr_contours, _ = cv2.findContours(
               tr_mask.astype(np.uint8), cv2.RETR_TREE,
               cv2.CHAIN_APPROX_SIMPLE)  # opencv4

          mask = np.zeros_like(tr_mask)

          result_polys = []
          result_scores = []
          for cont in tr_contours:
               deal_map = mask.copy().astype(np.int8)
               cv2.drawContours(deal_map, [cont], -1, 1, -1)

               score_map = score_pred * deal_map
               score_mask = score_map > 0
               xy_text = np.argwhere(score_mask)
               dxy = xy_text[:, 1] + xy_text[:, 0] * 1j

               x, y = x_pred[score_mask], y_pred[score_mask]
               c = x + y * 1j
               c[:, self.fourier_degree] = c[:, self.fourier_degree] + dxy
               c *= scale

               polygons = self._fourier2poly(c, self.num_reconstr_points)
               scores = score_map[score_mask].reshape(-1, 1).tolist()
               polygons, scores = self.poly_nms(polygons, scores, self.nms_thr)
               result_polys += polygons
               result_scores += scores

          result_polys, result_scores = self.poly_nms(result_polys,
                                                       result_scores,
                                                       self.nms_thr)

          if self.text_repr_type == 'quad':
               new_polys = []
               for poly in result_polys:
                    poly = np.array(poly).reshape(-1, 2).astype(np.float32)
                    points = cv2.boxPoints(cv2.minAreaRect(poly))
                    points = np.int0(points)
                    new_polys.append(points.reshape(-1))

               return new_polys, result_scores
          return result_polys, result_scores

     def _fourier2poly(self,
                         fourier_coeff: np.ndarray,
                         num_reconstr_points: int = 50):
          """ Inverse Fourier transform
               Args:
                    fourier_coeff (ndarray): Fourier coefficients shaped (n, 2k+1),
                    with n and k being candidates number and Fourier degree
                    respectively.
                    num_reconstr_points (int): Number of reconstructed polygon
                    points. Defaults to 50.

               Returns:
                    List[ndarray]: The reconstructed polygons.
               """

          a = np.zeros((len(fourier_coeff), num_reconstr_points),
                         dtype='complex')
          k = (len(fourier_coeff[0]) - 1) // 2

          a[:, 0:k + 1] = fourier_coeff[:, k:]
          a[:, -k:] = fourier_coeff[:, :k]

          poly_complex = ifft(a) * num_reconstr_points
          polygon = np.zeros((len(fourier_coeff), num_reconstr_points, 2))
          polygon[:, :, 0] = poly_complex.real
          polygon[:, :, 1] = poly_complex.imag
          return polygon.astype('int32').reshape(
               (len(fourier_coeff), -1)).tolist()


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


参考资料

  • 1.https://github.com/open-mmlab/mmocr
  • 2.数字处理第4版p153-p155

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式之中介模式与解释器模式详解和应用

目录1 中介模式详解1.1 中介模式的定义1.1.1 中介者模式在生活场景中应用1.1.2 中介者模式的使用场景1.2 中介模式的通用实现1.2.1 类图设计1.2.2 代码实现1.3 中介模式应用案例之聊天室1.3.1 类图设计1.3.2 代码实现1.4 中介者模式在源码中应用1.4.1 jdk中Timer类1.5 中介者模…

logd守护进程

logd守护进程1、adb logcat命令2、logd守护进程启动2.1 logd文件目录2.2 main方法启动3、LogBuffer缓存大小3.1 缓存大小优先级设置3.2 缓存大小相关代码位置android12-release1、adb logcat命令 命令功能adb bugreport > bugreport.txtbugreport 日志adb shell dmesg >…

kafka-11-kafka的监控工具和常用配置参数

kafka官方文档 参考Kafka三款监控工具比较 1 查看kafka的版本 进入kafka所在目录,通过查看libs目录下的jar包。 2.11是scala的版本,2.0.0是kafka的版本。 测试环境 #systemctl start zookeeper #systemctl start kafkka 2 kafka的常用配置 Kafka使用…

SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】

SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】 目录SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】一、创建一个SpringBoot项目二、修改pom.xml中SpringBoot的版本三、配置文件3.1 application-dev.ym…

2.TCP/UDP什么时候选择,HTTP,使用TCP/UDP的协议有哪些,TCP三次握手四次挥手大概流程,为什么要三次握手.

文章目录1.什么时候选择 TCP,什么时候选 UDP?2. HTTP 基于 TCP 还是 UDP?3.使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?4.TCP 三次握手和四次挥手(非常重要、传输层)5.为什么要三次握手?1.什么时候选择 TCP,什么时候选 UDP? UDP 一般…

Spring Cloud Nacos实战(五)- 命名空间分组和DataID三者关系

Nacos命名空间分组和DataID三者关系 名词解释 命名空间(Namespace) ​ 用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发…

Kubernetes一 Kubernetes之入门

二 Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上,主要经历了三个时代: 传统部署:互联网早期,会直接将应用程序部署在物理机上 优点:简单,不需要其它技术的参与 缺点:不能为应…

机器学习实战教程(六):决策树

决策树 决策树是什么?决策树(decision tree)是一种基本的分类与回归方法。举个通俗易懂的例子,如下图所示的流程图就是一个决策树,长方形代表判断模块(decision block),椭圆形成代表终止模块(terminating block),表示…

SpringBoot实战——个人博客项目

目录 一、项目简介 二、项目整体架构 数据库模块 后端模块 前端模块 三、项目具体展示 四、项目的具体实现 1、一些准备工作 🍎数据库、数据表的创建 🍎设置数据库和MyBatis的配置 🍎将前端项目引入到当前项目中 2、登录注册模块 &…

CV学习笔记-Inception

CV学习笔记-Inception 目录 文章目录CV学习笔记-Inception目录1. 常见的卷积神经网络2. Inception(1) Inception提出背景(2) Inception module 核心思想3. Inception的历史版本(1) InceptionV1-GoogleNet(2) InceptionV2(3) InceptionV3(4) Inception V44. Inception模型的特点…

一起学 pixijs(4):如何绘制文字md

大家好,我是前端西瓜哥,今天我们来学 pixijs 如何绘制文字。pixijs 版本为 7.1.2。 使用原生的 WebGL 来绘制文字是非常繁琐的,pixijs 对此进行了高层级的封装,提供了 Text 类和 BitMapText 类来绘制文字。 Text 最基本的写法&…

【Flutter入门到进阶】Dart进阶篇---Dart异步编程

1 并行与并发的编程区别 1.1 并发与并行 1.1.1 说明 我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车辆就是 8 辆此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可以并行运行。 CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数…

Ubuntu20.04如何安装虚拟机(并安装Android)

安装虚拟机(KVM)这种KVM只能安装windows无法安装安卓(From https://phoenixnap.com/kb/ubuntu-install-kvm)A type 2 hypervisor enables users to run isolated instances of other operating systems inside a host system. As a Linux based OS, Ubun…

Redis第四讲

目录 四、Redis04 4.1 Redis集群应用场景 4.2 集群 4.2.1 基本原理 4.2.2 主从复制的作用 4.3 配置集群(一台虚拟机) 4.3.1 规划网络 4.3.2 创建节点 4.3.3 创建目录 4.3.4 配置redis7001.conf 4.3.5 配置其余文件 4.3.6 后台启动redis 4.3…

【NLP实战】NLTK工具包

“Natural Language Toolkit,自然语言处理工具包,在NLP领域中,最常使用的一个Python库。NLTK是一个开源的项目,包含:Python模块,数据集和教程,用于NLP的研究和开发。NLTK由Steven Bird和Edward …

「可信计算」助力TLS 传输更安全

序言背景(Satuation):TLS 是 TCP/IP 上的传输层安全协议,保护着数以亿万级的数据安全,我们在浏览器中输入的 https,就是受到 TLS 保护的。冲突(complication):从可信计算…

洛谷P8601[蓝桥杯][2013年第四届真题]剪格子

题目描述如图 11 所示,33 的格子中填写了一些整数。我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 6060。本题的要求就是请你编程判定:对给定的 m\times nmn 的格子中的整数,是否可以分割为两个部分&am…

【Java开发】Spring 12 :Spring IOC控制反转和依赖注入(解决单接口多实现类调用)

IOC 是 Inversion of Control 的简写,译为“控制反转”,Spring 通过 IOC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IOC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建…

分享112个HTML艺术时尚模板,总有一款适合您

分享112个HTML艺术时尚模板,总有一款适合您 112个HTML艺术时尚模板下载链接:https://pan.baidu.com/s/1D3-mfPOud-f3vy9yLl-bmw?pwdfph2 提取码:fph2 Python采集代码下载链接:采集代码.zip - 蓝奏云 时尚平面模特网站模板 潮…

Redis数据类型以及应用场景

目录1、 String(字符串)2、 Hash(哈希)3、 List(列表)4、 Set(集合)5、 sorted set(有序集合ZSet)字符串(String)、哈希表&#xff08…