基于PP-OCRv3的电表检测识别
本案例将使用OCR技术自动识别电表读数与电表编号,攻克表计图片“识别不到”与“识别不准”的难题,通过本章您可以掌握:
- PaddleOCR快速使用
- 文本检测优化方法
- 文本识别优化方法
- 数据合成方法
- 基于现有数据微调
1 项目背景
我国电力行业发展迅速,电表作为测电设备经历了普通电表、预付费电表和智能电表三个阶段的发展,虽然智能电表具有通信功能,但一方面环境和设备使得智能电表具有不稳定性,另一方面非智能电表仍然无法实现自动采集,人工抄表有时往往不可取代。采集到的大量电表图片如果能够借助人工智能技术批量检测和识别,将会大幅提升效率和精度。
在本系列项目中,我们使用Paddle工具库实现一个OCR垂类场景。原始数据集是一系列电度表的照片,类型较多,需要完成电表的读数识别,对于有编号的电表,还要完成其编号的识别。关于电表检测任务的难点以及基线解决方案,请参考项目PPOCR:多类别电表读数识别的详细说明。
2022年5月,PaddleOCR更新到了release/2.5,并发布了PP-OCRv3,速度可比情况下,中文场景效果相比于PP-OCRv2再提升5%,英文场景提升11%,80语种多语言模型平均识别准确率提升5%以上。
如此重要的升级,电表检测识别模型怎能错过?
因此,我们就在基于PP-OCRv2开发的项目PPOCR:多类别电表读数识别基础上,来测试下升级到PP-OCRv3之后的电表检测模型效果吧!
参考资料——电表检测识别全系列:
(主线篇)
- PPOCR:多类别电表读数识别
- PPOCR:使用TextRender进行电表编号识别的finetune
- 电表读数识别:数据集补充解决方案对比(TextRender和StyleText)
- 数据标注懒人包:PPOCRLabel极速增强版——以电表识别为例(二)
- 【PaddlePaddle+OpenVINO】电表检测识别模型的部署
- 【PaddlePaddle+OpenVINO】打造一个会发声的电表检测识别器
(众人拾柴——应用篇)
(番外篇)
2 安装说明
项目开发环境基于PaddleOCR release/2.5。
# 拉取模型库
!git clone https://gitee.com/paddlepaddle/PaddleOCR.git
# 安装依赖
!pip install -r PaddleOCR/requirements.txt
# text_renderer在项目中已经准备好
# !git clone https://gitee.com/wowowoll/text_renderer.git
!pip install -r text_renderer/requirements.txt
3 数据集简介
本项目示例数据集采用通过PPOCRLabel标注得到263张脱敏电表OCR图片,标注有电表读数和编号。在实际工业场景中,可以通过摄像头采集的方法收集大量真实数据,但是对这些数据逐一进行标注耗时甚长。因此,本例项目中,重点介绍如何利用合成数据,快速实现数据集扩充的方法。
该方法基于TextRender,由于需要与最初训练效果指标进行对比,本文只对电表编号数据进行了扩增。电表读数的扩展方法大同小异,读者可准备好相关字体文件,参考项目电表读数识别:数据集补充解决方案对比(TextRender和StyleText)自行进行电表读数补充数据的生成。
# 解压电表数据集
!unzip data/data117381/M2021.zip
# 准备电表编号补充目录
!mkdir M2021_crop
import os
import json
import cv2
import numpy as np
import math
from PIL import Image, ImageDraw
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
with open('./M2021/Label.txt','r',encoding='utf8')as fp:
s = [i[:-1].split('\t') for i in fp.readlines()]
f1 = open('M2021_crop/rec_gt_train.txt', 'w', encoding='utf-8')
f2 = open('M2021_crop/rec_gt_eval.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)
rotate = Rotate(image, coordinate)
# 把图片放到目录下
crop_path = 'M2021_crop' + path[5:-4:] + '_' + str(j) + '.jpg'
rotate.run().convert('RGB').save(crop_path)
# label文件不写入图片目录
crop_path = path[6:-4:] + '_' + str(j) + '.jpg'
if i[0] % 5 != 0:
f1.writelines(crop_path + '\t' + label + '\n')
else:
f2.writelines(crop_path + '\t' + label + '\n')
f1.close()
f2.close()
with open('./M2021/Label.txt','r',encoding='utf8')as fp:
s = [i[:-1].split('\t') for i in fp.readlines()]
f3 = open('text_renderer/data/list_corpus/books.txt', 'w', encoding='utf-8')
for i in enumerate(s):
anno = json.loads(i[1][1])
for j in range(len(anno)):
label = anno[j-1]['transcription']
if len(label) > 8:
print(label)
f3.writelines(label + '\n')
f3.close()
%cd text_renderer/
/home/aistudio/text_renderer
# 作为示例,这里只生成5000张图片
!python main.py --num_img=5000 --chars_file='./data/chars/eng.txt' \
--fonts_list='./data/fonts_list/eng.txt' --corpus_dir "data/list_corpus" \
--corpus_mode "list"
Total fonts num: 5
Background num: 1
Loading corpus from: data/list_corpus
Load corpus: data/list_corpus/books.txt
Total lines: 229
Generate text images in ./output/default
5000/5000 100%
Finish generate data: 59.740 s
# 生成PaddleOCR文字识别数据集格式文件
with open('./output/default/tmp_labels.txt','r') as f:
s = [i[:-1].split(' ') for i in f.readlines()]
f4 = open('./render_data.txt', 'w', encoding='utf-8')
for i in enumerate(s):
path = i[1][0] + '.jpg'
f4.writelines(path + '\t' + i[1][1] + '\n')
f4.close()
# 将生成的标签文件和图片与裁剪的电表编号数据集合并
!mv ./render_data.txt ../M2021_crop/
!mv output/default/*.jpg ../M2021_crop/
4 模型介绍
PP-OCRv3在PP-OCRv2的基础上进一步升级。整体的框架图保持了与PP-OCRv2相同的pipeline,针对检测模型和识别模型进行了优化。其中,检测模块仍基于DB算法优化,而识别模块不再采用CRNN,换成了IJCAI 2022最新收录的文本识别算法SVTR,并对其进行产业适配。PP-OCRv3系统框图如下所示(粉色框中为PP-OCRv3新增策略):
从算法改进思路上看,分别针对检测和识别模型,进行了共9个方面的改进:
-
检测模块:
- LK-PAN:大感受野的PAN结构;
- DML:教师模型互学习策略;
- RSE-FPN:残差注意力机制的FPN结构;
-
识别模块:
- SVTR_LCNet:轻量级文本识别网络;
- GTC:Attention指导CTC训练策略;
- TextConAug:挖掘文字上下文信息的数据增广策略;
- TextRotNet:自监督的预训练模型;
- UDML:联合互学习策略;
- UIM:无标注数据挖掘方案。
%cd ../PaddleOCR/
/home/aistudio/PaddleOCR
4.1 检测模型优化
PP-OCRv3检测模型是对PP-OCRv2中的CML(Collaborative Mutual Learning) 协同互学习文本检测蒸馏策略进行了升级。CML的核心思想结合了①传统的Teacher指导Student的标准蒸馏与 ②Students网络之间的DML互学习,可以让Students网络互学习的同时,Teacher网络予以指导。
PP-OCRv3分别针对教师模型和学生模型进行进一步效果优化。其中,在对教师模型优化时,提出了大感受野的PAN结构LK-PAN和引入了DML(Deep Mutual Learning)蒸馏策略;在对学生模型优化时,提出了残差注意力机制的FPN结构RSE-FPN。
(1)LK-PAN:大感受野的PAN结构
LK-PAN (Large Kernel PAN) 是一个具有更大感受野的轻量级PAN结构,核心是将PAN结构的path augmentation中卷积核从3*3
改为9*9
。通过增大卷积核,提升特征图每个位置覆盖的感受野,更容易检测大字体的文字以及极端长宽比的文字。使用LK-PAN结构,可以将教师模型的hmean从83.2%提升到85.0%。
(2)DML:教师模型互学习策略
DML (Deep Mutual Learning)互学习蒸馏方法,如下图所示,通过两个结构相同的模型互相学习,可以有效提升文本检测模型的精度。教师模型采用DML策略,hmean从85%提升到86%。将PP-OCRv2中CML的教师模型更新为上述更高精度的教师模型,学生模型的hmean可以进一步从83.2%提升到84.3%。
(3)RSE-FPN:残差注意力机制的FPN结构
RSE-FPN(Residual Squeeze-and-Excitation FPN)如下图所示,引入残差结构和通道注意力结构,将FPN中的卷积层更换为通道注意力结构的RSEConv层,进一步提升特征图的表征能力。考虑到PP-OCRv2的检测模型中FPN通道数非常小,仅为96,如果直接用SEblock代替FPN中卷积会导致某些通道的特征被抑制,精度会下降。RSEConv引入残差结构会缓解上述问题,提升文本检测效果。进一步将PP-OCRv2中CML的学生模型的FPN结构更新为RSE-FPN,学生模型的hmean可以进一步从84.3%提升到85.4%。
4.2 识别模型优化
PP-OCRv3的识别模块是基于文本识别算法SVTR优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。直接将PP-OCRv2的识别模型,替换成SVTR_Tiny,识别准确率从74.8%提升到80.1%(+5.3%),但是预测速度慢了将近11倍,CPU上预测一条文本行,将近100ms。因此,如下图所示,PP-OCRv3采用如下6个优化策略进行识别模型加速。
(1)SVTR_LCNet:轻量级文本识别网络
SVTR_LCNet是针对文本识别任务,将基于Transformer的SVTR网络和轻量级CNN网络PP-LCNet 融合的一种轻量级文本识别网络。使用该网络,预测速度优于PP-OCRv2的识别模型20%,但是由于没有采用蒸馏策略,该识别模型效果略差。此外,进一步将输入图片规范化高度从32提升到48,预测速度稍微变慢,但是模型效果大幅提升,识别准确率达到73.98%(+2.08%),接近PP-OCRv2采用蒸馏策略的识别模型效果。
(2)GTC:Attention指导CTC训练策略
GTC(Guided Training of CTC),利用Attention模块CTC训练,融合多种文本特征的表达,是一种有效的提升文本识别的策略。使用该策略,预测时完全去除 Attention 模块,在推理阶段不增加任何耗时,识别模型的准确率进一步提升到75.8%(+1.82%)。训练流程如下所示:
(3)TextConAug:挖掘文字上下文信息的数据增广策略
TextConAug是一种挖掘文字上下文信息的数据增广策略,主要思想来源于论文ConCLR,作者提出ConAug数据增广,在一个batch内对2张不同的图像进行联结,组成新的图像并进行自监督对比学习。PP-OCRv3将此方法应用到有监督的学习任务中,设计了TextConAug数据增强方法,可以丰富训练数据上下文信息,提升训练数据多样性。使用该策略,识别模型的准确率进一步提升到76.3%(+0.5%)。TextConAug示意图如下所示:
(4)TextRotNet:自监督的预训练模型
TextRotNet是使用大量无标注的文本行数据,通过自监督方式训练的预训练模型,参考于论文STR-Fewer-Labels。该模型可以初始化SVTR_LCNet的初始权重,从而帮助文本识别模型收敛到更佳位置。使用该策略,识别模型的准确率进一步提升到76.9%(+0.6%)。TextRotNet训练流程如下图所示:
(5)UDML:联合互学习策略
UDML(Unified-Deep Mutual Learning)联合互学习是PP-OCRv2中就采用的对于文本识别非常有效的提升模型效果的策略。在PP-OCRv3中,针对两个不同的SVTR_LCNet和Attention结构,对他们之间的PP-LCNet的特征图、SVTR模块的输出和Attention模块的输出同时进行监督训练。使用该策略,识别模型的准确率进一步提升到78.4%(+1.5%)。
(6)UIM:无标注数据挖掘方案
UIM(Unlabeled Images Mining)是一种非常简单的无标注数据挖掘方案。核心思想是利用高精度的文本识别大模型对无标注数据进行预测,获取伪标签,并且选择预测置信度高的样本作为训练数据,用于训练小模型。使用该策略,识别模型的准确率进一步提升到79.4%(+1%)。
5 模型训练
5.1 检测模型训练
由于电表读数、电表编号所在区域,相比于整个电表,面积占比并不高。因此,在检测模型训练时,一个核心的优化点是要调大输入检测模型的图片size。
检测模块训练配置文件关键设置如下:
Train:
dataset:
name: SimpleDataSet
data_dir: ../
label_file_list:
- ../train_list.txt
……
- EastRandomCropData:
size:
- 1600
- 1600
max_tries: 50
keep_ratio: true
……
loader:
shuffle: true
drop_last: false
batch_size_per_card: 6
use_shared_memory: False
num_workers: 8
Eval:
dataset:
name: SimpleDataSet
data_dir: ../
label_file_list:
- ../val_list.txt
……
loader:
shuffle: false
drop_last: false
batch_size_per_card: 1
use_shared_memory: False
num_workers: 2
注意:在AIStudio训练,一定要注意几个重点!
- 用至尊版!因为原图分辨率太大,目标框相对其实很小,所以输入模型的size太小训练效果不好,而size设大自然需要更多显存
use_shared_memory
设置为False
batch_size_per_card
不能设置太大,因为输入size比较大
# 下载预训练模型
!wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar
!tar -xvf ch_PP-OCRv3_det_distill_train.tar
# 训练检测模型
!python tools/train.py -c ../dianbiao/ch_PP-OCRv3_det_student.yml
最终,在PP-OCRv3下,电表编号和读数检测模型可以达到的参考效果如下:
2022/05/08 08:50:36] ppocr INFO: epoch: [400/400], global_step: 3600, lr: 0.000000, loss: 1.110980, loss_shrink_maps: 0.546560, loss_threshold_maps: 0.420974, loss_binary_maps: 0.109129, avg_reader_cost: 2.31141 s, avg_batch_cost: 2.73862 s, avg_samples: 5.3, ips: 1.93528 samples/s, eta: 0:00:00
[2022/05/08 08:51:09] ppocr INFO: cur metric, precision: 0.9157894736842105, recall: 0.925531914893617, hmean: 0.9206349206349206, fps: 4.671872943533558
[2022/05/08 08:51:09] ppocr INFO: save best model is to ./output/det_ppocrv3/best_accuracy
[2022/05/08 08:51:09] ppocr INFO: best metric, hmean: 0.9206349206349206, precision: 0.9157894736842105, recall: 0.925531914893617, fps: 4.671872943533558, best_epoch: 400
[2022/05/08 08:51:10] ppocr INFO: save model in ./output/det_ppocrv3/latest
[2022/05/08 08:51:10] ppocr INFO: save model in ./output/det_ppocrv3/iter_epoch_400
[2022/05/08 08:51:10] ppocr INFO: best metric, hmean: 0.9206349206349206, precision: 0.9157894736842105, recall: 0.925531914893617, fps: 4.671872943533558, best_epoch: 400
5.2 识别模型训练
识别模块训练配置文件关键设置如下:
Train:
dataset:
name: SimpleDataSet
data_dir: ../M2021_crop/
ext_op_transform_idx: 1
label_file_list:
- ../M2021_crop/rec_gt_train.txt
- ../M2021_crop/render_data.txt
ratio_list: [1.0, 1.0]
……
loader:
shuffle: true
batch_size_per_card: 128
drop_last: true
use_shared_memory: False
num_workers: 8
Eval:
dataset:
name: SimpleDataSet
data_dir: ../M2021_crop/
label_file_list:
- ../M2021_crop/rec_gt_eval.txt
……
loader:
shuffle: false
drop_last: false
batch_size_per_card: 128
use_shared_memory: False
num_workers: 8
!wget https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_train.tar
!tar -xvf en_PP-OCRv3_rec_train.tar
!python tools/train.py -c ../dianbiao/rec_en_ppocrv3.yml
识别模型参考训练效果如下:
[2022/05/08 12:37:19] ppocr INFO: epoch: [199/200], global_step: 4160, lr: 0.000001, CTCLoss: 0.046551, SARLoss: 0.233911, loss: 0.279258, avg_reader_cost: 0.42351 s, avg_batch_cost: 0.46051 s, avg_samples: 25.6, ips: 55.59113 samples/s, eta: 0:00:28
[2022/05/08 12:37:22] ppocr INFO: epoch: [199/200], global_step: 4170, lr: 0.000001, CTCLoss: 0.051888, SARLoss: 0.240020, loss: 0.290269, avg_reader_cost: 0.00250 s, avg_batch_cost: 0.18425 s, avg_samples: 128.0, ips: 694.69022 samples/s, eta: 0:00:21
[2022/05/08 12:37:24] ppocr INFO: epoch: [199/200], global_step: 4179, lr: 0.000001, CTCLoss: 0.042004, SARLoss: 0.233527, loss: 0.281940, avg_reader_cost: 0.00015 s, avg_batch_cost: 0.16175 s, avg_samples: 115.2, ips: 712.19180 samples/s, eta: 0:00:15
[2022/05/08 12:37:25] ppocr INFO: save model in ./output/rec_en_ppocrv3/latest
[2022/05/08 12:37:29] ppocr INFO: epoch: [200/200], global_step: 4180, lr: 0.000001, CTCLoss: 0.047450, SARLoss: 0.232198, loss: 0.283179, avg_reader_cost: 0.41280 s, avg_batch_cost: 0.43282 s, avg_samples: 12.8, ips: 29.57376 samples/s, eta: 0:00:14
[2022/05/08 12:37:32] ppocr INFO: epoch: [200/200], global_step: 4190, lr: 0.000000, CTCLoss: 0.054212, SARLoss: 0.227913, loss: 0.292011, avg_reader_cost: 0.06983 s, avg_batch_cost: 0.25463 s, avg_samples: 128.0, ips: 502.69504 samples/s, eta: 0:00:07
[2022/05/08 12:37:36] ppocr INFO: epoch: [200/200], global_step: 4200, lr: 0.000000, CTCLoss: 0.054718, SARLoss: 0.230516, loss: 0.291420, avg_reader_cost: 0.04034 s, avg_batch_cost: 0.22075 s, avg_samples: 128.0, ips: 579.85354 samples/s, eta: 0:00:00
[2022/05/08 12:37:36] ppocr INFO: save model in ./output/rec_en_ppocrv3/latest
[2022/05/08 12:37:36] ppocr INFO: best metric, acc: 0.8645832432725788, norm_edit_dis: 0.9656151899000623, fps: 1557.634472327207, best_epoch: 72
6 模型评估
通过上述代码训练好模型后,运行 tools/eval.py
, 指定配置文件和模型参数即可评估效果。
实验结果如下表所示:
检测模型 | 实验 | 指标 |
---|---|---|
PP-OCRv2 | 仅调整lr | H-Means=0.3 |
PP-OCRv2 | 调整lr+增大输入分辨率 | H-Means=0.65 |
PP-OCRv2 | 调整lr+增大输入分辨率+CopyPaste | H-Means=0.85 |
PP-OCRv3 | 调整lr+增大输入分辨率+CopyPaste | H-Means=0.97 |
识别模型 | 实验 | 指标 |
---|---|---|
PP-OCRv2 | 电表编号TextRender扩增+5000张 | Acc=0.81 |
PP-OCRv3 | 电表编号TextRender扩增+5000张 | Acc=0.86 |
!python tools/eval.py -c ../dianbiao/ch_PP-OCRv3_det_student.yml
[2022/07/04 01:58:59] ppocr INFO: train with paddle 2.1.2 and device CUDAPlace(0)
[2022/07/04 01:58:59] ppocr INFO: Initialize indexs of datasets:['../val_list.txt']
W0704 01:58:59.245405 27927 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0704 01:58:59.249910 27927 device_context.cc:422] device: 0, cuDNN Version: 7.6.
[2022/07/04 01:59:02] ppocr INFO: load pretrain successful from ../dianbiao/train_log_model/det_ppocrv3_1600/best_accuracy
eval model:: 100%|██████████████████████████████| 50/50 [00:14<00:00, 3.38it/s]
[2022/07/04 01:59:17] ppocr INFO: metric eval ***************
[2022/07/04 01:59:17] ppocr INFO: precision:0.9805825242718447
[2022/07/04 01:59:17] ppocr INFO: recall:0.9711538461538461
[2022/07/04 01:59:17] ppocr INFO: hmean:0.9758454106280192
[2022/07/04 01:59:17] ppocr INFO: fps:7.078776890862657
!python tools/eval.py -c ../dianbiao/rec_en_ppocrv3.yml
[2022/07/04 01:55:47] ppocr INFO: train with paddle 2.1.2 and device CUDAPlace(0)
[2022/07/04 01:55:47] ppocr INFO: Initialize indexs of datasets:['../M2021_crop/rec_gt_eval.txt']
W0704 01:55:47.620630 27339 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0704 01:55:47.625003 27339 device_context.cc:422] device: 0, cuDNN Version: 7.6.
[2022/07/04 01:55:50] ppocr INFO: load pretrain successful from ../dianbiao/rec_en_ppocrv3/best_accuracy
eval model:: 100%|████████████████████████████████| 1/1 [00:01<00:00, 1.34s/it]
[2022/07/04 01:55:52] ppocr INFO: metric eval ***************
[2022/07/04 01:55:52] ppocr INFO: acc:0.8645832432725788
如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料

将下载或训练完成的模型放置在对应目录下即可完成模型推理
关于后续的模型导出和串接,系列项目前面已经介绍过了,本文就不再赘述。
7 总结
在电表检测识别任务上,PP-OCRv3效果实现了大幅提升,可以预见的是,在增加电表原始图片+生成补充数据集后,基于PP-OCRv3的电表检测识别模型,将基本具备直接落地的能力。
开源链接:https://aistudio.baidu.com/aistudio/projectdetail/511591