目录

  1. 导言

  2. 现实世界问题

    1. 说明

    2. 问题陈述

    3. 业务目标和约束条件

  3. 可用于文本检测和识别的数据集

    1. 数据集概述和说明

  4. 探索性数据分析(EDA)

  5. 深度学习时代之前的文本检测方法

  6. EAST(高效精确的场景文本检测器)

  7. 示范实现

  8. 模型分析与模型量化

  9. 部署

  10. 今后的工作

1.介绍

在这个数字化时代,从不同来源提取文本信息的需求在很大程度上增加了。

幸运的是,计算机视觉的最新进展减轻了文本检测和其他文档分析和理解的负担。

在计算机视觉中,将图像或扫描文档中的文本转换为机器可读格式的方法称为光学字符识别(OCR),该格式可在以后编辑、搜索并用于进一步处理。

光学字符识别的应用

A.信息检索和自动数据输入-OCR对于许多公司和机构起着非常重要的作用,这些公司和机构有成千上万的文档需要处理、分析和转换,以执行日常操作。

例如,在账户详细信息等银行信息中,可以使用OCR轻松提取支票金额。同样,在机场,在检查护照的同时也可以使用OCR提取信息。其他示例包括使用OCR从收据、发票、表单、报表、合同等中检索信息。

B车牌识别-OCR还可用于识别车辆牌照,然后可用于车辆跟踪、收费等。

C自动驾驶汽车-OCR也可用于建立自动驾驶汽车的模型。它可以帮助识别交通标志。否则,自动驾驶汽车将对道路上的行人和其他车辆构成风险。

在本文中,我们将讨论并实现用于OCR的深度学习算法。

数字化:将文本、图片或声音转换成计算机可以处理的数字形式

2.现实世界问题

2.1说明

我们现在已经熟悉了文本检测和识别的各种应用。本文将讨论从自然场景图像中检测和识别文本。

因此,在我们的例子中,我们使用的是任何自然图像或场景(不特别是文档、许可证或车辆编号),对于给定的图像/场景,我们希望通过边界框定位图像中的字符/单词/句子。然后,我们要识别任何语言的本地化文本。总体工作流程图如下所示:

上面使用的图像用于显示整个任务。但对于本案例研究,我们将使用随机自然场景作为输入图像。

2.2问题陈述

对于给定的自然场景/图像,目标是通过绘制边界框来检测文本区域,然后必须识别检测到的文本。

2.3业务目标和约束条件。

  • 自然场景图像中的文本可以使用不同的语言、颜色、字体、大小、方向和形状。我们必须在自然场景图像中处理这些文本,这些图像具有更高的多样性和可变性。

  • 自然场景的背景可能带有图案或形状与任何文本极其相似的对象,这会在检测文本时产生问题。

  • 图像中断(低质量/分辨率/多方向)

  • 实时检测、识别和翻译图像中的文本需要低延迟。

3.可用于文本检测和识别的数据集

有许多公开的数据集可用于此任务,下面列出了不同的数据集,包括发布年份、图像编号、文本方向、语言和重要功能。

由于非结构化文本、不同方向等原因,所有数据集可能无法很好地适用于所有深度学习模型。

对于这项任务,我选择ICDAR 2015数据,因为它很容易获得足够数量的图像,用于非商业用途,这些图片中的文本是英文的,因为我是一名初学者,我想重点了解解决这项任务的算法的工作原理。

此外,该数据集中的图像很小,具有多方向性和模糊性,因此我可以对检测部分进行更多的实验。

3.1数据集概述和说明

数据源-下载:https://rrc.cvc.uab.es/?ch=4&com=downloads

ICDAR-2015由国际会议文件分析与识别提供

说明:

  • 该数据集在训练和测试集中可用,每一组都有真实标签。它总共包含1500张图像,其中1000张用于训练,500张用于测试。它还包含2077个裁剪文本实例,包括200多个不规则文本示例。

  • 这些图像是从可穿戴相机上获得的。

4.探索性数据分析(EDA)

  • 下载数据后,所有文件的结构如下-

Data(main directory)|||----- ICDAR2015|||-----train ( containing all image files )|||------train_gt ( containing texts and coordinates )|||------test ( containing all image files )|||-----test ( containing texts and coordinates )
  • 使用以下代码,观察到图像尺寸、通道数等其他信息

训练图像

