转载自AI Studio 项目链接:https://aistudio.baidu.com/aistudio/projectdetail/3710242?channelType=0&channel=0
0 背景:PaddleOCR的电表识别任务(主线之四)
我国电力行业发展迅速,电表作为测电设备经历了普通电表、预付费电表和智能电表三个阶段的发展,虽然智能电表具有通信功能,但一方面环境和设备使得智能电表具有不稳定性,另一方面非智能电表仍然无法实现自动采集,人工抄表有时往往不可取代。采集到的大量电表图片如果能够借助人工智能技术批量检测和识别,将会大幅提升效率和精度。

在本系列项目中,我们使用Paddle工具库实现一个OCR垂类场景。原始数据集是一系列电度表的照片,类型较多,要对数据集进行一定程度的扩充,才能进一步提升训练模型的性能,在前置项目研究中,我们已经针对电表编号的点阵字符数据进行了扩充,在本项目中,我们将进一步探索更加复杂情况,比如生成黑底白字的电表读数、液晶屏读数;并对比常用的OCR数据集扩充方法:TextRender和StyleText在电表识别这个任务下的效果。

前置系列项目:

(主线篇)

PPOCR:多类别电表读数识别
PPOCR:使用TextRender进行电表编号识别的finetune
数据标注懒人包:PPOCRLabel极速增强版——以电表识别为例(二)
(番外篇)

PPOCR+PPDET电表读数和编号检测
上面这些项目,解决了电表读数和编号检测的一些基础问题,并针对如何补充电表编号点阵数据进行了深入探索。

但是,有心的读者想必会发现,我们是不是还缺了什么?

是的,确实还有一个问题没解决,电表读数识别的finetune哪去了?

将前置项目生成的finetune模型导出部署到PPOCRLabel后会发现,面对后面补充的数据集,识别模型多少还是有些捉襟见肘,一些电表读数没法识别到位。因此,对电表读数也来一番finetune,还是十分必要的。

本文的主要任务,是对TextRender的应用进行更进一步的探究,并与PaddleOCR提供的另一种解决方案,StyleText进行一番对比。

1 环境准备
In [ ]

解压数据集

!unzip -O GB2312 data/data117381/M2021.zip
In [29]
import os
import json
import cv2
import numpy as np
import math
from PIL import Image, ImageDraw
In [ ]
!git clone https://gitee.com/paddlepaddle/PaddleOCR.git
In [14]
%cd PaddleOCR/
!pip install fasttext==0.8.3
!pip install paddleocr --no-deps -r …/requirements.txt
%cd ~
In [2]
!git clone https://gitee.com/wowowoll/text_renderer.git
In [ ]
!pip install -r text_renderer/requirements.txt
2 准备电表读数识别数据集
无论是TextRender还是StyleText的强大功能,都需要我们先准备好字体文件、语料库等基本素材。

但是这里需要说明的是,由于电表识别内容很多,至少包括了:

白字黑底的电表读数
白字黑底+末尾白字黑底的电表读数
液晶读数
白底黑色点阵的电表编号
在准备数据集时,我们首先要对各种类别数据进行区分——幸运的是,大部分还是可以通过梳理业务逻辑进行分类的。

2.1 裁剪标注图片
In [5]
class Rotate(object):

def __init__(self, image: Image.Image, coordinate):
    self.image = image.convert('RGB')
    self.coordinate = coordinate
    self.xy = [tuple(self.coordinate[k]) for k in ['left_top', 'right_top', 'right_bottom', 'left_bottom']]
    self._mask = None
    self.image.putalpha(self.mask)

@property
def mask(self):
    if not self._mask:
        mask = Image.new('L', self.image.size, 0)
        draw = ImageDraw.Draw(mask, 'L')
        draw.polygon(self.xy, fill=255)
        self._mask = mask
    return self._mask

def run(self):
    image = self.rotation_angle()
    box = image.getbbox()
    return image.crop(box)

def rotation_angle(self):
    x1, y1 = self.xy[0]
    x2, y2 = self.xy[1]
    angle = self.angle([x1, y1, x2, y2], [0, 0, 10, 0]) * -1
    return self.image.rotate(angle, expand=True)

def angle(self, v1, v2):
    dx1 = v1[2] - v1[0]
    dy1 = v1[3] - v1[1]
    dx2 = v2[2] - v2[0]
    dy2 = v2[3] - v2[1]
    angle1 = math.atan2(dy1, dx1)
    angle1 = int(angle1 * 180 / math.pi)
    angle2 = math.atan2(dy2, dx2)
    angle2 = int(angle2 * 180 / math.pi)
    if angle1 * angle2 >= 0:
        included_angle = abs(angle1 - angle2)
    else:
        included_angle = abs(angle1) + abs(angle2)
        if included_angle > 180:
            included_angle = 360 - included_angle
    return included_angle

2.1.1 准备普通电表读数抠图
对普通电表读数抠图,只要业务逻辑理顺,还是比较容易实现的。在本项目提供的数据集中,主要逻辑就是两个:

