Paper-TotalText

论文阅读。

资源

包含两个任务:

Total-Text: A Comprehensive Dataset for Scene Text Detection and Recognition

Abstract

弯曲文本在常见的文本数据集中(ICDAR'13MSRA-TD500)几乎不存在,TotalText 数据集则有。最近,一种将文本检测作为分割问题的新解决方案已经证明了它们对多方向文本的有效性。

1 INTRODUCTION

​ 市面上几乎没有弯曲文本的数据集,CUTE80 是唯一可用的,然而它只有 80 张,不好使。

​ 如果没有合适的数据集,解决弯曲文本检测问题的努力就很少出现。

Total Text

  • 一个考虑到弯曲文本的场景文本数据集,填补了场景文本数据集中文本方向的空白
  • 1555 幅场景图像,4265 个文本实例,9330 个注释单词
  • 三种不同的文本方向,包括水平 horizontal多向 multi-oriented弯曲 curved
png
第一行是分别是 ICDAR 2013,ICDAR 2015 和 MSRA-TD500(几乎没有弯曲文本)
第二行是 Total-Text,具有弯曲文本
png
现有的 Text Detection 对弯曲文本都不好使

A. Scene Text Datasets

  • ICDARs 系列,几百到几千张图像数量不等,文本质量模糊。

  • MSRA-TD500 于 2012 年推出,旨在解决场景文本数据集中缺乏任意定向文本的问题。它有 300 个训练图像和 200 个测试图像;用最小面积矩形标注。

  • COCO-Text 于 2016 年推出,数据量大(63686 张图像和 173589 个文本标记区域),也包含水平 horizontal、多向 multi-oriented 和弯曲 curved,然而它的 Ground Truth 只有 bbox,这只对水平和垂直文本有效。

  • CUTE80 只有 80 张,数据量太小了。

B. Scene Text Detection

​ 介绍各种 Scene Text Detection 的模型,略。

3 TOTAL-TEXT DATASET

A. Dataset Attributes

  • 弯曲文本是一个被忽视的问题 Curved text is an overlooked problem 在水平文本 ICDAR 中,性能几乎已达饱和(f-score 达到 0.9)

  • 弯曲文本观察 Curved text observation 从几何角度讲,直线沿着直线没有角度变化,因此可以描述为线性函数,y=mx+c。而曲线不是直线。它在整个线路上不受角度变化的限制。

  • **方向假设 Orientation assumption ** 目前很多 Text Detection 模型都具有方向假设,这种方式估计放到弯曲文本就寄了。

  • 以聚焦的场景文本为起点 Focused scene text as a start ICDAR 系列的文本显示质量差,作者认为文本质量稍微好一点更适合启动相关研究工作。

  • GT 越完备越好 Tighter groundtruth is better ICDAR 2015 使用四边形(四个点),COCO 使用 bbox(两个点)。在 Total Text 中,我们用紧密贴合的多边形对文本区域进行了注释。

  • 评估协议 Evaluation Protocol 与 ICDAR 数据集一样,TotalText 使用 DetEval。

  • 注释详细信息 Annotation Details 仍然保留了 bbox 的注释。Total Text 只考虑自然图像中的英文字符;其他语言、数字水印和无法阅读的文本被贴上了 “do not care” 的标签。

B. Dataset Statstics

  • 数量优势 Strength in numbers 它总共有 9330 个注释文本,平均每个图像有 6 个实例。Total Text 中超过一半的图像具有 2 个不同的方向及以上,平均每个图像产生 1.8 个方向。数据集的收集也考虑了
png
  • 方向多样性 Orientation diversity 大约一半的是弯曲的:

    • Horizontal Curve 水平弯曲 57.1%
    • Vertical Curve 垂直弯曲 23.5%
    • Circular 圆形 17.3%
    • Wavy 波浪形 2%

​ 弯曲文本通常与水平文本或多方向文本一起出现。图像中方向的混合对文本检测算法在文本方向方面实现鲁棒性和泛化提出了挑战。

  • 场景多样性 Scene diversity 弯曲文本出现的场景多样性好。

4 SEMANTIC SEGMENTATION FOR TEXT DETECTION 用于文本检测的语义分割

A. DeconvNet

​ 介绍他们提出的 DecovNet。使用最大的场景文本数据集 COCO-text 对其进行了预训练。COCO-text 中的图像被分为可阅读和难以阅读的文本,我们只在可阅读的文本上训练我们的网络,因为它与我们的数据集非常相似。

B. Experiments

DatasetRecallPrecisionF-score
Total-Text0.330.400.36

Total-Text: toward orientation robustness in scene text detection

Abstract

​ 目前场景文本数据集中的文本方向还不够多样化。

​ 提出的 Total-Text 具有三种不同的文本方向:

  • 水平 horizontal
  • 多方向 multi-oriented
  • 曲线方向 curve-oriented

​ 研究了其他几个重要因素:

  • ground truth 的 practicality(实用性)和 quality(质量)
  • evaluation protocol 评估协议
  • annotation process 注释过程

​ 提出了一个新的场景文本检测模型作为 Total text 基线,称为 Polygon-Faster-RCNN

1 Introduction

png
Total Text 的注释详细信息。a 总文本的图像,b 文本区域二进制掩码,c 字符级二进制掩码,d 固定长度多边形顶点,转录(区分大小写)和方向注释('c':弯曲,'m':多方向)

​ 提出的 Total-Text 包含:

  • 1555 幅场景图像,11459 个注释单词
  • GT 包括:
    • spatial location 空间位置
    • transcription 标注
    • pixel level for text detection, recognition and segmentation task 像素级的用于文本检测、识别和分割任务

​ 提出了新的场景文本模型:Polygon Faster RCNN(Poly FRCNN)。用于回归多边形而不是长方体参数。它能够检测所有方向的文本,并以精确的方式将其绑定。我们提出的模型在 ICDAR2013、ICDAR2015 和 Total Text 上的 F 测度分别达到 0.85、0.72 和 0.7;证明了其在具有不同属性的数据集上的有效性。

1.1 Improved ground truth

​ 多边形顶点的数量因文本实例的不同而不同。这给 Faster RCNN、SSD 和 YOLO 等检测框架带来了一个实际问题(所有这些都激发了许多场景文本检测工作),它们需要在回归目标中有固定数量的顶点。

1.2 Optimized evaluation protocol for Total-Text

​ DetEval 中当前推荐的阈值没有通过包含弯曲文本进行优化。进行了一系列实验,以确定一组新的更公平评估阈值。

1.3 Scene text detection annotation tool

​ 在扩展数据集时,地面实况注释是最大的瓶颈。Karatzas 等人介绍了一个注重质量控制和数据库管理的在线注释平台。然而,仅仅是平凡而艰苦的注释任务就有很大的改进空间。因此,我们在第节中介绍了全文工具(T3)。5、一种辅助注释框架,该辅助注释框架能够减少注释时间,同时获得高质量的基本事实。

1.4 Cross dataset experiment

​ 在 Total Text 上训练的模型在其他场景文本数据集上表现出良好的泛化能力

1.5 State-of-the-art analysis

​ 自 TotalText 出现以来,许多工作开始解决弯曲文本检测问题。

2.1 Scene text datasets

2.1.1 ICDAR2003-ICDAR2015

系列数量注释特性
2003509bbox
2011484bbox
2013462bbox
20151670四边形包含任意方向,失焦

2.1.2 MSRA-TD500

​ 2012,包含任意定向文本,300 训练图像,200 测试图像,使用旋转的边界框进行注释。

2.1.3 USTB-SV1K

​ 从美国六个城市的街道上收集了 1000 张图像,以多方向文本为特色,用旋转的边界框进行注释。

2.1.4 COCO-text

​ 2016,迄今为止最大的场景文本数据集,拥有 63686 幅图像和 173589 个标记文本区域。它主要由横向和多向文本以及少量弯曲文本组成,bbox。

2.1.5 MLT

​ MLT 数据集,这是为场景文本检测、识别和脚本识别任务收集的最新多脚本数据集之一。它由 18000 个图像组成,用于训练和验证,以 9 种语言和 6 种不同的脚本为特色。

2.1.6 CTW-12k

​ 2017,12000 多张,中英文。

2.1.7 MTWI

​ 迄今为止最大的多语言数据集之一,拥有 20000 张图像。

2.1.8 SynthText

​ 包含 80 万个场景文本图像。它的注释由单词级和字符级轴对齐的边界框及其转录组成。它的缺点是,它使用了与 COCO 文本类似的轴对齐的边界框,这不适合多方向的文本。

2.1.9 CUTE80

​ 第一个突出弯曲文本的场景文本数据集,可惜只有 80 张。

2.1.10 CTW1500

​ 原则上是最接近 Total Text 的数据集。Total Text 的文本实例在单词级别进行注释,而 CTW1500 的文本实例则在行级别进行注释。

2.2 Scene text detection

2.2.1 Scene text inspired handcrafted feature era

​ 场景文本的手工特征时代。

2.2.2 The emergence of CNN

​ CNN 的出现。

2.2.3 Segmentation-based scene text detection

​ 基于分割的场景文本检测。

2.2.4 Proposal-based scene text detection

​ 基于提示的场景文本检测。

2.2.5 Single network scene text detection

​ 单网络场景文本检测。

2.2.6 Curved text detection

​ 曲线文本检测。

3 The Total-Text dataset

3.1 Dataset attributes

3.1.1 Curved text is an overlooked problem

​ 水平文本的检测性能几乎达到饱和(F-score 为 0.9),但是缺乏弯曲文本。

3.1.2 Curved text observation

​ 曲线不是直线。它在整个线路上不受角度变化的限制。

3.1.3 Detection ground truth annotation

​ Total Text 中的文本实例是以单词级别的粒度进行注释的。

3.1.4 Recognition ground truth annotation

​ 提供了单词 recognition 挑战的 GT。

3.1.5 Segmentation ground truth annotation

​ 像素级的 GT 标注是最耗时的过程,我们提供了多种预处理方案。

png
像素级注释过程。a 输入图像补丁。bc 调整颜色阈值,使文本与背景区域分离。d–f 删除“非文本”区域。g 最终结果

3.1.6 Orientation annotation

具体来说,注释是这样表示的:

  • 'h' 表示水平文本
  • 'm' 表示多向文本
  • 'c' 表示弯曲文本

3.1.7 Regulated polygon ground truth

​ 在本文中,我们使用以下方案对全文注释进行了改进。除了将多边形顶点的数量设置为 10(根据经验,10 个顶点足以紧密覆盖我们数据集中的所有单词级文本实例)

​ 新的多边形地面实况注释步骤如图 7 所示。首先,需要人工注释器手动选择四个不同的顶点,作为单词实例的开始和结束顶点。单词“MARLEY”(红点和绿点)上角的两个顶点将用于生成三条等距的黄色引导线。生成引导线的算法在算法 1 中进行了说明。然后,人类注释者将沿着每条黄色引导线选择一个截取点(表示为“*”),该点最能绑定单词的顶部边界。


算法 1 在调节多边形注释过程中生成引导线的算法,emmmm 就是给个上界和下界生成三等分的点,这样文字的四个角和三条三等分的线就可以形成 10 个顶点。

png

3.2 Dataset statistics

3.2.1 Strength in numbers

​ Total Text 分为两组:训练集和测试集,分别有 1255 张和 300 张图像。图 10 显示了 Total Text 的一系列统计信息。它总共有 11459 个带注释的文本实例,平均每个图像有7.37个实例。Total Text 中超过一半的图像具有两个不同的方向及以上,平均每个图像产生1.8个方向。

​ 数据集的收集也考虑到了质量,包括场景复杂性。例如类似文本和低对比度背景,不同的字体类型和大小。

3.2.2 Orientation diversity

​ 大约一半的文本实例是弯曲的,另一半在水平和多向之间几乎相等地划分。尽管所有图像都是在考虑弯曲文本的情况下收集的,但其他方向仍然占据了总实例的一半。仔细观察 Total Text 可以发现,弯曲文本通常与水平文本或多方向文本一起出现。图像中文本方向的混合对文本检测算法在文本方向方面实现鲁棒性和泛化提出了挑战。

3.2.3 Scenery diversity

​ Total Text 图像中的风景也很多样化。

4 Evaluation protocol

4.1 DetEval

​ Total Text 首次引入 DetEval 评估协议。然而,我们意识到,建议的 tp 和 tr 阈值,分别为 0.4 和 0.8,并没有通过在 Total text 中包含弯曲文本和多边形地面实况进行优化。

4.2 PASCAL VOC

​ 与 CTW1500 和 ICDAR2015 类似,PASCAL VOC 评估方法也适用于 Total Text。

4.3 Intersection area between polygons

​ 预测区域和地面实况区域之间的交集计算是 DetEval 和 Pascal VOC 评估协议的核心。

5 Scene text detection annotation tool

​ 提出了一个注释工具:Total Text tool(T3),能够将注释时间减少 25%,与人类注释者的一致率为 84%。

5.1 Total-Text-Tool

5.2 Experiment setup

5.3 Performance analysis

6 Polygon-faster-RCNN

6.1 Text line encoding method

​ Faster RCNN、SSD和YOLO中使用的传统回归目标 (xmymwh)(x_m,y_m,w,h) 只能用于轴对称矩形框,而不能用于多边形。

6.1.1 Variants of Poly-FRCNN

​ Poly-FRCNN-5 是 3 的放大版,它在回归头中又有 10 个参数

6.1.2 Encode

6.1.3 Decode

6.2 Anchor polygons parameterization

6.3 Implementation details

6.3.1 Feature extractor

​ 模型采用 Inception-Resnet-V2 作为特征提取器。

6.3.2 Anchor boxes

6.3.3 Loss function

6.3.4 Training

​ 所有的 models 都经过了相同的训练计划。

  • 它们首先使用 ImageNet pre-trained 的权重进行初始化。

  • 然后,训练计划从 SynthText 上的 100 K 次迭代开始,然后是来自 COCO Text 的真实世界数据上的另外 100 K 次重复。最后,我们使用目标训练集对它们进行了 fine tune,以进行另外 50 K 次迭代。

​ 该训练计划中最大数据集(SynthText)的初始学习率设置为0.003,然后自COCO Text训练开始以来降低了0.0003,并在其余训练中保持不变。

​ 来自 SynthText100K 图像被随机选择用于训练的第一阶段。

​ 然后,在第二训练阶段期间使用来自具有至少一个可阅读文本实例的 COCO-Text 的大约 13K 个训练图像。

​ 最后,来自总文本训练集的 1255 幅图像被用于 fine-tuning 阶段。

6.3.5 Testing

​ 这个过程尽可能简单。除了标准的非最大值抑制(NMS)外,没有使用后处理或多尺度。

6.4 Evaluation

6.4.1 Dataset

​ 我们评估了 Poly FRCNN 在 ICDAR2013、ICDAR2015 和 Total Text 上的性能。选择 ICDAR2013 和 ICDAR2015 分别演示了 PolyFRCNN 在水平文本和多方向文本上的性能。

6.4.2 Evaluation Protocol

​ 使用 DetEval 方案中的建议(即 tp=0.4tp=0.4tr=0.8tr=0.8)对 ICDAR2013 报告的性能进行评估,以便与现有技术的解决方案进行公平比较。对于 Total-Text ,我们使用了 tp=0.6tp=0.6tr=0.7tr=0.7,同时,对 ICDAR2015 和 Total Text 的结果使用了 Pascal VOC 评估方法的标准0.5IoU0.5\mathrm{IoU} 阈值。

6.4.3 Performance analysis

6.4.4 Box-FRCNN versus Poly-FRCNN-3

6.4.5 Poly-Baseline versus Poly-FRCNN-3

6.4.6 Poly-FRCNN-5

6.4.7 Inference time

6.4.8 Performance on other curved text datasets

​ 除了Total Text,我们还在其他曲线文本数据集 CUTE-80 和 CTW1500 上评估了我们提出的模型。Poly-FRCNN-3 在上述数据集上的 F-Score 分别达到 0.65 和 0.72(表5)。请注意,CUTE80 中的注释粒度不一致(即单词级别和行级别的混合),因此我们在评估之前将它们重新标记。

6.5 Cross datasets experiment

png

6.5.1 Pretraining on SynthText and COCO-Text only

​ 仅在 SynthText 和 COCO Text 上训练的模型(表 6 中的第一行)通常比在相应数据集上进行微调的其他模型表现更差。尽管该模型的性能在 ICDAR2013 和 ICDAR2015 上仍然具有竞争力,即使没有对其进行微调;它在 Total Text 上的性能最差,与性能最好的模型(第四排)相比,在 F-Score 方面有 0.24 的大差距。

6.5.2 Fine-tuning on ICDAR2013 and ICDAR2015

​ ICDAR2013 主要由水平文本组成,并使用轴对称框作为基本事实,因此,对其进行微调无助于提高其在 Total text 上的性能也就不足为奇了

6.5.3 Fine-tuning on Total-Text

​ 虽然该模型仅在 Total-Text 上进行了微调,但在其他两个数据集上取得了良好的结果,这表明 Total Text 的数据足够多样化,可以用于模型的泛化。

6.6 State-of-the-art analysis

​ 其他工作balabala……

7 Conclusion

仓库

​ 从 GitHub cs-chan/Total-Text-Dataset: Total Text Dataset. It consists of 1555 images with more than 3 different text orientations: Horizontal, Multi-Oriented, and Curved, one of a kind. (github.com) 里下载代码:

png

Annotation_tools

png

​ 他们提供的一个标注工具,看样子使用 Objective-C 写的。

​ Total-Text-Tool (T3) is a guided annotation framework that is designed to reduce annotation time. In our experiment, T3 reduces annotation time by 25%. For more details of T3 and all related experiments, please refer to our IJDAR journal publication.

​ We make all three variants of T3 available.

  1. T3_v1 - the baseline version, the suggestion mechanism is not incorporated. 不带提示,完全手工
  2. T3_v2 - the suggestion mechanism is incorporated, only suggest rectangle bounding box, polygon is not suggested. 只支持 bbox
  3. T3_v3 - suggests both rectangle and polygon bounding box. 最后可以生成 bbox 和 多边形

​ Kindly refer to 'T3_use_cases' for different use cases of T3_v3.

Baseline

​ 有两个模型:

  • Polygon-Faster-RCNN-3 (P3)
  • Polygon-Faster-RCNN-5 (P5)

​ 要预训练模型啊……好像搞不到

Dataset

​ The Total-Text dataset can be downloaded at this https URL (size = 441Mb).

Evaluation_Protocol

​ 这些代码是 Total Text 的官方评估协议实现。提供了两种方法:Deteval 和 Pascal VOC 协议。

Deteval

​ 我们建议 tr=0.7tr=0.7tp=0.6tp=0.6 阈值,以便使用多边形地面实况和检测格式进行更公平的评估。

Deteval.py 代码解析:

  • 定义
python
from os import listdir
from scipy import io
import numpy as np
# mask counting version
# from polygon_wrapper import iod
# from polygon_wrapper import area_of_intersection
# from polygon_wrapper import area
 
# polygon based version
from polygon_fast import iod
from polygon_fast import area_of_intersection
from polygon_fast import area
from tqdm import tqdm
 
try: # python2
    range = xrange
excerpt excerption:
    # python3
    range = range
 
"""
Input format: y0,x0, ..... yn,xn. Each detection is separated by the end of line token ('\n')'
"""
 
input_dir = '../Examples/Prediction/' #detection directory goes here
gt_dir = '../Examples/Groundtruth/' #gt directory goes here
fid_path = '../Examples/' #output text file directory goes here
 
allInputs = listdir(input_dir)
  • def input_reading_mod()
python
def input_reading_mod(input_dir, input):
    """This helper reads input from txt files"""
    with open('%s/%s' % (input_dir, input), 'r', encoding='latin-1') as input_fid:
        pred = input_fid.readlines()
    det = [x.strip('\n') for x in pred]
    return det

这是一个名为 input_reading_mod 的函数定义,它用于从文本文件中读取输入。

函数的输入参数包括 input_dir input,分别表示输入目录和要读取的文件名。

函数的功能是打开指定路径下的文本文件,并按照每行的格式读取文件内容。使用open函数打开文件时,通过 '%s/%s' % (input_dir, input) 的方式构建了文件的完整路径,并且指定了使用 'r' 模式以只读方式打开文件。encoding='latin-1' 参数指定了文件的编码格式为 Latin-1。

接着,使用 readlines 方法将文件内容按行读取,并将结果存储在 pred 列表中。然后,使用列表推导式 [x.strip('\n') for x in pred] 去掉每行末尾的换行符,得到处理后的数据列表 det

最后,函数返回 det 列表作为输出结果。

  • gt_reading_mod()
python
def gt_reading_mod(gt_dir, gt_id):
    """This helper reads groundtruths from mat files"""
    gt_id = gt_id.split('.')[0]
    gt = io.loadmat('%s/poly_gt_%s.mat' % (gt_dir, gt_id))
    gt = gt['polygt']
    return gt

这是一个名为 gt_reading_mod 的函数定义,它用于从 .mat 文件中读取标注数据。

函数的输入参数包括 gt_dirgt_id,分别表示标注目录和要读取的文件名。

函数的功能是首先对 gt_id 进行字符串处理,通过 .split('.')[0] 将文件名中的扩展名去除。

然后,使用 io.loadmat 函数读取指定路径下的 .mat 文件,该文件的完整路径由'%s/poly_gt_%s.mat' % (gt_dir, gt_id)构建而成。loadmat函数将.mat文件中的数据读入到一个字典类型的变量gt中。

接着,从字典中获取名为polygt的键值对应的数据,并将其存储在变量gt中。

最后,函数返回gt作为输出结果。

  • detection_filtering()
python
def detection_filtering(detections, groundtruths, threshold=0.5):
    for gt_id, gt in enumerate(groundtruths):
        if (gt[5] == '#') and (gt[1].shape[1] > 1):
            gt_x = list(map(int, np.squeeze(gt[1])))
            gt_y = list(map(int, np.squeeze(gt[3])))
            for det_id, detection in enumerate(detections):
                detection = detection.split(',')
                detection = list(map(int, detection))
                det_y = detection[0::2]
                det_x = detection[1::2]
                det_gt_iou = iod(det_x, det_y, gt_x, gt_y)
                if det_gt_iou > threshold:
                    detections[det_id] = []
 
            detections[:] = [item for item in detections if item != []]
    return detections

这是一个名为 detection_filtering 的函数定义,用于过滤掉与标注数据重叠度低的检测结果。

函数的输入参数包括 detectionsgroundtruths,分别表示检测结果和标注数据。另外还有一个可选参数 threshold,表示重叠度的阈值,默认为 0.5。

函数的功能是遍历所有的标注数据,对于每个标注数据,判断其是否为文字区域(gt[5] == '#')并且有多个顶点(gt[1].shape[1] > 1)。如果满足这两个条件,则将标注数据的顶点信息提取出来,分别存储在 gt_xgt_y 列表中。

然后,遍历所有的检测结果,对于每个检测结果,先将其按逗号分隔,并将得到的字符串列表转换为整数列表。再根据坐标的奇偶性,将检测结果的 x 坐标和 y 坐标分别存储在 det_xdet_y 列表中。

接下来,使用 iod 函数计算检测结果与标注数据之间的重叠度(Intersection over Detection)。如果重叠度大于设定的阈值,则将该检测结果从 detections 列表中移除,即将其置为空列表 []

最后,使用列表推导式 [item for item in detections if item != []] 将不为空的检测结果重新存储到 detections 列表中。

函数返回经过过滤后的 detections 列表作为输出结果。

  • sigma_calculation()
python
def sigma_calculation(det_x, det_y, gt_x, gt_y):
    """
    sigma = inter_area / gt_area
    """
    # print(area_of_intersection(det_x, det_y, gt_x, gt_y))
    return np.round((area_of_intersection(det_x, det_y, gt_x, gt_y) / area(gt_x, gt_y)), 2)

这是一个名为 sigma_calculation 的函数定义,用于计算检测结果与标注数据之间的重叠度。

函数的输入参数包括 det_xdet_ygt_xgt_y,分别表示检测结果和标注数据的顶点坐标。

函数的功能是根据以下公式计算重叠度(sigma):

sigma = inter_area / gt_area

其中,inter_area 表示检测结果与标注数据之间的交集区域面积,gt_area 表示标注数据的区域面积。

函数内部通过调用两个辅助函数 area_of_intersectionarea 来计算交集区域面积和标注数据区域面积。然后将交集区域面积除以标注数据区域面积,并使用 np.round 函数将结果四舍五入到小数点后两位。

最后,函数返回计算得到的重叠度作为输出结果。

  • tau_calculation()
python
def tau_calculation(det_x, det_y, gt_x, gt_y):
    """
    tau = inter_area / det_area
    """
    return np.round((area_of_intersection(det_x, det_y, gt_x, gt_y) / area(det_x, det_y)), 2)

这是一个名为 tau_calculation 的函数定义,用于计算检测结果与标注数据之间的重叠度。

函数的输入参数包括 det_xdet_ygt_xgt_y,分别表示检测结果和标注数据的顶点坐标。

函数的功能是根据以下公式计算重叠度(tau):

tau = inter_area / det_area

其中,inter_area 表示检测结果与标注数据之间的交集区域面积,det_area 表示检测结果的区域面积。

函数内部通过调用两个辅助函数 area_of_intersectionarea 来计算交集区域面积和检测结果区域面积。然后将交集区域面积除以检测结果区域面积,并使用 np.round 函数将结果四舍五入到小数点后两位。

最后,函数返回计算得到的重叠度作为输出结果。

  • 变量定义
python
global_tp = 0
global_fp = 0
global_fn = 0
global_sigma = []
global_tau = []
tr = 0.7
tp = 0.6
fsc_k = 0.8
k = 2

这段代码定义了一些全局变量 global_tpglobal_fpglobal_fnglobal_sigmaglobal_tau,并给它们分别赋初值0和空列表。

其中,global_tp 表示全局的真正例数量(True Positive),global_fp 表示全局的假正例数量(False Positive),global_fn 表示全局的假负例数量(False Negative),global_sigma 表示全局的重叠度(sigma)列表,global_tau 表示全局的重叠度(tau)列表。

接下来,代码定义了一些参数:tr 表示阈值,tp 表示真正例比例阈值,fsc_k 表示 F1 分数的参数 k,k 表示一个系数。

这段代码仅仅给这些变量和参数赋了初值,并没有其他具体的逻辑操作。

  • 评估
python
for input_id in tqdm(allInputs):
    if (input_id != '.DS_Store') and (input_id != 'Pascal_result.txt') and (
            input_id != 'Pascal_result_curved.txt') and (input_id != 'Pascal_result_non_curved.txt') and (input_id != 'Deteval_result.txt') and (input_id != 'Deteval_result_curved.txt') \
            and (input_id != 'Deteval_result_non_curved.txt'):
        # print(input_id)
        detections = input_reading_mod(input_dir, input_id)
        groundtruths = gt_reading_mod(gt_dir, input_id)
        detections = detection_filtering(detections, groundtruths)  # filters detections overlapping with DC area
        dc_id = np.where(groundtruths[:, 5] == '#')
        groundtruths = np.delete(groundtruths, (dc_id), (0))
 
        local_sigma_table = np.zeros((groundtruths.shape[0], len(detections)))
        local_tau_table = np.zeros((groundtruths.shape[0], len(detections)))
 
        for gt_id, gt in enumerate(groundtruths):
            if len(detections) > 0:
                for det_id, detection in enumerate(detections):
                    detection = detection.split(',')
                    detection = list(map(int, detection))
                    det_y = detection[0::2]
                    det_x = detection[1::2]
                    gt_x = list(map(int, np.squeeze(gt[1])))
                    gt_y = list(map(int, np.squeeze(gt[3])))
 
                    local_sigma_table[gt_id, det_id] = sigma_calculation(det_x, det_y, gt_x, gt_y)
                    local_tau_table[gt_id, det_id] = tau_calculation(det_x, det_y, gt_x, gt_y)
 
        global_sigma.append(local_sigma_table)
        global_tau.append(local_tau_table)
 
global_accumulative_recall = 0
global_accumulative_precision = 0
total_num_gt = 0
total_num_det = 0

这段代码通过一个 for 循环遍历名为 allInputs 的列表中的每个元素 input_id

在循环内部,首先使用一系列条件语句来过滤掉一些特定的 input_id,包括 .DS_StorePascal_result.txt 等等。然后调用 input_reading_mod 函数从 input_dir 目录中读取输入数据,调用 gt_reading_mod 函数从 gt_dir 目录中读取标注数据。

接下来,通过调用 detection_filtering 函数对检测结果进行过滤,去掉与 DC 区域重叠的检测结果。然后找到标注数据中 DC 区域对应的索引,并通过 np.delete 函数将其从标注数据中删除。

接着,创建了两个零矩阵 local_sigma_tablelocal_tau_table,形状分别为 (groundtruths.shape[0], len(detections)),用来存储每个标注和检测结果之间的重叠度信息。

接下来的两个嵌套的 for 循环用于计算每个标注和检测结果之间的重叠度。首先解析检测结果和标注数据的坐标信息,并调用 sigma_calculationtau_calculation 函数来计算重叠度。将计算得到的重叠度存储在 local_sigma_tablelocal_tau_table 中。

最后,将 local_sigma_tablelocal_tau_table 分别添加到 global_sigmaglobal_tau 列表中,用于保存所有标注和检测结果之间的重叠度信息。

代码的最后几行定义了一些变量,包括 global_accumulative_recallglobal_accumulative_precisiontotal_num_gttotal_num_det,并给它们赋初值 0。这些变量可能在后续代码中使用。

  • one_to_one()
python
def one_to_one(local_sigma_table, local_tau_table, local_accumulative_recall,
               local_accumulative_precision, global_accumulative_recall, global_accumulative_precision,
               gt_flag, det_flag):
    for gt_id in range(num_gt):
        gt_matching_qualified_sigma_candidates = np.where(local_sigma_table[gt_id, :] > tr)
        gt_matching_num_qualified_sigma_candidates = gt_matching_qualified_sigma_candidates[0].shape[0]
        gt_matching_qualified_tau_candidates = np.where(local_tau_table[gt_id, :] > tp)
        gt_matching_num_qualified_tau_candidates = gt_matching_qualified_tau_candidates[0].shape[0]
 
        det_matching_qualified_sigma_candidates = np.where(local_sigma_table[:, gt_matching_qualified_sigma_candidates[0]] > tr)
        det_matching_num_qualified_sigma_candidates = det_matching_qualified_sigma_candidates[0].shape[0]
        det_matching_qualified_tau_candidates = np.where(local_tau_table[:, gt_matching_qualified_tau_candidates[0]] > tp)
        det_matching_num_qualified_tau_candidates = det_matching_qualified_tau_candidates[0].shape[0]
 
 
        if (gt_matching_num_qualified_sigma_candidates == 1) and (gt_matching_num_qualified_tau_candidates == 1) and \
                (det_matching_num_qualified_sigma_candidates == 1) and (det_matching_num_qualified_tau_candidates == 1):
            global_accumulative_recall = global_accumulative_recall + 1.0
            global_accumulative_precision = global_accumulative_precision + 1.0
            local_accumulative_recall = local_accumulative_recall + 1.0
            local_accumulative_precision = local_accumulative_precision + 1.0
 
            gt_flag[0, gt_id] = 1
            matched_det_id = np.where(local_sigma_table[gt_id, :] > tr)
            det_flag[0, matched_det_id] = 1
    return local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, gt_flag, det_flag

这段代码定义了一个名为 one_to_one 的函数,它接受多个参数,包括 local_sigma_tablelocal_tau_tablelocal_accumulative_recalllocal_accumulative_precisionglobal_accumulative_recallglobal_accumulative_precisiongt_flagdet_flag

函数中的 for 循环遍历从 0 到 num_gt 的每个 gt_id,其中 num_gt 是标注数据的数量。

在循环内部,首先使用 np.where 函数找到满足条件 local_sigma_table[gt_id, :] > tr 的索引,然后计算满足条件的索引数量,并分别保存在 gt_matching_num_qualified_sigma_candidatesgt_matching_qualified_tau_candidates 变量中。

接着,再次使用 np.where 函数找到与上述条件对应的检测结果的索引,并计算满足条件的索引数量,分别保存在 det_matching_num_qualified_sigma_candidatesdet_matching_num_qualified_tau_candidates 变量中。

之后,通过一系列的条件判断,检查是否满足"一对一"匹配的条件,即只有一个满足重叠度阈值要求的标注和一个满足重叠度阈值要求的检测结果。如果满足这些条件,则更新相关计数变量,并将相应的标志位设置为1。

最后,函数返回更新后的 local_accumulative_recalllocal_accumulative_precisionglobal_accumulative_recallglobal_accumulative_precisiongt_flagdet_flag

  • one_to_many()
python
def one_to_many(local_sigma_table, local_tau_table, local_accumulative_recall,
               local_accumulative_precision, global_accumulative_recall, global_accumulative_precision,
               gt_flag, det_flag):
    for gt_id in range(num_gt):
        #skip the following if the groundtruth was matched
        if gt_flag[0, gt_id] > 0:
            continue
 
        non_zero_in_sigma = np.where(local_sigma_table[gt_id, :] > 0)
        num_non_zero_in_sigma = non_zero_in_sigma[0].shape[0]
 
        if num_non_zero_in_sigma >= k:
            ####search for all detections that overlaps with this groundtruth
            qualified_tau_candidates = np.where((local_tau_table[gt_id, :] >= tp) & (det_flag[0, :] == 0))
            num_qualified_tau_candidates = qualified_tau_candidates[0].shape[0]
 
            if num_qualified_tau_candidates == 1:
                if ((local_tau_table[gt_id, qualified_tau_candidates] >= tp) and (local_sigma_table[gt_id, qualified_tau_candidates] >= tr)):
                    #became an one-to-one case
                    global_accumulative_recall = global_accumulative_recall + 1.0
                    global_accumulative_precision = global_accumulative_precision + 1.0
                    local_accumulative_recall = local_accumulative_recall + 1.0
                    local_accumulative_precision = local_accumulative_precision + 1.0
 
                    gt_flag[0, gt_id] = 1
                    det_flag[0, qualified_tau_candidates] = 1
            elif (np.sum(local_sigma_table[gt_id, qualified_tau_candidates]) >= tr):
                gt_flag[0, gt_id] = 1
                det_flag[0, qualified_tau_candidates] = 1
 
                global_accumulative_recall = global_accumulative_recall + fsc_k
                global_accumulative_precision = global_accumulative_precision + num_qualified_tau_candidates * fsc_k
 
                local_accumulative_recall = local_accumulative_recall + fsc_k
                local_accumulative_precision = local_accumulative_precision + num_qualified_tau_candidates * fsc_k
 
    return local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, gt_flag, det_flag

这段代码定义了一个名为 one_to_many 的函数,它与之前的 one_to_one 函数类似,接受相同的参数。

函数中的 for 循环遍历从 0 到 num_gt 的每个 gt_id,其中 num_gt 是标注数据的数量。

在循环内部,首先检查是否已经匹配过该标注数据,如果 gt_flag[0, gt_id] 大于 0,则表示已进行匹配,直接跳过后续操作。

接下来,使用 np.where 函数找到满足条件 local_sigma_table[gt_id, :] > 0 的索引,并计算满足条件的索引数量,保存在 num_non_zero_in_sigma 变量中。

然后,如果满足条件 num_non_zero_in_sigma >= k,进入下一步操作。

在下一步操作中,通过np.where函数找到满足条件 (local_tau_table[gt_id, :] >= tp) & (det_flag[0, :] == 0) 的索引,并计算满足条件的索引数量,保存在 num_qualified_tau_candidates 变量中。

接着,判断 num_qualified_tau_candidates 的值,如果为 1,则进一步判断该标注和对应的检测结果是否满足"一对一"匹配的条件,即重叠度要求同时满足。如果满足条件,则更新相关计数变量,并将相应的标志位设置为1。

如果 num_qualified_tau_candidates 的值大于 1,并且满足条件 np.sum(local_sigma_table[gt_id, qualified_tau_candidates]) >= tr,则将相关计数变量更新,并将标志位设置为 1。

最后,函数返回更新后的 local_accumulative_recalllocal_accumulative_precisionglobal_accumulative_recallglobal_accumulative_precisiongt_flagdet_flag

  • many_to_one()
python
def many_to_one(local_sigma_table, local_tau_table, local_accumulative_recall,
               local_accumulative_precision, global_accumulative_recall, global_accumulative_precision,
               gt_flag, det_flag):
    for det_id in range(num_det):
        # skip the following if the detection was matched
        if det_flag[0, det_id] > 0:
            continue
 
        non_zero_in_tau = np.where(local_tau_table[:, det_id] > 0)
        num_non_zero_in_tau = non_zero_in_tau[0].shape[0]
 
        if num_non_zero_in_tau >= k:
            ####search for all detections that overlaps with this groundtruth
            qualified_sigma_candidates = np.where((local_sigma_table[:, det_id] >= tp) & (gt_flag[0, :] == 0))
            num_qualified_sigma_candidates = qualified_sigma_candidates[0].shape[0]
 
            if num_qualified_sigma_candidates == 1:
                if ((local_tau_table[qualified_sigma_candidates, det_id] >= tp) and (local_sigma_table[qualified_sigma_candidates, det_id] >= tr)):
                    #became an one-to-one case
                    global_accumulative_recall = global_accumulative_recall + 1.0
                    global_accumulative_precision = global_accumulative_precision + 1.0
                    local_accumulative_recall = local_accumulative_recall + 1.0
                    local_accumulative_precision = local_accumulative_precision + 1.0
 
                    gt_flag[0, qualified_sigma_candidates] = 1
                    det_flag[0, det_id] = 1
            elif (np.sum(local_tau_table[qualified_sigma_candidates, det_id]) >= tp):
                det_flag[0, det_id] = 1
                gt_flag[0, qualified_sigma_candidates] = 1
 
                global_accumulative_recall = global_accumulative_recall + num_qualified_sigma_candidates * fsc_k
                global_accumulative_precision = global_accumulative_precision + fsc_k
 
                local_accumulative_recall = local_accumulative_recall + num_qualified_sigma_candidates * fsc_k
                local_accumulative_precision = local_accumulative_precision + fsc_k
    return local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, gt_flag, det_flag

这段代码定义了一个名为 many_to_one 的函数,与之前的 one_to_oneone_to_many 函数类似,接受相同的参数。

函数中的 for 循环遍历从 0 到 num_det 的每个 det_id,其中 num_det 是检测结果的数量。

在循环内部,首先检查是否已经匹配过该检测结果,如果 det_flag[0, det_id] 大于 0,则表示已进行匹配,直接跳过后续操作。

接下来,使用 np.where 函数找到满足条件 local_tau_table[:, det_id] > 0 的索引,并计算满足条件的索引数量,保存在 num_non_zero_in_tau 变量中。

然后,如果满足条件 num_non_zero_in_tau >= k,进入下一步操作。

在下一步操作中,通过 np.where 函数找到满足条件 (local_sigma_table[:, det_id] >= tp) & (gt_flag[0, :] == 0) 的索引,并计算满足条件的索引数量,保存在 num_qualified_sigma_candidates 变量中。

接着,判断 num_qualified_sigma_candidates 的值,如果为 1,则进一步判断该检测结果和对应的标注数据是否满足"一对一"匹配的条件,即重叠度和重合度要求同时满足。如果满足条件,则更新相关计数变量,并将相应的标志位设置为 1。

如果 num_qualified_sigma_candidates 的值大于 1,并且满足条件 np.sum(local_tau_table[qualified_sigma_candidates, det_id]) >= tp,则将相关计数变量更新,并将标志位设置为 1。

最后,函数返回更新后的 local_accumulative_recalllocal_accumulative_precisionglobal_accumulative_recallglobal_accumulative_precisiongt_flagdet_flag

  • 保存结果
python
for idx in range(len(global_sigma)):
    print(allInputs[idx])
    local_sigma_table = global_sigma[idx]
    local_tau_table = global_tau[idx]
 
    num_gt = local_sigma_table.shape[0]
    num_det = local_sigma_table.shape[1]
 
    total_num_gt = total_num_gt + num_gt
    total_num_det = total_num_det + num_det
 
    local_accumulative_recall = 0
    local_accumulative_precision = 0
    gt_flag = np.zeros((1, num_gt))
    det_flag = np.zeros((1, num_det))
 
    #######first check for one-to-one case##########
    local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, \
    gt_flag, det_flag = one_to_one(local_sigma_table, local_tau_table,
                                  local_accumulative_recall, local_accumulative_precision,
                                  global_accumulative_recall, global_accumulative_precision,
                                  gt_flag, det_flag)
 
    #######then check for one-to-many case##########
    local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, \
    gt_flag, det_flag = one_to_many(local_sigma_table, local_tau_table,
                                   local_accumulative_recall, local_accumulative_precision,
                                   global_accumulative_recall, global_accumulative_precision,
                                   gt_flag, det_flag)
 
    #######then check for many-to-one case##########
    local_accumulative_recall, local_accumulative_precision, global_accumulative_recall, global_accumulative_precision, \
    gt_flag, det_flag = many_to_one(local_sigma_table, local_tau_table,
                                    local_accumulative_recall, local_accumulative_precision,
                                    global_accumulative_recall, global_accumulative_precision,
                                    gt_flag, det_flag)
 
 
 
 
    fid = open(fid_path, 'a+')
    try:
        local_precision = local_accumulative_precision / num_det
    excerpt ZeroDivisionError:
        local_precision = 0
 
    try:
        local_recall = local_accumulative_recall / num_gt
    excerpt ZeroDivisionError:
        local_recall = 0
 
    temp = ('%s______/Precision:_%s_______/Recall:_%s\n' % (allInputs[idx], str(local_precision), str(local_recall)))
    fid.write(temp)
    fid.close()
try:
    recall = global_accumulative_recall / total_num_gt
excerpt ZeroDivisionError:
    recall = 0
 
try:
    precision = global_accumulative_precision / total_num_det
excerpt ZeroDivisionError:
    precision = 0
 
try:
    f_score = 2*precision*recall/(precision+recall)
excerpt ZeroDivisionError:
    f_score = 0
 
fid = open(fid_path, 'a')
temp = ('Precision:_%s_______/Recall:_%s\n' %(str(precision), str(recall)))
fid.write(temp)
fid.close()
print(temp)

这段代码是一个循环,循环遍历 global_sigma 列表中的每个元素。

在每次循环中,首先打印 allInputs[idx] 的值,然后将 global_sigma[idx] 赋值给 local_sigma_table,将 global_tau[idx] 赋值给 local_tau_table

接下来,计算 local_sigma_table 的形状,并将结果分别赋值给 num_gtnum_det

然后,将 num_gtnum_det 加到累计变量 total_num_gttotal_num_det 上。

接着,初始化一些变量,包括 local_accumulative_recalllocal_accumulative_precisiongt_flagdet_flag

然后,依次调用 one_to_oneone_to_manymany_to_one 函数,对 local_sigma_tablelocal_tau_table 进行匹配计算,并更新相关计数变量和标志位。

接下来,打开一个文件,并将 local_accumulative_precisionlocal_precision 计算结果写入文件。

Pascal VOC

​ 传统的 0.50.5 阈值。


​ 提供了两种语言:Matlab 和 Python,只有 Matlab 的有示例……Python 的没有,绝

png

Groundtruth

​ 可提供的 GT:

  • Pixel 级

    • Character Level Mask

      • The pixel level groundtruth of Total-Text dataset can be downloaded at this https URL (80Mb).
      gif
    • Text Region Mask

      • The text region mask groundtruth of Total-Text dataset can be downloaded at this https URL (6Mb).
      gif
  • Text 级

    png

计划(寄)

​ 复现一下 Total-Text Benchmark (Scene Text Detection) | Papers With Codecs-chan/Total-Text-Dataset: Total Text Dataset. It consists of 1555 images with more than 3 different text orientations: Horizontal, Multi-Oriented, and Curved, one of a kind. (github.com) 里的代码。

​ 真是太难复现了我日。

​ 尝试过的/打算尝试的:

ModelF-MeasurePrecisionRecallPaperGitHub备注
CentripetalText87.85%90.6785.19CentripetalText: An Efficient Text Instance Representation for Scene Text Detection Papers With Codeshengtao96/CentripetalText (github.com)默认的 Total-Text 形式
FCE89.382.585.8GXYM/TextBPN-Plus-Plus: Arbitrary Shape Text Detection via Boundary Transformer;The paper at: https://arxiv.org/abs/2205.05320, which has been accepted by IEEE Transactions on Multimedia (T-MM 2023). (github.com)GXYM/TextBPN-Plus-Plus: Arbitrary Shape Text Detection via Boundary Transformer;The paper at: https://arxiv.org/abs/2205.05320, which has been accepted by IEEE Transactions on Multimedia (T-MM 2023). (github.com)默认的 Total-Text 形式
DPText-DETR
(ResNet-50)
87.3%82.193.1[2211.10772] DeepSolo: Let Transformer Decoder with Explicit Points Solo for Text Spotting (arxiv.org)[ViTAE-Transformer/DeepSolo: The official repo for CVPR'23] "DeepSolo: Let Transformer Decoder with Explicit Points Solo for Text Spotting" & [ArXiv'23] "DeepSolo++: Let Transformer Decoder with Explicit Points Solo for Text Spotting" (github.com)Total-Text 的形式不同于默认形式(复杂的 json 文件)
FAST-B-80087.5%90.085.2[2111.02394v2] FAST: Faster Arbitrarily-Shaped Text Detector with Minimalist Kernel Representation (arxiv.org)czczup/FAST: Faster Arbitrarily-Shaped Text Detector with Minimalist Kernel Representation (github.com)sh ./compile.sh 编译失败,寄!
TextFuseNet
(ResNeXt-101)
87.5%89.285.8TextFuseNet: Scene Text Detection with Richer Fused Features Papers With Codeying09/TextFuseNet: A PyTorch implementation of "TextFuseNet: Scene Text Detection with Richer Fused Features". (github.com)
I3CL + SSL
(ResNet-50)
86.9%89.884.2I3CL + SSL
(ResNet-50)
ViTAE-Transformer/I3CL: The official repo for [IJCV'22] "I3CL: Intra- and Inter-Instance Collaborative Learning for Arbitrary-shaped Scene Text Detection" (github.com)仓库里好像没有适配 Total-Text 的
CharNet H-88 (multi-scale)86.5%8885Convolutional Character Networks Papers With Codemsight-tech/research-charnet: CharNet: Convolutional Character Networks (github.com)仓库里好像没有适配 Total-Text 的,只有 ICDAR2015
DBNet++
(ResNet-50)
86%88.983.2Real-Time Scene Text Detection with Differentiable Binarization and Adaptive Scale Fusion Papers With CodeMhLiao/DB: A PyTorch implementation of "Real-time Scene Text Detection with Differentiable Binarization". (github.com)Total-Text 的形式不同于默认形式(较易懂的 txt 形式)
PAN-64085%89.381Efficient and Accurate Arbitrary-Shaped Text Detection with Pixel Aggregation Network Papers With CodeWenmuZhou/PAN.pytorch: A unofficial pytorch implementation of PAN(PSENet2): Efficient and Accurate Arbitrary-Shaped Text Detection with Pixel Aggregation Network (github.com)仓库里好像没有适配 Total-Text 的,只有 ICDAR2015
DB-ResNet-50 (800)84.7%Real-time Scene Text Detection with Differentiable Binarization Papers With CodeMhLiao/DB: A PyTorch implementation of "Real-time Scene Text Detection with Differentiable Binarization". (github.com)Total-Text 的形式不同于默认形式(较易懂的 txt 形式)
CRAFT83.6%87.679.9Character Region Awareness for Text Detection Papers With Codeclovaai/CRAFT-pytorch: Official implementation of Character Region Awareness for Text Detection (CRAFT) (github.com)仓库里好像没有适配 Total-Text 的
TextSnake78.4%82.774.5TextSnake: A Flexible Representation for Detecting Text of Arbitrary Shapes Papers With Codeprincewang1994/TextSnake.pytorch: A PyTorch implementation of ECCV2018 Paper: TextSnake: A Flexible Representation for Detecting Text of Arbitrary Shapes (github.com)Total-Text 的形式不同于默认形式

可视化数据集

​ 根据数据集的源图像以及它的 txt 标注格式,再一阵 ChatGPT 和一阵操作,写一个可视化代码:

python
import cv2
import os
import matplotlib.pyplot as plt
import numpy as np
 
index = 396
 
image_dir = r'E:\dataset\TotalText\Images\Test\\'
label_dir = r'E:\dataset\TotalText\GroundTruth\Text\Test\\'
 
image_path = os.path.join(image_dir, 'img' + str(index) + '.jpg')
label_path = os.path.join(label_dir, 'poly_gt_img' + str(index) + '.txt')
 
image_origin = cv2.imread(image_path)
image = image_origin.copy()
height, width, _ = image.shape
label_file = open(label_path, 'r')
annotations = label_file.readlines()
label_file.close()
 
for annotation in annotations:
    x = [int(num) for num in annotation[annotation.find("x: [[") + 5: annotation.find("]], y: [[")].split()]
    y = [int(num) for num in annotation[annotation.find("y: [[") + 5: annotation.find("]], ornt: [")].split()]
    ornt = annotation[annotation.find("ornt: [u'") + 9: annotation.find("'], transcriptions: [")]
    transcriptions = annotation[annotation.find("transcriptions: [u'") + 19: -3]
    
    points = np.array([x, y], np.int32).T
    
    cv2.polylines(image, [points], isClosed=True, color=(255, 0, 0), thickness=2)
    for p in points:
        cv2.circle(image, (p[0], p[1]), int(min(height, width) / 150), (0, 255, 255), -1)
 
    cv2.putText(image, ornt, (x[0], y[0] + int(min(height, width) / 50)), cv2.FONT_HERSHEY_SIMPLEX,
                min(height, width) / 1000, (255, 0, 255), int(min(height, width) / 500))
    cv2.putText(image, transcriptions, (x[0], y[0] - int(min(height, width) / 150)), cv2.FONT_HERSHEY_SIMPLEX,
                min(height, width) / 1000, (0, 255, 0), int(min(height, width) / 500))
    
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 9))
axes = axes.flatten()
 
axes[0].imshow(cv2.cvtColor(image_origin, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[0].set_title('Origin')
 
axes[1].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[1].set_title('Annotation')
 
plt.tight_layout()
plt.show()
png