# 获取训练数据帧中所有图像的尺寸、通道和扩展dim, channel, extnsn = [], [], []
for path in df_train['path_img'].values:img = cv2.imread(path)dim.append(img.shape[:2])channel.append(img.shape[2])extnsn.append(path.split('.')[-1])
print('Dimension of all images:',set(dim))
print('No. channels of all images:',set(channel))
print('Extesions of all images:',set(extnsn))
Dimension of all images: {(720, 1280)}
No. channels of all images: {3}
Extesions of all images: {'jpg'}

测试图像

# 测试数据帧中所有图像的尺寸、通道和扩展
dim, channel, extnsn = [], [], []
for path in df_test['path_img'].values:img = cv2.imread(path)dim.append(img.shape[:2])channel.append(img.shape[2])extnsn.append(path.split('.')[-1])
print('Dimension of all images:',set(dim))
print('No. channels of all images:',set(channel))
print('Extesions of all images:',set(extnsn))
Dimension of all images: {(720, 1280)}
No. channels of all images: {3}
Extesions of all images: {'jpg'}
  • 我们还可以从条形图得出结论,所有图像的高度和宽度都相同,即720和1280。

训练图像

测试图像

  • 利用真实标签绘制原始图像和边界框图像

  • 训练图像

测试图像

从EDA得出的结论

  • 在ICDAR-15数据集中,所有图像具有相似的大小(720x1280)和扩展(.jpg)。

  • 训练组有1000个图像,而测试组有500个图像。

  • 所有图像的高度和宽度都是相同的,所以我们不需要取平均高度和平均宽度。

  • 在大多数图像中,所有文本都位于小区域,图像模糊。

  • 所有文本均为英语语言,少数文本也不可用,并且*替换为“###”。

  • 大多数文本都是单个单词,而不是文字和句子,而且单词也有多种意思的。我们必须建立这样一个模型来预测这些模糊的文本。

5.深度学习时代之前的文本检测方法

正如问题陈述中提到的,我们必须首先定位图像中的文本,即首先检测文本,然后识别检测到的文本。

现在,对于检测,我们将尝试一些在深度学习时代之前用于检测文本的方法。

a.MSER

#创建MSER对象for i in range(10,15):filepath = df_train['path_img'][i]img_array = cv2.imread(filepath)plt.figure(figsize=(18,8))plt.subplot(1,2,1)plt.grid(False)   plt.imshow(img_array)plt.subplot(1,2,2)mser = cv2.MSER_create()# 读取图片img = cv2.imread(df_train['path_img'].values[i])# 灰度模式gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 灰度图像中的区域检测regions, _ = mser.detectRegions(gray)# 每个选定区域的外壳hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]# 在图像上绘制多段线cv2.polylines(img, hulls, 1, (0, 255, 0))# 显示带有多段线的图像plt.grid(False)plt.imshow(img)

b. SWT(笔划宽度变换)

#https://stackoverflow.com/questions/11116199/stroke-width-transform-swt-implementation-python
from swtloc import SWTLocalizer
from swtloc.utils import imgshowN, imgshowswtl = SWTLocalizer()
imgpaths = ... # 图像路径,可以是一个图像或多个图像
swtl.swttransform(imgpaths=df_train['path_img'][13], save_results=True, save_rootpath='swtres/',edge_func = 'ac', ac_sigma = 1.0, text_mode = 'db_lf',gs_blurr=True, blurr_kernel = (5,5), minrsw = 3, maxCC_comppx = 10000, maxrsw = 200, max_angledev = np.pi/6, acceptCC_aspectratio = 5.0)
MainProcess @  |                COMPLETE                 | -> STATUS: 0.0% 1/1 Images Done. Transformations Complete
#https://stackoverflow.com/questions/11116199/stroke-width-transform-swt-implementation-python
from swtloc import SWTLocalizer
from swtloc.utils import imgshowN, imgshowswtl = SWTLocalizer()
imgpaths = ... # 图像路径,可以是一个图像或多个图像
swtl.swttransform(imgpaths=df_train['path_img'][13], save_results=True, save_rootpath='swtres/',edge_func = 'ac', ac_sigma = 1.0, text_mode = 'db_lf',gs_blurr=True, blurr_kernel = (5,5), minrsw = 3, maxCC_comppx = 10000, maxrsw = 200, max_angledev = np.pi/6, acceptCC_aspectratio = 5.0)
MainProcess @  |                COMPLETE                 | -> STATUS: 0.0% 1/1 Images Done. Transformations Complete
respacket = swtl.get_grouped(lookup_radii_multiplier=1, sw_ratio=2,cl_deviat=[13,13,13], ht_ratio=2, ar_ratio=3, ang_deviat=30)grouped_labels = respacket[0]
grouped_bubblebbox = respacket[1]
grouped_annot_bubble = respacket[2]
grouped_annot = respacket[3]
maskviz = respacket[4]
maskcomb  = respacket[5]imgshowN([swtl.orig_img, swtl.swt_labelled3C, grouped_annot_bubble],['Original', 'SWT','Bubble BBox Grouping'],figsize=(15,8))