label不包括小数点(液晶读数有小数点)
label长度小于等于5(用以区分电表读数和编号)
In [23]
!mkdir M2021_type1_crop
In [7]
with open(‘./M2021/Label.txt’,‘r’,encoding=‘utf8’)as fp:
s = [i[:-1].split(‘\t’) for i in fp.readlines()]
f1 = open(‘M2021_type1_crop/rec_gt_type1.txt’, ‘w’, encoding=‘utf-8’)
for i in enumerate(s):
path = i[1][0]
anno = json.loads(i[1][1])
filename = i[1][0][6:-4]
image = Image.open(path)
for j in range(len(anno)):
label = anno[j][‘transcription’]
roi = anno[j][‘points’]
coordinate = {‘left_top’: anno[j][‘points’][0], ‘right_top’: anno[j][‘points’][1], ‘right_bottom’: anno[j][‘points’][2], ‘left_bottom’: anno[j][‘points’][3]}
print(roi, label)
if ‘.’ in label:
pass
elif len(label)==5:
rotate = Rotate(image, coordinate)
# 把图片放到目录下
crop_path = ‘M2021_type1_crop’ + path[5:-4:] + ‘’ + str(j) + ‘.jpg’
rotate.run().convert(‘RGB’).save(crop_path)
# label文件不写入图片目录
crop_path = path[6:-4:] + '
’ + str(j) + ‘.jpg’
f1.writelines(crop_path + ‘\t’ + label + ‘\n’)
f1.close()
效果如下:

2.1.2 准备液晶屏电表读数抠图
在本项目提供的数据集中,也有一部分电表是液晶显示读数的,不过识别起来也相对容易,主要就看label中是否包括了小数点。

我们可以基于这个逻辑,对这部分的图片进行提取。

In [1]
!mkdir M2021_type2_crop
In [9]
with open(‘./M2021/Label.txt’,‘r’,encoding=‘utf8’)as fp:
s = [i[:-1].split(‘\t’) for i in fp.readlines()]
f1 = open(‘M2021_type2_crop/rec_gt_type2.txt’, ‘w’, encoding=‘utf-8’)
for i in enumerate(s):
path = i[1][0]
anno = json.loads(i[1][1])
filename = i[1][0][6:-4]
image = Image.open(path)
for j in range(len(anno)):
label = anno[j][‘transcription’]
roi = anno[j][‘points’]
coordinate = {‘left_top’: anno[j][‘points’][0], ‘right_top’: anno[j][‘points’][1], ‘right_bottom’: anno[j][‘points’][2], ‘left_bottom’: anno[j][‘points’][3]}
print(roi, label)
if ‘.’ in label:
rotate = Rotate(image, coordinate)
# 把图片放到目录下
crop_path = ‘M2021_type2_crop’ + path[5:-4:] + ‘’ + str(j) + ‘.jpg’
rotate.run().convert(‘RGB’).save(crop_path)
# label文件不写入图片目录
crop_path = path[6:-4:] + '
’ + str(j) + ‘.jpg’
f1.writelines(crop_path + ‘\t’ + label + ‘\n’)
f1.close()

2.1.3 准备电表编号抠图
这个逻辑在前置项目中已经梳理过了,不过本项目展示另一种逻辑:

label不包括小数点(排除液晶读数)
label长度超过7位(基本排除普通电表读数)
这么操作后,至少在本项目的电表数据集中,已经可以锁定点阵电表编号了。

In [7]
!mkdir M2021_type3_crop
In [11]
with open(‘./M2021/Label.txt’,‘r’,encoding=‘utf8’)as fp:
s = [i[:-1].split(‘\t’) for i in fp.readlines()]
f1 = open(‘M2021_type3_crop/rec_gt_type3.txt’, ‘w’, encoding=‘utf-8’)
for i in enumerate(s):
path = i[1][0]
anno = json.loads(i[1][1])
filename = i[1][0][6:-4]
image = Image.open(path)
for j in range(len(anno)):
label = anno[j][‘transcription’]
roi = anno[j][‘points’]
coordinate = {‘left_top’: anno[j][‘points’][0], ‘right_top’: anno[j][‘points’][1], ‘right_bottom’: anno[j][‘points’][2], ‘left_bottom’: anno[j][‘points’][3]}
print(roi, label)
if ‘.’ in label:
pass
elif len(label)>=7:
rotate = Rotate(image, coordinate)
# 把图片放到目录下
crop_path = ‘M2021_type3_crop’ + path[5:-4:] + ‘’ + str(j) + ‘.jpg’
rotate.run().convert(‘RGB’).save(crop_path)
# label文件不写入图片目录
crop_path = path[6:-4:] + '
’ + str(j) + ‘.jpg’
f1.writelines(crop_path + ‘\t’ + label + ‘\n’)
f1.close()

2.2 准备随机语料文件
随机生成4位、5位、6位整数的电表读数语料文件。

In [17]
import random
f1 = open(‘type1.txt’, ‘w’, encoding=‘utf-8’)

生成包括5000个随机样本的语料库

for i in range(5000):
if i % 3 == 0:
i = random.randint(1000,9999)
elif i % 3 == 1:
i = random.randint(10000,99999)
elif i % 3 == 2:
i = random.randint(100000,999999)
f1.writelines(str(i) + ‘\n’)
f1.close()
随机生成液晶电子屏的电表读数语料(和普通电表差异在于,会带小数点)。

In [58]
import random
f1 = open(‘type2.txt’, ‘w’, encoding=‘utf-8’)

生成包括5000个随机样本的语料库

for i in range(5000):
if i % 3 == 0:
i = round(random.uniform(1000,9999), 2)
elif i % 3 == 1:
i = round(random.uniform(10000,99999), 2)
elif i % 3 == 2:
i = round(random.uniform(1000,9999), 2)
f1.writelines(str(i) + ‘\n’)
f1.close()
3 TextRender生成补充数据集
3.1 黑底白字图片生成
这里最关键的就是反色和字间距的配置:

配置字间距

random_space:
enable: true
fraction: 1
min: 0.4
max: 0.6

100%使用背景图

img_bg:
enable: true
fraction: 0

反色配置

reverse_color:
enable: true
fraction: 1
完整的配置信息保存在data_type1目录中,读者只需用data_type1替换text_renderer下的data目录即可体验。