这两种方法的所有输出都不是很清楚,在第一种方法中,我们可以观察到图像中有一些区域没有文本,但仍然用方框标记。同样在第二种方法中,文本没有被正确检测。

还有许多其他用于文本检测和识别的深度学习算法。在本文中,我们将讨论EAST检测器,并将借助一篇关于EAST算法的研究论文尝试实现它。

https://arxiv.org/pdf/1704.03155.pdf

为了识别,我们将尝试预训练的模型Tesseract。

6.EAST(高效精确的场景文本检测器)

它是一种快速准确的场景文本检测方法,包括两个阶段:

1.它使用完全卷积网络(FCN)模型直接生成基于像素的单词或文本行预测

2.生成文本预测(旋转矩形或四边形)后,输出将发送到非极大值抑制以生成最终结果。

管道如下图所示:

网络体系结构-(带PVANet)

PVANet-它是一种用于目标检测的轻量级特征提取网络体系结构,可在不损失准确性的情况下实现实时目标检测性能。

该模型可分为三个部分:主干特征提取、特征合并分支和输出层。

i.特征提取程序(PVANet)

这部分可以是任何卷积神经网络,例如PVANet、VGG16和RESNET50。从该网络可以获得四个级别的特征图f1、f2、f3和f4。因为我们正在提取特征,所以它被称为特征提取器。

ii.特征合并分支

在这一部分中,从特征提取器获得的特征映射首先被馈送到上池化层,使其大小加倍,然后连接所有特征。接下来,使用1X1卷积,减少了计算,然后使用3X3卷积来融合信息,以产生每个合并阶段的最终输出,如图所示。

g和h的计算过程如下图所示

其中

gi是一种中间状态,是合并的基础

hi是合并的特征图

iii、输出层

合并状态的最终输出通过1X1 Conv层和1个通道,该通道给出范围为[0–1]的分数映射。最终输出还通过RBOX或四边形几何体(有关这些的说明如下图所示),该几何体给出了多通道几何体映射。

有关score map和geo map的详细信息将在实现时讨论。

7.实现

对于实现,我们将遵循上面显示的管道-

步骤1-数据准备和数据生成(数据管道)

在这一步中,我们必须进行数据准备,还必须构建一个生成器函数,该函数将提供一个图像阵列(模型的输入),其中包含score map(输出)和geo map(输出),如上图所示,你可以观察到多通道FCN的输出以及训练掩码。

得分图

它表示该位置预测几何地图的置信度分数/级别。它位于[0,1]范围内。让我们通过一个示例来理解它:

假设0.80是一个像素的分数,这意味着对于这个像素,我们有80%的信心预测对应的几何贴图,或者我们可以说,像素有80%的几率是预测文本区域的一部分。

geo map

正如我们所知,随着score map,我们还获得了一个多通道几何信息地图作为输出。几何输出可以是RBOX或QUAD。下表显示了AABB、RBOX和QUAD的通道数量以及描述。

RBOX:

从上图中,我们可以观察到,对于RBOX,几何体使用四通道轴对齐边界框(AABB)R和通道旋转角度θ。R的公式为G。四个通道代表4个距离,即从像素位置到矩形边界的距离和通道的旋转角度,如下所示。

QUAD:

对于四边形,我们使用8个数字表示从四个顶点到每个像素位置的坐标位移。每个偏移距离包含Δxi | Δyi两个数,几何输出包含8个通道。下面是一个例子

在这个实现中,我们将只使用RBOX。

对于生成器功能,我们必须遵循几个步骤

这里有所有的代码

https://jovian.ai/paritosh/data-preparation-and-model-implt

此处显示了从生成器函数输出的原始图像,包括分数贴图、几何贴图和训练掩码-