In [66]
%cd text_renderer/
/home/aistudio/text_renderer
In [67]
!rm -r data
!cp -r …/data_type1 ./data
!cp …/default_type1.yaml configs/default.yaml
In [68]

作为示例,这里只生成5000张图片

!python main.py --num_img=5000 --chars_file=‘./data/chars/metrics.txt’
–fonts_list=‘./data/fonts_list/eng.txt’ --corpus_dir=“data/list_corpus”
–img_height=128 --img_width=512
–t=type1
–bg_dir “data/bg” --corpus_mode “list”
Total fonts num: 1
Background num: 3
Loading corpus from: data/list_corpus
Load corpus: data/list_corpus/type1.txt
Total lines: 5000
Generate text images in ./output/type1
5000/5000 100%
Finish generate data: 149.580 s
5000张图片生成时间约为150秒,单张图片生成时间大概是0.03秒,可以说非常快了!

经过了这样一番操作,黑底白字的操作总算是实现了,尽快要用他们“仿冒”电表读数,看起来还是有一定的差距。不过,不管怎么说,还是勉强可以一用嘛。

3.2 液晶电子屏图片生成
只要找到了电子屏液晶字体、液晶屏背景,这个部分和电表编号识别数据集的扩充基本没什么区别。本项目也就不再赘述了,几个注意事项:

反色配置记得要改回来
电子屏数字间距没有普通电表那么大,可以适当调整
In [69]
!rm -r data
!cp -r …/data_type2 ./data
!cp …/default_type2.yaml configs/default.yaml
In [70]

作为示例,这里只生成5000张图片

!python main.py --num_img=5000 --chars_file=‘./data/chars/metrics.txt’
–fonts_list=‘./data/fonts_list/eng.txt’ --corpus_dir=“data/list_corpus”
–img_height=128 --img_width=512
–t=type2
–bg_dir “data/bg” --corpus_mode “list”
Total fonts num: 1
Background num: 2
Loading corpus from: data/list_corpus
Load corpus: data/list_corpus/type2.txt
Total lines: 5000
Generate text images in ./output/type2
5000/5000 100%
Finish generate data: 464.914 s
图片生成效果如下:

4 StyleText生成补充数据集
4.1 StyleText原理解析
Style-Text数据合成工具是基于百度和华科合作研发的文本编辑算法《Editing Text in the Wild》https://arxiv.org/abs/1908.03047

不同于常用的基于GAN的数据合成工具,StyleText主要框架包括:

1.文本前景风格迁移模块。

2.背景抽取模块。

3.融合模块。

经过这样三步,就可以迅速实现图像文本风格迁移。

看着还是很美的吧~~~

4.2 StyleText注意事项
重要的事情必须最先说!开工之前,先阅读文档非常重要,千万不要嫌麻烦!比如PaddleOCR,要是没看FAQ,会不知觉掉入大坑……

(当然个人觉得,其实OCR文档结构可以完善下,各子模块在说明中关联下对应的FAQ,还是很有必要的)

现在,我们先摘录下PaddleOCR的FAQ中,关于StyleText的说明:

Q: Style-Text 如何不文字风格迁移,就像普通文本生成程序一样默认字体直接输出到分割的背景图?

A:使用image_synth模式会输出fake_bg.jpg,即为背景图。如果想要批量提取背景,可以稍微修改一下代码,将fake_bg保存下来即可。要修改的位置: https://github.com/PaddlePaddle/PaddleOCR/blob/de3e2e7cd3b8b65ee02d7a41e570fa5b511a3c1d/StyleText/engine/synthesisers.py#L68

Q: 能否修改StyleText配置文件中的分辨率?

A:StyleText目前的训练数据主要是高度32的图片,建议不要改变高度(重点!!!)。未来我们会支持更丰富的分辨率。

Q: StyleText是否可以更换字体文件?

A:StyleText项目中的字体文件为标准字体,主要用作模型的输入部分,不能够修改(重点!!!)。 StyleText的用途主要是:提取style_image中的字体、背景等style信息,根据语料生成同样style的图片。

Q: StyleText批量生成图片为什么没有输出?

A:需要检查以下您配置文件中的路径是否都存在。尤其要注意的是label_file配置。 如果您使用的style_image输入没有label信息,您依然需要提供一个图片文件列表。

Q:使用StyleText进行数据合成时,文本(TextInput)的长度远超StyleInput的长度,该怎么处理与合成呢?

A:在使用StyleText进行数据合成的时候,建议StyleInput的长度长于TextInput的长度。有2种方法可以处理上述问题:

将StyleInput按列的方向进行复制与扩充,直到其超过TextInput的长度。
将TextInput进行裁剪,保证每段TextInput都稍短于StyleInput,分别合成之后,再拼接在一起。
实际使用中发现,使用第2种方法的效果在长文本合成的场景中的合成效果更好,StyleText中提供的也是第2种数据合成的逻辑。

Q: StyleText 合成数据效果不好?

A:StyleText模型生成的数据主要用于OCR识别模型的训练。PaddleOCR目前识别模型的输入为32 x N,因此当前版本模型主要适用高度为32的数据。 建议要合成的数据尺寸设置为32 x N。尺寸相差不多的数据也可以生成,尺寸很大或很小的数据效果确实不佳(重点!!!)。

通过仔细阅读上面的FAQ,我们可以知道,因为PaddleOCR中开源的只是StyleText的部署部分,网络结构、字体、输入size在训练时都已经确定了。因此,我们在使用StyleText时,也需要遵循FAQ中提到的要求,才能少走弯路。

4.3 使用Style-Text合成图片
Style-Text可以使用一批风格图片和语料,批量合成数据。合成过程如下:

在configs/dataset_config.yml中配置目标场景风格图像和语料的路径,具体如下:

Global:
output_dir::保存合成数据的目录。
StyleSampler:
image_home:风格图片目录;
label_file:风格图片路径列表文件,如果所用数据集有label,则label_file为label文件路径;
with_label:标志label_file是否为label文件。
CorpusGenerator:
method:语料生成方法,目前有FileCorpus和EnNumCorpus可选。如果使用EnNumCorpus,则不需要填写其他配置,否则需要修改corpus_file和language;
language:语料的语种,目前支持英文(en)、简体中文(ch)和韩语(ko);
corpus_file: 语料文件路径。语料文件应使用文本文件。语料生成器首先会将语料按行切分,之后每次随机选取一行。
语料文件格式示例:

PaddleOCR
飞桨文字识别
StyleText
风格文本图像数据合成

现在我们知道,在大部分默认配置都不能改动的情况下,用StyleText只需好好准备背景图片和语料文件就行。

In [ ]
%cd ~
%cd PaddleOCR/StyleText
In [ ]
!wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/style_text/style_text_models.zip
!unzip style_text_models.zip
我们知道,在电表数据集中,图片分辨率非常大,这是抠图后的某张图片:

在这种情况下,我们需要调整下图片的image_height到32。

4.3.1 生成普通电表数字
准备一个resize后的数据集。

In [47]
!mkdir …/…/M2021_type1_crop_small
In [51]
filename = os.listdir(“…/…/M2021_type1_crop”) #原图片集所在目录
base_dir = “…/…/M2021_type1_crop/”
new_dir = “…/…/M2021_type1_crop_small/” #缩放后的图片存放的路径
new_height = 32 #设置新图像最长边像素数

for img in filename: #遍历文件夹中的图片
if img[-3:] == ‘jpg’:
image = Image.open(base_dir + img)
width =image.size[0] #原图宽
height =image.size[1] #原图高
if(width>=height):
new_width = int(width * new_height / height)
out=image.resize((new_width,new_height),Image.ANTIALIAS)
out.save(new_dir + img) #保存新图片
批量合成普通电表读数,由于风格迁移生成图片时间比较长,本项目仅合成10个风格图片作为示例。

在配置文件修改中,重点就是把各种路径配进去。

StyleSampler:
method: DatasetSampler
image_home: …/…/M2021_type1_crop_small
label_file: …/…/M2021_type1_crop/rec_gt_type1.txt
with_label: true
CorpusGenerator:
method: FileCorpus
language: en
corpus_file: …/…/type1.txt
In [56]
!cp …/…/dataset_config_type1.yml configs/dataset_config.yml
In [55]
!python tools/synth_dataset.py -c configs/dataset_config.yml -t=type1
合成1张图片耗时约1.7秒,合成图片效果如下:

4.3.2 生成液晶电表数字
这里比较大的一个问题在于,不能配置字体文件(或者说配置了也没用),因为StyleText的训练配置未开源,而训练过程都是基于默认字体文件实现的。

In [57]
!mkdir …/…/M2021_type2_crop_small
In [58]
filename = os.listdir(“…/…/M2021_type2_crop”) #原图片集所在目录
base_dir = “…/…/M2021_type2_crop/”
new_dir = “…/…/M2021_type2_crop_small/” #缩放后的图片存放的路径
new_height = 32 #设置新图像最长边像素数

for img in filename: #遍历文件夹中的图片
if img[-3:] == ‘jpg’:
image = Image.open(base_dir + img)
width =image.size[0] #原图宽
height =image.size[1] #原图高
if(width>=height):
new_width = int(width * new_height / height)
out=image.resize((new_width,new_height),Image.ANTIALIAS)
out.save(new_dir + img) #保存新图片
In [63]
!python tools/synth_dataset.py -c configs/dataset_config.yml -t=type2
[2022/03/31 21:20:15] srnet INFO: load pretrained model from style_text_models/bg_generator
[2022/03/31 21:20:17] srnet INFO: load pretrained model from style_text_models/text_generator
[2022/03/31 21:20:17] srnet INFO: load pretrained model from style_text_models/fusion_generator
[2022/03/31 21:20:17] srnet INFO: using FileCorpus
[2022/03/31 21:20:18] srnet INFO: generate image: output_data/images/type2/0.png
[2022/03/31 21:20:19] srnet INFO: generate image: output_data/images/type2/1.png
[2022/03/31 21:20:21] srnet INFO: generate image: output_data/images/type2/2.png
[2022/03/31 21:20:22] srnet INFO: generate image: output_data/images/type2/3.png
[2022/03/31 21:20:23] srnet INFO: generate image: output_data/images/type2/4.png
[2022/03/31 21:20:24] srnet INFO: generate image: output_data/images/type2/5.png
[2022/03/31 21:20:26] srnet INFO: generate image: output_data/images/type2/6.png
[2022/03/31 21:20:27] srnet INFO: generate image: output_data/images/type2/7.png
[2022/03/31 21:20:28] srnet INFO: generate image: output_data/images/type2/8.png
[2022/03/31 21:20:29] srnet INFO: generate image: output_data/images/type2/9.png

观察上面的合成图片,我们会发现SytleText在生成液晶电表读数时,出现了一个大问题,小数点“.”不见了!

即使修改源码,也处理不了这个问题,原因还是在于,SytleText开源的只有推理模型。有极大概率,训练时的数据就没有小数点,自然合成不了了。

4.3.3 合成电表编号图片
因为FAQ里已经提到,StyleText在合成较长的文字时效果可能不好,因此这里我们就做几个单张图片测试,看看电表编号的图片合成效果。这里准备了张已经缩放过的图片:

In [64]
!python tools/synth_image.py -c configs/config.yml --style_image …/…/IMG_20210712_101213_0.jpg --language en --text_corpus 2002-325411
[2022/03/31 21:30:46] srnet INFO: load pretrained model from style_text_models/bg_generator
[2022/03/31 21:30:48] srnet INFO: load pretrained model from style_text_models/text_generator
[2022/03/31 21:30:48] srnet INFO: load pretrained model from style_text_models/fusion_generator
生成的文字在fake_text.jpg文件中:

提取的图片在fake_bg.jpg文件中:

合成的图片在fake_fusion.jpg文件中:

所以,问题和SytleText在生成液晶电表读数时一样,连接字符“-”不见了!原因显而易见……

5 性能对比
应该说,TextRender和StyleText代表了两种“造字”路线。当然,二者也有很多相似之处,比如需要语料库和字体文件(完整的StyleText也是基于特定字体文件训练的),输出文件标签格式也类似。

从整体特点上看,TextRender可能相对“死板”,生成的字体规规矩矩,背景图相对单一(尽管有一些简单的模糊、噪声、旋转操作);而StyleText仿的是风格,相对而言更“灵活”一些,但是成本代价实在有点大、效果也不太稳定。

基于本文的探索,这里整理了一个简单的对照表,供读者参考:

类别 TextRender StyleText
单张图片合成速度 0.03s 1.7s
需要原图 否 是
需要背景图 是 否
字体文件配置 支持多种字体 不支持
生成文字风格 固定 多样
生成文本准确性 稳定 不稳定
输入size可变 是 否
在选型时,读者还是需要根据自己数据集的实际情况进行判断。比如在这个电表任务中,如果想快速补充数据集,TextRender可能是个比较好的选择。

6 模型训练
准备了那么久数据,不试验下效果怎么行?接下来,我们就试试看再次补充数据后,模型性能能提高到什么程度。

本项目中,我们直接使用PPOCR:使用TextRender进行电表编号识别的finetune训练后的模型,作为预训练模型——快速看看成效!

6.1 标签和数据集整理
In [71]
%cd ~
/home/aistudio
In [ ]

!mkdir M2021_crop
将合成的文本识别数据集与原有训练集合并,这里提供了追加标注文件的脚本。

不过,在进行下列操作前,最好把M2021_crop的原始验证集、训练集标签先存一份副本,后面效果评估的时候会用到。

In [83]

生成PaddleOCR文字识别数据集格式文件