步骤2:模型建立和损失函数

在这一步中,我们将尝试在Imagenet数据上使用预先训练过的VGG16模型和ResNet50模型作为特征提取器来构建检测器体系结构。

模型1(VGG16作为特征提取器)

源代码-

vgg = tf.keras.applications.VGG16(input_shape=(512,512,3),include_top=False,weights='imagenet')
x = vgg.get_layer('block5_pool').outputx = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_1')(x)
x = tf.keras.layers.concatenate([x,vgg.get_layer('block4_pool').output], axis=3)
x = tf.keras.layers.Conv2D(256, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(256, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_2')(x)
x = tf.keras.layers.concatenate([x, vgg.get_layer('block3_pool').output], axis=3)
x = tf.keras.layers.Conv2D(128, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(128, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_3')(x)
x = tf.keras.layers.concatenate([x, vgg.get_layer('block2_pool').output], axis=3)
x = tf.keras.layers.Conv2D(64, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(64, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.Conv2D(32,kernel_size=3, strides=1,padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.UpSampling2D(size=(4,4),interpolation='bilinear',data_format='channels_last',name='extra')(x)pred_score_map = tf.keras.layers.Conv2D(1, (1, 1), activation=tf.nn.sigmoid, name='pred_score_map',padding='same')(x)
rbox_geo_map = tf.keras.layers.Conv2D(4, (1, 1), activation=tf.nn.sigmoid, name='rbox_geo_map')(x)
rbox_geo_map = tf.keras.layers.Lambda(lambda x: x * 512)(rbox_geo_map)
angle_map = tf.keras.layers.Conv2D(1, (1, 1), activation=tf.nn.sigmoid, name='rbox_angle_map')(x)
angle_map = tf.keras.layers.Lambda(lambda x: (x - 0.5) * np.pi / 2)(angle_map)
output = tf.keras.layers.concatenate([pred_score_map,rbox_geo_map, angle_map], axis=3, name='pred_map')model_vgg = tf.keras.models.Model(inputs=vgg.input, outputs= output,name='EAST')
for layers in vgg.layers:layers.trainable=False

模型架构-

tf.keras.utils.plot_model(model_vgg,show_shapes=True)

模型2(作为特征提取器的ResNet50)

resnet = tf.keras.applications.ResNet50(input_shape=(512, 512, 3), weights='imagenet', include_top=False)
tf.keras.backend.clear_session()
x = resnet.get_layer('conv5_block3_out').outputx = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_1')(x)
x = tf.keras.layers.concatenate([x, resnet.get_layer('conv4_block6_out').output], axis=3)
x = tf.keras.layers.Conv2D(128, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(128, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_2')(x)
x = tf.keras.layers.concatenate([x, resnet.get_layer('conv3_block4_out').output], axis=3)
x = tf.keras.layers.Conv2D(64, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(64, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.UpSampling2D(size=(2,2),interpolation='bilinear',data_format='channels_last',name='resize_3')(x)
x = tf.keras.layers.concatenate([x, resnet.get_layer('conv2_block3_out').output], axis=3)
x = tf.keras.layers.Conv2D(32, (1, 1), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)
x = tf.keras.layers.Conv2D(32, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)x = tf.keras.layers.Conv2D(32, (3, 3), padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-5))(x)
x = tf.keras.layers.BatchNormalization(momentum=0.997, epsilon=1e-5, scale=True)(x)
x = tf.keras.layers.Activation('relu')(x)pred_score_map = tf.keras.layers.Conv2D(1, (1, 1), activation=tf.nn.sigmoid, name='pred_score_map')(x)
rbox_geo_map = tf.keras.layers.Conv2D(4, (1, 1), activation=tf.nn.sigmoid, name='rbox_geo_map')(x)
angle_map = tf.keras.layers.Conv2D(1, (1, 1), activation=tf.nn.sigmoid, name='rbox_angle_map')(x)
angle_map = tf.keras.layers.Lambda(lambda x: (x - 0.5) * np.pi / 2)(angle_map)
output = tf.keras.layers.concatenate([pred_score_map,rbox_geo_map, angle_map], axis=3, name='pred_map')model = tf.keras.models.Model(inputs=resnet.input, outputs= output,name='EAST')
for layers in resnet.layers:layers.trainable=False

模型架构-

tf.keras.utils.plot_model(model,show_shapes=True)

损失函数

当我们处理图像数据时,IOU分数是常用的损失之一。但是这里我们主要有两个输出,score map和geo map,所以我们必须计算两者的损失。

总损失表示为:

Ls和Lg表示得分图和几何形状,λg表示两个权重的重要性。在我们的实验中,我们设定λg为1。

score map损失

在本文中,用于score map的损失是二元交叉熵损失,其权重为正类和负类,如图所示。

geo map损失

对于RBOX,损失定义为

第一个损失是盒子损失,对于这个IOU损失,它是针对不同比例的对象使用的不变量。

对于旋转角度,损失由下式给出-

实现代码如下所示:

# 这是损失函数,用于将点分类为文本和非文本区域
def dice_coefficient(y_true_cls, y_pred_cls,training_mask):'''dice loss:param y_true_cls::param y_pred_cls::param training_mask:x:return:'''eps = 10**-6intersection = tf.reduce_sum(y_true_cls * y_pred_cls * training_mask)union = tf.reduce_sum(y_true_cls * training_mask) + tf.reduce_sum(y_pred_cls * training_mask) + epsloss = 1. - (2 * intersection / union)return lossdef rbox_loss(y_true_cls,y_true_geo,y_pred_geo,training_mask):# d1 -> top, d2->right, d3->bottom, d4->leftd1_gt, d2_gt, d3_gt, d4_gt, theta_gt = tf.split(value=y_true_geo, num_or_size_splits=5, axis=3)d1_pred, d2_pred, d3_pred, d4_pred, theta_pred = tf.split(value=y_pred_geo, num_or_size_splits=5, axis=3)area_gt = (d1_gt + d3_gt) * (d2_gt + d4_gt)area_pred = (d1_pred + d3_pred) * (d2_pred + d4_pred)w_union = tf.minimum(d2_gt, d2_pred) + tf.minimum(d4_gt, d4_pred)h_union = tf.minimum(d1_gt, d1_pred) + tf.minimum(d3_gt, d3_pred)area_intersect = w_union * h_unionarea_union = area_gt + area_pred - area_intersectL_AABB = -tf.math.log((area_intersect + 1.0) / (area_union + 1.0))L_theta = 1 - tf.cos(theta_pred - theta_gt)L_g = L_AABB +  50*L_thetaL_g=tf.squeeze(L_g,axis=3)return tf.reduce_mean(L_g * y_true_cls * training_mask)
# total损失
class total_Loss(tf.keras.losses.Loss):def __init__(self, from_logits=False,reduction=tf.keras.losses.Reduction.AUTO,name='Loss_layer'):super(total_Loss, self).__init__(reduction=reduction, name=name)def call(self, y_true, y_pred):# 获取geo_map和score_mapsy_true_cls=y_true[:,:,:,0]y_pred_cls=y_pred[:,:,:,0]y_pred_geo=y_pred[:,:,:,1:6]y_true_geo=y_true[:,:,:,1:6]training_mask=y_true[:,:,:,6]#1. Dice 损失dice_loss = dice_coefficient(y_true_cls, y_pred_cls, training_mask)# 缩放,以匹配iou损失dice_loss *=0.01rbox_loss_ = rbox_loss(y_true_cls,y_true_geo,y_pred_geo,training_mask)return 100*(rbox_loss_ + dice_loss)

第三步模型训练

使用Adam优化器对这两个模型进行30个epoch的训练,其他参数如下所示-

模型-1

model_vgg.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001,amsgrad=True),loss= total_Loss())

epoch与损失图:

模型2

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001,amsgrad=True),loss=total_Loss())

epoch与损失图:

Step-4 推理管道

首先训练后,几何体贴图将转换回边界框。然后根据score map进行阈值分割,去除一些低置信度盒。使用非最大抑制合并其余框。

非最大抑制(NMS)是许多计算机视觉算法中使用的一种技术。这是一类从多个重叠实体中选择一个实体(例如边界框)的算法

这里我们将使用本地感知的NMS。它将加权合并添加到标准NMS中。所谓加权合并是根据分数在输出框中合并高于某个阈值的2个IOU。下面讨论实施过程中遵循的步骤-

首先,对geo map形进行排序,并从最上面开始。

2.在这一行的下一个方框中找到带有上一个方框的iou

3.如果IOU>threshold,则通过按分数取加权平均值来合并两个框,否则保持原样。

4.重复步骤2至3,直到迭代所有框。

5.最后在剩余的框上应用标准NMS。

实现代码-

非最大抑制

# 在将geo_maps转换为score_maps时,将使用这些函数,并在nms之后返回图像的边界框def sort_poly(p):min_axis = np.argmin(np.sum(p, axis=1))p = p[[min_axis, (min_axis+1)%4, (min_axis+2)%4, (min_axis+3)%4]]if abs(p[0, 0] - p[1, 0]) > abs(p[0, 1] - p[1, 1]):return pelse:return p[[0, 3, 2, 1]]
def intersection(g, p):g = Polygon(g[:8].reshape((4, 2)))p = Polygon(p[:8].reshape((4, 2)))if not g.is_valid or not p.is_valid:return 0inter = Polygon(g).intersection(Polygon(p)).areaunion = g.area + p.area - interif union == 0:return 0else:return inter/uniondef weighted_merge(g, p):g[:8] = (g[8] * g[:8] + p[8] * p[:8])/(g[8] + p[8])g[8] = (g[8] + p[8])return gdef standard_nms(S, thres):order = np.argsort(S[:, 8])[::-1]keep = []while order.size > 0:i = order[0]keep.append(i)ovr = np.array([intersection(S[i], S[t]) for t in order[1:]])inds = np.where(ovr <= thres)[0]order = order[inds+1]return S[keep]def nms_locality(polys, thres=0.3):''':param polys: N*9 numpy数组。首先是8个坐标,然后是概率:return: NMS后的框'''S = []p = Nonefor g in polys:if p is not None and intersection(g, p) > thres:p = weighted_merge(g, p)else:if p is not None:S.append(p)p = gif p is not None:S.append(p)if len(S) == 0:return np.array([])return standard_nms(np.array(S), thres)

检测模型的干扰管道

# 用于在图像上生成用于文本检测的预测边界框的推理管道函数
def inferencePipeline(img,model):start_time=time.time()#1.文本检测img=cv2.resize(img,(512,512))ii=model.predict(np.expand_dims(img,axis=0))score_map=ii[0][:,:,0]geo_map=ii[0][:,:,1:]print("Shape of Score Map",score_map.shape)print("Shape of Geo Map",geo_map.shape)for ind in [0,1,2,3,4]:geo_map[:,:,ind]*=score_map#2.ROI旋转score_map_thresh=0.5box_thresh=0.1 nms_thres=0.2if len(score_map.shape) == 4:score_map = score_map[0, :, :, 0]geo_map = geo_map[0, :, :, :]# 过滤score mapxy_text = np.argwhere(score_map > score_map_thresh)# 通过y轴排序文本框xy_text = xy_text[np.argsort(xy_text[:, 0])]# 恢复text_box_restored = restore_rectangle(xy_text[:, ::-1], geo_map[xy_text[:, 0], xy_text[:, 1], :]) # N*4*2boxes = np.zeros((text_box_restored.shape[0], 9), dtype=np.float32)boxes[:, :8] = text_box_restored.reshape((-1, 8))boxes[:, 8] = score_map[xy_text[:, 0], xy_text[:, 1]]print("Boxes Shape",boxes.shape)boxes = nms_locality(boxes.astype(np.float64), nms_thres)# 这里我们通过平均score map过滤了一些低分数框,这与原始论文不同for i, box in enumerate(boxes):mask = np.zeros_like(score_map, dtype=np.uint8)cv2.fillPoly(mask, box[:8].reshape((-1, 4, 2)).astype(np.int32), 1)boxes[i, 8] = cv2.mean(score_map, mask)[0]if i==4:breakif len(boxes)>0:boxes = boxes[boxes[:, 8] > box_thresh]boxes[:,:8:2] = np.clip(boxes[:,:8:2], 0, 512 - 1)boxes[:,1:8:2] = np.clip(boxes[:,1:8:2], 0, 512 - 1)  res = []result = []if len(boxes)>0:for box in boxes:box_ =  box[:8].reshape((4, 2))if np.linalg.norm(box_[0] - box_[1]) < 8 or np.linalg.norm(box_[3]-box_[0]) < 8:continueresult.append(box_)res.append(np.array(result, np.float32))   box_index = []brotateParas = []filter_bsharedFeatures = []for i in range(len(res)):rotateParas = []rboxes=res[i]txt=[]for j, rbox in enumerate(rboxes):para = restore_roiRotatePara(rbox)if para and min(para[1][2:]) > 8:rotateParas.append(para)box_index.append((i, j))pts=[]   #3. 文本识别(文本检测+ROI旋转给出的框)if len(rotateParas) > 0:for num in range(len(rotateParas)):text=""out=rotateParas[num][0]crop=rotateParas[num][1]points=np.array([[out[0],out[1]],[out[0]+out[2],out[1]],[out[0]+out[2],out[1]+out[3]],[out[0],out[1]+out[3]]])pts.append(points)# 4. 在图像中检测标记和识别的文本for i in range(len(pts)):cv2.polylines(img,[pts[i]],isClosed=True,color=(0,255,0),thickness=2)#cv2.putText(img,txt[i],(pts[i][0][0],pts[i][0][1]),cv2.FONT_HERSHEY_SIMPLEX,0.8, (0, 255, 0), 3)end_time=time.time()print("Time Taken By Pipeline="+str(end_time-start_time)+" seconds") return img

每个模型的输出:

模型-1

模型2

如果我们比较两个模型的损失,那么我们可以得出结论,模型2(resnet_east)表现良好。让我们对模型2的性能进行分析。

8.模型分析与模型量化

正如我们所看到的,模型2的性能优于模型1,这里我们将对模型2的输出进行一些分析。

首先,计算训练和测试目录中每个图像的损失,然后基于分布,通过查看每个训练和测试图像的损失,我们将选择两个阈值损失,最后,我们将数据分为三类,即最佳、平均和最差。

训练和测试的密度图-

将图像数据分为3类(最佳、平均和最差)

  • 将数据分为3类

Best = if loss < np.percentile(final_train.loss, 33)----->Threshold-1
Average = if np.percentile(final_train.loss, 33)< loss < np.percentile(final_train.loss, 75)
Worst = if loss > np.percentile(final_train.loss, 75)--Threshold-2

每个类别的图像数量如下所示:

训练图像:

对于测试图像:

  • 从第一个图中,对于训练数据,我们可以观察到33%的数据属于最佳类别,42%的数据属于平均类别,只有25%的数据属于最差类别。

  • 从第二个图中,对于测试数据,我们可以观察到2.6%的数据属于最佳类别,13.2%的数据属于平均类别,84.2%的数据属于最差类别。

  • 从这些观察结果中,我们还可以得出结论,我们的模型可能对测试图像(即新图像)表现不佳。

模型量化

深度学习的量化是通过低位宽数的神经网络近似使用浮点数的神经网络的过程。这大大降低了使用神经网络的内存需求和计算成本。量化后,原始模型和量化模型的大小如下所示-

print("Orignal model size=",(os.path.getsize('east_resnet.h5')/1e+6),"MB")
print("Dynamic Post Training Quantization model size=",os.path.getsize('dynamic_east.tflite')/1e+6,"MB")
print("Float16 Post Training Quantization model size=",os.path.getsize('east_float16.tflite')/1e+6,"MB")
Orignal model size= 105.43348 MB
Dynamic Post Training Quantization model size= 24.810672 MB
Float16 Post Training Quantization model size= 48.341456 MB

9.部署

模型量化后,使用streamlit和Github选择并部署了float16量化模型。

使用Streamlight uploader函数,创建了一个.jpg文件输入部分,你可以在其中提供原始图像数据,模型将提供图像上存在检测到的文本。

网页链接-https://share.streamlit.io/paritoshmahto07/scene-text-detection-and-recognition-/main/app_2.py

部署视频-https://youtu.be/Pycj2fszhTk

10.今后的工作

在这项任务中,我们的主要目标是了解检测模型的工作原理,并从头开始实现它。

为了提高模型的性能,我们可以使用大数据集来训练我们的模型。我们还可以使用另一种识别模型来更好地识别文本。

Github:https://github.com/paritoshMahto07

☆ END ☆

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。

扫描二维码添加小编↓

基于EAST和Tesseract的文本检测与识别相关推荐

  1. 机器视觉 OpenCV—python 基于LSTM网络的OCR文本检测与识别

    文章目录 一.背景与环境搭建 二.文本检测与识别 一.背景与环境搭建 OpenCV的文本识别流程: OpenCV EAST 文本检测器执行文本检测, 我们提取出每个文本 ROI 并将其输入 Tesse ...

  2. 基于深度学习的场景文本检测和识别(Scene Text Detection and Recognition)综述

    1. 引言 文字是人类最重要的创作之一,它使人们在时空上可以有效地.可靠的传播或获取信息. 场景中的文字的检测和识别对我们理解世界很有帮助,它应用在图像搜索.即时翻译.机器人导航.工业自动化等领域. ...

  3. opencv 文字识别_Python+opencv+EAST做自然场景文本检测!

    在本教程中,您将学习如何使用OpenCV通过EAST文本检测器检测自然场景图像中的文本. OpenCV的EAST文本检测器是一种深度学习模型,基于新颖的架构和训练模式.它的优势是 (1)可以以13 F ...

  4. 【技术白皮书】第一章:基于深度学习的文本检测与识别的技术背景

    1.技术背景 1.1技术背景--什么是文本检测与识别 OCR全称Optical Character Recognition,即光学字符识别,最早在1929年被德国科学家Tausheck提出,定义为将印 ...

  5. Python基于CRNN&CTPN的文本检测系统(源码&教程)

    1.背景 文本是人类最伟大和最具影响力的发明之一,是人类智慧的结晶,是人类文化.思想传承的一种基本的表达方式和不可或缺的载体.在21世纪,文本与日常生活密切相关.描述.理解万事万物,表达情感,与他人交 ...

  6. 【文本检测与识别白皮书-3.2】第一节:基于分割的场景文本识别方法

    3.2技术背景--文本识别方法 3.2.1 基于分割的场景文本识别方法 基于分割的识别算法是自然场景文本识别算法的一个重要分支(Wang 等,2012;Bissacco 等,2013;Jaderber ...

  7. 【每周CV论文】深度学习文本检测与识别入门必读文章

    欢迎来到<每周CV论文推荐>.在这个专栏里,还是本着有三AI一贯的原则,专注于让大家能够系统性完成学习,所以我们推荐的文章也必定是同一主题的. 文本检测和识别是计算机视觉的一个非常重要的应 ...

  8. 自然场景的文本检测与识别发展综述

    摘要 本文介绍图像文本识别(OCR)领域的最新技术进展.首先介绍应用背景,包括面临的技术挑战.典型应用场景.系统实施框架等.接着介绍搭建图文识别模型过程中经常被引用到的多种特征提取基础网络.物体检测网 ...

  9. 【论文翻译】Scene Text Detection and Recognition: The Deep Learning Era 场景文本检测和识别:深度学习时代

    原文地址:Scene Text Detection and Recognition: The Deep Learning Era 文章目录 摘要 1.引言 2.深度学习时代之前的方法 2.1概括 3. ...

最新文章

  1. 什么情况下可以不写PHP的结束标签“?”
  2. cpp cu入门教程
  3. node 多进程 vs java_node多进程服务器
  4. python代码计算字数_Python计算一个文件里字数的方法
  5. Atomic原子类和Unsafe魔法类 详解
  6. 解决Windows和Ubuntu时间不一致的问题
  7. oozie中时间EL表达式
  8. Android ButterKnife示例
  9. JUC编程入门(高并发)
  10. wince植入胎压监测_wince设备通过USB连接线上网指南(原创)(测试成功)
  11. WinForm公共控件
  12. 基于SSH的新闻发布系统
  13. 解决KEIL MDK编译生成Bin文件时,却生成了*bin文件夹
  14. Java如何得到时间格式dd-MMM-yy???
  15. 人在当时处境中,像旋涡中的一片落叶,身不由己
  16. 员工出错处罚通知_员工处罚通知书范文.doc
  17. 备份和恢复 ESXi 主机配置
  18. 小旋风蜘蛛池采集工具教程分享
  19. 组合框里添加复选框的方法
  20. Java项目:SSM网上外卖订餐管理系统

热门文章

  1. shell脚本检查域名证书是否过期
  2. Java主要应用于哪些方面 Java就业方向有哪些
  3. 良基、归纳法、动态规划
  4. ffmpeg webm 提取_使用ffmpeg将webm转换为mp4
  5. C语言编程>第一周 ⑧ 输入两个正整数m和n,求其最大公约数和最小公倍数。
  6. kalilinux生成安卓木马(仅供学习使用)
  7. OpenStack 虚拟机冷/热迁移的实现原理与代码分析
  8. 线性回归统计指标 SSE、MSE、RMSE、MAE、R-square
  9. NVP6124I北京冠宇铭通 芯片
  10. 使用GerberTools的Gerber Panelizer工具进行gerber文件拼板的方法