with open(‘./text_renderer/output/type1/tmp_labels.txt’,‘r’) as f:
s = [i[:-1].split(’ ‘) for i in f.readlines()]
f1 = open(‘M2021_crop/rec_gt_train.txt’, ‘a+’, encoding=‘utf-8’)
f2 = open(‘M2021_crop/rec_gt_eval.txt’, ‘a+’, encoding=‘utf-8’)
for i in enumerate(s):
path =‘type1/’ + i[1][0] + ‘.jpg’
if i[0] % 5 == 0:
f2.writelines(path + ‘\t’ + i[1][1] + ‘\n’)
else:
f1.writelines(path + ‘\t’ + i[1][1] + ‘\n’)
f1.close()
f2.close()
In [84]
with open(’./text_renderer/output/type2/tmp_labels.txt’,‘r’) as f:
s = [i[:-1].split(’ ') for i in f.readlines()]
f1 = open(‘M2021_crop/rec_gt_train.txt’, ‘a+’, encoding=‘utf-8’)
f2 = open(‘M2021_crop/rec_gt_eval.txt’, ‘a+’, encoding=‘utf-8’)
for i in enumerate(s):
path =‘type2/’ + i[1][0] + ‘.jpg’
if i[0] % 5 == 0:
f2.writelines(path + ‘\t’ + i[1][1] + ‘\n’)
else:
f1.writelines(path + ‘\t’ + i[1][1] + ‘\n’)
f1.close()
f2.close()
In [86]

数据集移到同一个目录下

!mv text_renderer/output/type1 ./M2021_crop/
!mv text_renderer/output/type2 ./M2021_crop/
6.2 训练与验证
In [ ]

rec_number_finetune.tar中,保存了我们要用的配置文件和电表编号识别最佳权重

!tar -xvf rec_number_finetune.tar -C PaddleOCR/
In [87]
%cd ~/PaddleOCR/
/home/aistudio/PaddleOCR
In [90]
!python tools/train.py -c rec_number_finetune/config.yml
7个epoch后验证集上表现就已经很优异了:

[2022/04/01 00:19:36] root INFO: Initialize indexs of datasets:[‘…/M2021_crop/rec_gt_train.txt’]
[2022/04/01 00:20:04] root INFO: epoch: [7/200], iter: 190, lr: 0.000998, loss: 0.106318, acc: 0.982418, norm_edit_dis: 0.997040, reader_cost: 2.57259 s, batch_cost: 2.68101 s, samples: 1280, ips: 47.74315
[2022/04/01 00:20:28] root INFO: epoch: [7/200], iter: 200, lr: 0.000998, loss: 0.097034, acc: 0.984371, norm_edit_dis: 0.996977, reader_cost: 1.98631 s, batch_cost: 2.21954 s, samples: 2560, ips: 115.33935
eval model:: 100%|██████████████████████████████| 32/32 [00:05<00:00, 5.53it/s]
[2022/04/01 00:20:34] root INFO: cur metric, acc: 0.9941372769236556, norm_edit_dis: 0.9983229434143235, fps: 780.0351024597015
[2022/04/01 00:20:34] root INFO: save best model is to ./output/best_accuracy
[2022/04/01 00:20:34] root INFO: best metric, acc: 0.9941372769236556, norm_edit_dis: 0.9983229434143235, fps: 780.0351024597015, best_epoch: 7
为了进一步确认,我们就把PPOCR:使用TextRender进行电表编号识别的finetune200个epoch的训练结果与在此基础上,补充电表读数合成数据集后,又训练了7个epoch的效果进行对比。注意:要用M2021_crop的原始验证集标签。

In [93]
!python tools/eval.py -c rec_number_finetune/config.yml -o Global.checkpoints=rec_number_finetune/best_accuracy
[2022/04/01 00:25:44] root INFO: Architecture :
[2022/04/01 00:25:44] root INFO: Backbone :
[2022/04/01 00:25:44] root INFO: model_name : small
[2022/04/01 00:25:44] root INFO: name : MobileNetV3
[2022/04/01 00:25:44] root INFO: scale : 0.5
[2022/04/01 00:25:44] root INFO: small_stride : [1, 2, 2, 2]
[2022/04/01 00:25:44] root INFO: Head :
[2022/04/01 00:25:44] root INFO: fc_decay : 1e-05
[2022/04/01 00:25:44] root INFO: name : CTCHead
[2022/04/01 00:25:44] root INFO: Neck :
[2022/04/01 00:25:44] root INFO: encoder_type : rnn
[2022/04/01 00:25:44] root INFO: hidden_size : 48
[2022/04/01 00:25:44] root INFO: name : SequenceEncoder
[2022/04/01 00:25:44] root INFO: Transform : None
[2022/04/01 00:25:44] root INFO: algorithm : CRNN
[2022/04/01 00:25:44] root INFO: model_type : rec
[2022/04/01 00:25:44] root INFO: Eval :
[2022/04/01 00:25:44] root INFO: dataset :
[2022/04/01 00:25:44] root INFO: data_dir : …/M2021_crop
[2022/04/01 00:25:44] root INFO: label_file_list : [‘…/M2021_crop/rec_gt_eval-Copy1.txt’]
[2022/04/01 00:25:44] root INFO: name : SimpleDataSet
[2022/04/01 00:25:44] root INFO: transforms :
[2022/04/01 00:25:44] root INFO: DecodeImage :
[2022/04/01 00:25:44] root INFO: channel_first : False
[2022/04/01 00:25:44] root INFO: img_mode : BGR
[2022/04/01 00:25:44] root INFO: CTCLabelEncode : None
[2022/04/01 00:25:44] root INFO: RecResizeImg :
[2022/04/01 00:25:44] root INFO: image_shape : [3, 32, 320]
[2022/04/01 00:25:44] root INFO: KeepKeys :
[2022/04/01 00:25:44] root INFO: keep_keys : [‘image’, ‘label’, ‘length’]
[2022/04/01 00:25:44] root INFO: loader :
[2022/04/01 00:25:44] root INFO: batch_size_per_card : 64
[2022/04/01 00:25:44] root INFO: drop_last : False
[2022/04/01 00:25:44] root INFO: num_workers : 4
[2022/04/01 00:25:44] root INFO: shuffle : False
[2022/04/01 00:25:44] root INFO: Global :
[2022/04/01 00:25:44] root INFO: cal_metric_during_train : True
[2022/04/01 00:25:44] root INFO: character_dict_path : ppocr/utils/en_dict.txt
[2022/04/01 00:25:44] root INFO: character_type : EN
[2022/04/01 00:25:44] root INFO: checkpoints : rec_number_finetune/best_accuracy
[2022/04/01 00:25:44] root INFO: debug : False
[2022/04/01 00:25:44] root INFO: distributed : False
[2022/04/01 00:25:44] root INFO: epoch_num : 200
[2022/04/01 00:25:44] root INFO: eval_batch_step : [0, 200]
[2022/04/01 00:25:44] root INFO: infer_img : None
[2022/04/01 00:25:44] root INFO: infer_mode : False
[2022/04/01 00:25:44] root INFO: log_smooth_window : 20
[2022/04/01 00:25:44] root INFO: max_text_length : 25
[2022/04/01 00:25:44] root INFO: pretrained_model : rec_number_finetune/best_accuracy
[2022/04/01 00:25:44] root INFO: print_batch_step : 10
[2022/04/01 00:25:44] root INFO: save_epoch_step : 20
[2022/04/01 00:25:44] root INFO: save_inference_dir : None
[2022/04/01 00:25:44] root INFO: save_model_dir : ./output
[2022/04/01 00:25:44] root INFO: use_gpu : True
[2022/04/01 00:25:44] root INFO: use_space_char : False
[2022/04/01 00:25:44] root INFO: use_visualdl : True
[2022/04/01 00:25:44] root INFO: Loss :
[2022/04/01 00:25:44] root INFO: name : CTCLoss
[2022/04/01 00:25:44] root INFO: Metric :
[2022/04/01 00:25:44] root INFO: main_indicator : acc
[2022/04/01 00:25:44] root INFO: name : RecMetric
[2022/04/01 00:25:44] root INFO: Optimizer :
[2022/04/01 00:25:44] root INFO: beta1 : 0.9
[2022/04/01 00:25:44] root INFO: beta2 : 0.999
[2022/04/01 00:25:44] root INFO: lr :
[2022/04/01 00:25:44] root INFO: learning_rate : 0.001
[2022/04/01 00:25:44] root INFO: name : Cosine
[2022/04/01 00:25:44] root INFO: name : Adam
[2022/04/01 00:25:44] root INFO: regularizer :
[2022/04/01 00:25:44] root INFO: factor : 1e-05
[2022/04/01 00:25:44] root INFO: name : L2
[2022/04/01 00:25:44] root INFO: PostProcess :
[2022/04/01 00:25:44] root INFO: name : CTCLabelDecode
[2022/04/01 00:25:44] root INFO: Train :
[2022/04/01 00:25:44] root INFO: dataset :
[2022/04/01 00:25:44] root INFO: data_dir : …/M2021_crop
[2022/04/01 00:25:44] root INFO: label_file_list : [‘…/M2021_crop/rec_gt_train.txt’]
[2022/04/01 00:25:44] root INFO: name : SimpleDataSet
[2022/04/01 00:25:44] root INFO: ratio_list : [1.0]
[2022/04/01 00:25:44] root INFO: transforms :
[2022/04/01 00:25:44] root INFO: DecodeImage :
[2022/04/01 00:25:44] root INFO: channel_first : False
[2022/04/01 00:25:44] root INFO: img_mode : BGR
[2022/04/01 00:25:44] root INFO: RecAug : None
[2022/04/01 00:25:44] root INFO: CTCLabelEncode : None
[2022/04/01 00:25:44] root INFO: RecResizeImg :
[2022/04/01 00:25:44] root INFO: image_shape : [3, 32, 320]
[2022/04/01 00:25:44] root INFO: KeepKeys :
[2022/04/01 00:25:44] root INFO: keep_keys : [‘image’, ‘label’, ‘length’]
[2022/04/01 00:25:44] root INFO: loader :
[2022/04/01 00:25:44] root INFO: batch_size_per_card : 256
[2022/04/01 00:25:44] root INFO: drop_last : True
[2022/04/01 00:25:44] root INFO: num_workers : 8
[2022/04/01 00:25:44] root INFO: shuffle : True
[2022/04/01 00:25:44] root INFO: profiler_options : None
[2022/04/01 00:25:44] root INFO: train with paddle 2.2.2 and device CUDAPlace(0)
[2022/04/01 00:25:44] root INFO: Initialize indexs of datasets:[‘…/M2021_crop/rec_gt_eval-Copy1.txt’]
W0401 00:25:44.958052 21325 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W0401 00:25:44.962754 21325 device_context.cc:465] device: 0, cuDNN Version: 7.6.
[2022/04/01 00:25:49] root INFO: resume from rec_number_finetune/best_accuracy
[2022/04/01 00:25:49] root INFO: metric in ckpt ***************
[2022/04/01 00:25:49] root INFO: acc:0.8148097851247832
[2022/04/01 00:25:49] root INFO: norm_edit_dis:0.9639812515967602
[2022/04/01 00:25:49] root INFO: fps:1779.4862442023993
[2022/04/01 00:25:49] root INFO: best_epoch:173
[2022/04/01 00:25:49] root INFO: start_epoch:174
eval model:: 100%|████████████████████████████████| 1/1 [00:00<00:00, 1.60it/s]
[2022/04/01 00:25:50] root INFO: metric eval ***************
[2022/04/01 00:25:50] root INFO: acc:0.8084934363098658
[2022/04/01 00:25:50] root INFO: norm_edit_dis:0.9269012513411569
[2022/04/01 00:25:50] root INFO: fps:1044.0056984583446
In [92]
!python tools/eval.py -c rec_number_finetune/config.yml -o Global.checkpoints=output/best_accuracy
[2022/04/01 00:24:36] root INFO: Architecture :
[2022/04/01 00:24:36] root INFO: Backbone :
[2022/04/01 00:24:36] root INFO: model_name : small
[2022/04/01 00:24:36] root INFO: name : MobileNetV3
[2022/04/01 00:24:36] root INFO: scale : 0.5
[2022/04/01 00:24:36] root INFO: small_stride : [1, 2, 2, 2]
[2022/04/01 00:24:36] root INFO: Head :
[2022/04/01 00:24:36] root INFO: fc_decay : 1e-05
[2022/04/01 00:24:36] root INFO: name : CTCHead
[2022/04/01 00:24:36] root INFO: Neck :
[2022/04/01 00:24:36] root INFO: encoder_type : rnn
[2022/04/01 00:24:36] root INFO: hidden_size : 48
[2022/04/01 00:24:36] root INFO: name : SequenceEncoder
[2022/04/01 00:24:36] root INFO: Transform : None
[2022/04/01 00:24:36] root INFO: algorithm : CRNN
[2022/04/01 00:24:36] root INFO: model_type : rec
[2022/04/01 00:24:36] root INFO: Eval :
[2022/04/01 00:24:36] root INFO: dataset :
[2022/04/01 00:24:36] root INFO: data_dir : …/M2021_crop
[2022/04/01 00:24:36] root INFO: label_file_list : [‘…/M2021_crop/rec_gt_eval-Copy1.txt’]
[2022/04/01 00:24:36] root INFO: name : SimpleDataSet
[2022/04/01 00:24:36] root INFO: transforms :
[2022/04/01 00:24:36] root INFO: DecodeImage :
[2022/04/01 00:24:36] root INFO: channel_first : False
[2022/04/01 00:24:36] root INFO: img_mode : BGR
[2022/04/01 00:24:36] root INFO: CTCLabelEncode : None
[2022/04/01 00:24:36] root INFO: RecResizeImg :
[2022/04/01 00:24:36] root INFO: image_shape : [3, 32, 320]
[2022/04/01 00:24:36] root INFO: KeepKeys :
[2022/04/01 00:24:36] root INFO: keep_keys : [‘image’, ‘label’, ‘length’]
[2022/04/01 00:24:36] root INFO: loader :
[2022/04/01 00:24:36] root INFO: batch_size_per_card : 64
[2022/04/01 00:24:36] root INFO: drop_last : False
[2022/04/01 00:24:36] root INFO: num_workers : 4
[2022/04/01 00:24:36] root INFO: shuffle : False
[2022/04/01 00:24:36] root INFO: Global :
[2022/04/01 00:24:36] root INFO: cal_metric_during_train : True
[2022/04/01 00:24:36] root INFO: character_dict_path : ppocr/utils/en_dict.txt
[2022/04/01 00:24:36] root INFO: character_type : EN
[2022/04/01 00:24:36] root INFO: checkpoints : output/best_accuracy
[2022/04/01 00:24:36] root INFO: debug : False
[2022/04/01 00:24:36] root INFO: distributed : False
[2022/04/01 00:24:36] root INFO: epoch_num : 200
[2022/04/01 00:24:36] root INFO: eval_batch_step : [0, 200]
[2022/04/01 00:24:36] root INFO: infer_img : None
[2022/04/01 00:24:36] root INFO: infer_mode : False
[2022/04/01 00:24:36] root INFO: log_smooth_window : 20
[2022/04/01 00:24:36] root INFO: max_text_length : 25
[2022/04/01 00:24:36] root INFO: pretrained_model : rec_number_finetune/best_accuracy
[2022/04/01 00:24:36] root INFO: print_batch_step : 10
[2022/04/01 00:24:36] root INFO: save_epoch_step : 20
[2022/04/01 00:24:36] root INFO: save_inference_dir : None
[2022/04/01 00:24:36] root INFO: save_model_dir : ./output
[2022/04/01 00:24:36] root INFO: use_gpu : True
[2022/04/01 00:24:36] root INFO: use_space_char : False
[2022/04/01 00:24:36] root INFO: use_visualdl : True
[2022/04/01 00:24:36] root INFO: Loss :
[2022/04/01 00:24:36] root INFO: name : CTCLoss
[2022/04/01 00:24:36] root INFO: Metric :
[2022/04/01 00:24:36] root INFO: main_indicator : acc
[2022/04/01 00:24:36] root INFO: name : RecMetric
[2022/04/01 00:24:36] root INFO: Optimizer :
[2022/04/01 00:24:36] root INFO: beta1 : 0.9
[2022/04/01 00:24:36] root INFO: beta2 : 0.999
[2022/04/01 00:24:36] root INFO: lr :
[2022/04/01 00:24:36] root INFO: learning_rate : 0.001
[2022/04/01 00:24:36] root INFO: name : Cosine
[2022/04/01 00:24:36] root INFO: name : Adam
[2022/04/01 00:24:36] root INFO: regularizer :
[2022/04/01 00:24:36] root INFO: factor : 1e-05
[2022/04/01 00:24:36] root INFO: name : L2
[2022/04/01 00:24:36] root INFO: PostProcess :
[2022/04/01 00:24:36] root INFO: name : CTCLabelDecode
[2022/04/01 00:24:36] root INFO: Train :
[2022/04/01 00:24:36] root INFO: dataset :
[2022/04/01 00:24:36] root INFO: data_dir : …/M2021_crop
[2022/04/01 00:24:36] root INFO: label_file_list : [‘…/M2021_crop/rec_gt_train.txt’]
[2022/04/01 00:24:36] root INFO: name : SimpleDataSet
[2022/04/01 00:24:36] root INFO: ratio_list : [1.0]
[2022/04/01 00:24:36] root INFO: transforms :
[2022/04/01 00:24:36] root INFO: DecodeImage :
[2022/04/01 00:24:36] root INFO: channel_first : False
[2022/04/01 00:24:36] root INFO: img_mode : BGR
[2022/04/01 00:24:36] root INFO: RecAug : None
[2022/04/01 00:24:36] root INFO: CTCLabelEncode : None
[2022/04/01 00:24:36] root INFO: RecResizeImg :
[2022/04/01 00:24:36] root INFO: image_shape : [3, 32, 320]
[2022/04/01 00:24:36] root INFO: KeepKeys :
[2022/04/01 00:24:36] root INFO: keep_keys : [‘image’, ‘label’, ‘length’]
[2022/04/01 00:24:36] root INFO: loader :
[2022/04/01 00:24:36] root INFO: batch_size_per_card : 256
[2022/04/01 00:24:36] root INFO: drop_last : True
[2022/04/01 00:24:36] root INFO: num_workers : 8
[2022/04/01 00:24:36] root INFO: shuffle : True
[2022/04/01 00:24:36] root INFO: profiler_options : None
[2022/04/01 00:24:36] root INFO: train with paddle 2.2.2 and device CUDAPlace(0)
[2022/04/01 00:24:36] root INFO: Initialize indexs of datasets:[‘…/M2021_crop/rec_gt_eval-Copy1.txt’]
W0401 00:24:36.899550 21194 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W0401 00:24:36.904112 21194 device_context.cc:465] device: 0, cuDNN Version: 7.6.
[2022/04/01 00:24:41] root INFO: resume from output/best_accuracy
[2022/04/01 00:24:41] root INFO: metric in ckpt ***************
[2022/04/01 00:24:41] root INFO: acc:0.9941372769236556
[2022/04/01 00:24:41] root INFO: norm_edit_dis:0.9983229434143235
[2022/04/01 00:24:41] root INFO: fps:780.0351024597015
[2022/04/01 00:24:41] root INFO: best_epoch:7
[2022/04/01 00:24:41] root INFO: start_epoch:8
eval model:: 100%|████████████████████████████████| 1/1 [00:00<00:00, 1.54it/s]
[2022/04/01 00:24:42] root INFO: metric eval ***************
[2022/04/01 00:24:42] root INFO: acc:0.8297695793706518
[2022/04/01 00:24:42] root INFO: norm_edit_dis:0.9373873504211157
[2022/04/01 00:24:42] root INFO: fps:1016.2348659418609
仅仅7个eopch,验证集上准确率就从0.808提高到接近0.830!效果立竿见影有木有!https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

只能说……对于深度学习,数据确实是王道!https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