OCR升级版 — 微调EasyOCR实战

OCR是从图像中提取文本的有价值工具。然而,有时您使用的OCR在特定需求上的表现不如您所希望的那样好。如果您面临这样的问题,微调OCR引擎是解决的一种方法。在本教程中,我将向您展示如何微调EasyOCR,这是一个免费、开源的OCR引擎,您可以在Python中使用。

概述

  • 先决条件

  • 安装所需的软件包

  • 克隆所需的Git存储库

  • 生成数据集

  • 将数据集转换为lmdb格式

  • 检索预训练的OCR模型:

  • 运行微调

  • 使用微调后的模型运行推理

  • 性能的定性测试

  • 性能的定量测试

  • 结论

先决条件

  • 基本的Python知识

  • 如何使用终端的基本知识

安装所需的软件包

首先,让我们安装所需的pip软件包。我建议为此创建一个虚拟环境,尽管这不是必需的。逐行运行以下命令:

pip install fire
pip install lmdb
pip install opencv-python
pip install natsort
pip install nltk

您还需要从此网站安装PyTorch(选择您的规格并复制pip install命令,查看下面我用于我的规格的命令)。最好选择GPU版本,但CPU版本也可以正常工作,唯一的区别是在CPU上运行微调会更慢。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

克隆所需的Git存储库

首先,您需要一个Git存储库,它将帮助您运行微调。使用以下命令克隆此Git存储库:

git clone https://github.com/clovaai/deep-text-recognition-benchmark

该存储库将为我们提供一些在微调EasyOCR模型时使用的有用文件。请注意,本文中使用的许多终端命令都来自该存储库,然后根据我的需求进行了调整,因此建议阅读该存储库。

我想在这里补充一下,clovaai在Git上总体上有许多对我非常有帮助的好存储库,所以请随时查看他们拥有的其他有趣的存储库。他们还有另一个非常有趣的存储库,即Donut模型存储库,我还写了一篇关于微调Donut模型的文章,您也应该查看一下。

生成数据集

在您可以微调OCR之前,您必须有一个要微调的数据集。您可以下载数据集或自己制作一个。由于我希望我的OCR在扫描超市收据时特别好,我将创建一个包含您可以在超市找到的物品的数据集,但请随时根据您需要OCR在其上执行良好的数据制作数据集。在本章中,我使用此GitHub页面来帮助我。

最简单的方法,使用我的虚拟数据集:

下载数据集

如果您想要另一个更大的数据集,可以从Dropbox官网上下载数据_lmdb_release.zip文件(请注意,其大小略大于18GB)。下载链接:https://drive.google.com/drive/folders/15WPsuPJDCzhp2SvYZLRj8mAlT3zmoAMW

制作您自己的数据集

如果您想采用更酷的方法创建自己的数据集,可以按照这个“生成OCR微调数据集”的教程进行操作。教程链接:https://medium.com/dev-genius/generating-a-fine-tuning-dataset-for-an-ocr-engine-3509167bc8a1

将数据集转换为lmdb格式

Lmdb代表Lightning Memory-Mapped Database Manager,本质上是您可以用于训练AI模型的数据集的编码。您可以在lmdb文档中了解更多信息。制作了数据集之后,您应该有一个包含图像的文件夹,并且所有图像的标签(图像中的文本)在labels.txt文件中。您的文件夹应如下图所示,并且此文件夹应位于deep-text-recognition文件夹内:


42e325624e0245bfa25c348bfc71824d.jpeg

文件夹在转换为lmdb格式之前

注意:确保文件夹中至少有10张图像,因为如果图像太少,运行后面教程中的训练脚本时可能会出现错误。

然后,您必须在deep-text-recognition-benchmark文件夹中的create_lmdb_dataset.py文件中进行一些更改:

  • 由于我遇到了磁盘内存错误,因此我不得不将map_size变量设置得较低。我将map_size的值设置为1073741824,并且您可以看到我更改的行如下所示:

# OLD LINE
# ...
env = lmdb.open(outputPath, map_size=1099511627776)
# ...# NEW LINE 
# ...
env = lmdb.open(outputPath, map_size=1073741824) 
# ...
  • 当打开gtFile时,我还遇到了utf编码错误,因此我在删除utf-8编码时。然后,新行看起来像这样:

# OLD LINE
# ...
with open(gtFile, 'r', encoding='utf-8') as data:
# ...# NEW LINE
# ...
with open(gtFile, 'r') as data:
# ...
  • 最后,我还必须更改读取imagePath的方式:

# OLD LINE
# ...
imagePath, label = datalist[i].strip('\n').split('\t')
# ...# NEW LINES
# ...
imagePath, label = datalist[i].strip('\n').split('.png')
imagePath += '.png'
# ...

我的完整create_lmdb_dataset.py文件看起来像这样(来自这个Git存储库,应用了上述更改)。

import fire
import os
import lmdb
import cv2import numpy as npdef checkImageIsValid(imageBin):if imageBin is None:return FalseimageBuf = np.frombuffer(imageBin, dtype=np.uint8)img = cv2.imdecode(imageBuf, cv2.IMREAD_GRAYSCALE)imgH, imgW = img.shape[0], img.shape[1]if imgH * imgW == 0:return Falsereturn Truedef writeCache(env, cache):with env.begin(write=True) as txn:for k, v in cache.items():txn.put(k, v)def createDataset(inputPath, gtFile, outputPath, checkValid=True):"""Create LMDB dataset for training and evaluation.ARGS:inputPath  : input folder path where starts imagePathoutputPath : LMDB output pathgtFile     : list of image path and labelcheckValid : if true, check the validity of every image"""os.makedirs(outputPath, exist_ok=True)env = lmdb.open(outputPath, map_size=1073741824) #TODO Changed map sizecache = {}cnt = 1with open(gtFile, 'r') as data: #TODO removed utf-8 encoding here since I have norwegian lettersdatalist = data.readlines()nSamples = len(datalist)print(nSamples)for i in range(nSamples):#TODO changed the way imagePath is found as well to match my usecaseimagePath, label = datalist[i].strip('\n').split('.png')imagePath += '.png'# imagePath, label = datalist[i].strip('\n').split('\t')imagePath = os.path.join(inputPath, imagePath)# # only use alphanumeric data# if re.search('[^a-zA-Z0-9]', label):#     continueif not os.path.exists(imagePath):print('%s does not exist' % imagePath)continuewith open(imagePath, 'rb') as f:imageBin = f.read()if checkValid:try:if not checkImageIsValid(imageBin):print('%s is not a valid image' % imagePath)continueexcept:print('error occured', i)with open(outputPath + '/error_image_log.txt', 'a') as log:log.write('%s-th image data occured error\n' % str(i))continueimageKey = 'image-%09d'.encode() % cntlabelKey = 'label-%09d'.encode() % cntcache[imageKey] = imageBincache[labelKey] = label.encode()if cnt % 1000 == 0:writeCache(env, cache)cache = {}print('Written %d / %d' % (cnt, nSamples))cnt += 1nSamples = cnt-1cache['num-samples'.encode()] = str(nSamples).encode()writeCache(env, cache)print('Created dataset with %d samples' % nSamples)if __name__ == '__main__':fire.Fire(createDataset)

在拥有正确的数据和正确的create_lmbd_dataset.py文件后,将文件夹移到deep-text-recognition-benchmark文件夹(您克隆的Git存储库)中。然后运行以下命令:

python .\create_lmdb_dataset.py <data folder name> <path to labels.txt in data folder> <output folder for your lmdb dataset>

其中:

  • <data folder name> 是包含图像和labels.txt的文件夹名称(在我的情况下是output)。

  •  <path to labels.txt>是.\output\labels.txt。

  • <output folder for your lmdb dataset>是将用于保存转换为lmdb格式的数据集的文件夹的名称(我称之为.\lmbd_output)。

对于我来说,上面的命令是这样的(确保在deep-text-recognition-benchmark文件夹中运行此命令):

python .\create_lmdb_dataset.py .\output .\output\labels.txt .\lmbd_output

现在,您应该在deep-text-recognition-benchmark文件夹中有一个新文件夹,类似于下图。

3000afef893cd4fefaff9a9663a5971f.jpeg

文件夹转换为lmdb格式的数据

注意:在现有文件夹上运行命令不会覆盖现有文件夹。因此,请确保要么删除文件夹,要么为lmdb_output指定新名称(这是我挣扎了一段时间的事情,希望这个警告能确保您避免那个错误)。

检索预训练的OCR模型:

现在,您需要一个可以用您的数据集进行微调的预训练OCR模型。为此,您可以访问此Dropbox网站(https://drive.google.com/drive/folders/15WPsuPJDCzhp2SvYZLRj8mAlT3zmoAMW)并下载TPS-ResNet-BiLSTM-Attn.pth模型,然后将其放置在deep-text-recognition-benchmark文件夹中(我知道这看起来有点可疑,但这是deep-text-recognition-benchmark存储库告诉您如何做的方式。Dropbox不是我的,我在这里提供链接是因为在Git存储库text-recognition-benchmark中链接到了它)。

运行微调:

首先,如果您使用CPU(如果您使用GPU可以忽略此步骤),请注意。如果在CPU上运行,您可能会遇到错误,提示RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False。可以通过更改train.py文件中的第85和87行来修复此错误:

# OLD LINES
# ...
if opt.FT:model.load_state_dict(torch.load(opt.saved_model), strict=False)
else:model.load_state_dict(torch.load(opt.saved_model))
# ...# NEW LINES (change to this if you are using CPU)
#
if opt.FT:model.load_state_dict(torch.load(opt.saved_model,map_location='cpu'), strict=False)
else:model.load_state_dict(torch.load(opt.saved_model,map_location='cpu'))
# ...

您还应该注意,如果数据中包含非字母数字字符,OCR 将不会运行微调。这意味着字符 A-Z 和 0-9,您可以在 Python 中使用以下行将所有非字母数字字符替换为空字符串:

new_word = re.sub("[^a-zA-Z]+", "", word)

如果您要创建自己的数据集,这一点尤其重要,但如果您使用我在 Google Drive 中提供的数据集,则不必担心这一点,尽管在使用 OCR 时要注意这一点很重要。

最后,您可以运行微调。要运行微调,可以使用以下命令:

python train.py --train_data lmdb_output --valid_data lmdb_output --select_data "/" --batch_ratio 1.0 --Transformation TPS --FeatureExtraction ResNet --SequenceModeling BiLSTM --Prediction Attn --batch_size 2 --data_filtering_off --workers 0 --batch_max_length 80 --num_iter 10 --valInterval 5 --saved_model TPS-ResNet-BiLSTM-Attn.pth

对命令的一些注释:

  • data_filtering_off 设置为True(只需使用该标志,无需给它变量)。我必须不使用数据过滤,因为启用过滤会导致无法训练样本。

  • workers 必须设置为0以避免错误。我认为这与多GPU设置有关,这也在deep-text-recognition-benchmark文件夹中的train.py文件中有提到。

  • batch_max_length 是训练数据集中任何文本的最大长度。如果使用不同的数据集,请随意更改此变量,但确保该变量至少与数据集中最长字符串的长度一样大,否则将收到错误。

  • 对于本教程,我使用train_data和valid_data引用相同的文件夹。在实践中,我会创建一个包含训练数据集的文件夹,一个包含验证数据集的文件夹,并引用它们。

  • 我将num_iter设置为10,以确保它可以工作。当进行实际模型微调时,自然必须将此变量设置得更高。

  • saved_model 是一个可选参数,但如果不设置它,将训练一个从头开始的模型。您可能不希望这样做(因为这将需要大量训练),因此将saved_model标志设置为从Dropbox下载的现有模型。

使用微调后的模型运行推理:

在微调模型后,您希望对其进行推理。为此,您可以使用以下命令:

python demo.py --Transformation TPS --FeatureExtraction ResNet --SequenceModeling BiLSTM --Prediction Attn --image_folder--saved_model

其中:

<path to images to test on>是包含要测试的PNG图像的文件夹。对我来说,这是: output

<path to model to use>是您微调的模型的保存路径。对我来说,这是: .\saved_models\TPS-ResNet-BiLSTM-Attn-Seed1111\best_accuracy.pth(微调会将微调的模型保存在saved_models文件夹中)

我使用的命令是:

python demo.py --Transformation TPS --FeatureExtraction ResNet --SequenceModeling BiLSTM --Prediction Attn --image_folder output --saved_model .\saved_models\TPS-ResNet-BiLSTM-Attn-Seed1111\best_accuracy.pth

我用于base EasyOCR 模型的命令是:

python demo.py --Transformation None --FeatureExtraction VGG --SequenceModeling BiLSTM --Prediction CTC --image_folder output --saved_model .\saved_models\None-VGG-BiLSTM-CTC-Seed1111\best_accuracy.pth

该命令简单地输出模型对<要测试的图像路径>文件夹中的每个图像的预测和置信度分数,因此您可以通过自己查看图像并查看模型是否正确预测来检查模型的性能。这是模型性能的定性测试。

性能的定性测试:

为了查看微调是否起作用,我将对原始模型与我的微调模型在10个特定单词和数字上进行性能的定性测试。我测试的单词如下所示(垂直合并到一个图像中)。我通过添加倾斜和模糊使模型变得有些困难。

b65c92a7e474d5060e44bead46203df3.jpeg

自制图像与 https://products.aspose.app/pdf/merger/png-to-png 合并。从上到下的单词是: “vanskeligheter”, “uvanligheter”, “skrekkeksempel”, “rosenborg”

考虑到我希望我的OCR能够读取挪威超市收据,我在这里放了一些挪威单词(这些单词来自http://openfoodfacts.com/,您可以在这篇文章中了解更多信息)。希望我的微调模型在这些单词上表现更好,因为原始OCR模型不习惯看到挪威单词,而我的微调模型已经在一些挪威单词上进行了训练。

每个图像中的文本是:

  • image0 -> vanskeligheter

  • image1 -> uvanligheter

  • image2 -> skrekkeksempel

  • image3 -> rosenborg

原始模型(未微调)的结果:

41dd0790bb1b9b07a918cb6b360770a9.jpeg

在定性测试中原始模型(未微调)的结果

微调模型的结果:

a96af4e864856cb5d0bb166c974e875c.jpeg

在定性测试中微调模型的结果

正如您所看到的,微调已经起作用,微调的模型在这个定性示例中取得了完美的结果。

性能的定量测试:

如果您想要进行更多定量测试,可以查看在微调期间显示的验证结果,或者您可以使用以下命令:

python test.py --eval_data--Transformation TPS --FeatureExtraction ResNet --SequenceModeling BiLSTM --Prediction Attn --saved_model--batch_max_length 70 --workers 0 --batch_size 2 --data_filtering_off

其中:

  • <path to test data set in lmdb format>是包含lmdb格式测试数据的文件夹路径,即 lmdb_norwegian_data_test

  • <path to model to test>是要测试性能的模型的路径,即 saved_models/TPS-ResNet-BiLSTM-Attn-Seed1111/best_accuracy.pth

因此,我使用的命令是:

python test.py --eval_data lmdb_norwegian_data_test --Transformation TPS --FeatureExtraction ResNet --SequenceModeling BiLSTM --Prediction Attn --saved_model saved_models/TPS-ResNet-BiLSTM-Attn-Seed1111/best_accuracy.pth --batch_max_length 70 --workers 0 --batch_size 2 --data_filtering_off

这将输出以百分比表示的准确性,即在测试数据集上OCR模型实现的准确性。在我的经验中,从Dropbox下载的模型需要一些训练。一开始,模型会做出完全没有意义的预测,但如果让它训练30分钟左右,您应该会看到一些改进。然后,我对上面显示的4个图像运行了test.py,并获得了以下结果,左边是旧模型(未微调),右边是新微调模型。您可以看到新的微调模型表现得更好。

0da6152651b9ce26055f0bdf30b1cb05.jpeg

旧模型(左边)实现了50%的准确性,新微调模型(右边)实现了100%的准确性

结论

您现在可以对光学字符识别(OCR)模型进行微调了。要对更大的模型产生显著影响并使其具有更好的泛化能力,您可能需要创建一个更大的数据集,您可以在本教程中了解相关信息,然后让模型进行一段时间的训练。最终,期望OCR模型在您的特定用例中表现更好。

·  END  ·

HAPPY LIFE

f7253ee21ccf6a5dbb9050299c656408.png

本文仅供学习交流使用,如有侵权请联系作者删除

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang-Map有序输出——使用orderedmap库实现

前言 工作中遇到一个问题&#xff1a;需要导出一个MySQL表格&#xff0c;表格内容由sql查询得来。但现在发现&#xff0c;所导出的表格中&#xff0c;各列的顺序不确定。多次导出&#xff0c; 每一次的序列顺序也是不定的。 因此确定是后端&#xff0c;Map使用相关导致的问题。…

安卓动态链接库文件体积优化探索实践

背景介绍 应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面&#xff0c;据Google Play统计&#xff0c;应用体积每增加6MB&#xff0c;安装的转化率将下降1%。 安装包的体积受诸多方面影响&#xff0c;针对dex、资源文件、so文件都有不同的优化策略&…

Python脚本之操作Elasticsearch【一】

本文为博主原创&#xff0c;未经授权&#xff0c;严禁转载及使用。 本文链接&#xff1a;https://blog.csdn.net/zyooooxie/article/details/109588072 前面刚写了 requests发请求 操作Elasticsearch - Search https://blog.csdn.net/zyooooxie/article/details/123730279&…

关于RabbitMQ面试题汇总

什么是消息队列&#xff1f;消息队列有什么用&#xff1f; 消息队列是一种在应用程序之间传递消息的通信机制。它是一种典型的生产者-消费者模型&#xff0c;其中生产者负责生成消息并将其发送到队列中&#xff0c;而消费者则从队列中获取消息并进行处理。消息队列的主要目的是…

束集搜索(Beam search)

在seq2seq任务重&#xff0c;传统的获取decoder输出的结果过程中&#xff0c;在每一个时间步上&#xff0c;我们只选择概率最大的那个词&#xff0c;作为当前时间步的输出&#xff0c;即在每一个时间步上我们取到的都是最大概率的词。等到解码器获取到 <EOS> 词元结束循环…

环境配置:Ubuntu18.04 ROS Melodic安装

前言 不同版本的Ubuntu与ROS存在对应关系。 ROS作为目前最受欢迎的机器人操作系统&#xff0c;其核心代码采用C编写&#xff0c;并以BSD许可发布。ROS起源于2007年&#xff0c;是由斯坦福大学与机器人技术公司Willow Garage合作的Switchyard项目。2012年&#xff0c;ROS团队从…

MySQL事务原理的分析

1.事务 并发连接下考虑事务。 事务的本质是并发控制的单元&#xff0c;是用户定义的一个操作序列。这些操作要么都做&#xff0c;要么都不做&#xff0c;是一个不可分割的工作单位。 事务控制语句 ACID特性 原子性&#xff1a;要么都做&#xff0c;要走么都不做。在事务执…

计算机毕业设计 | SSM 医药信息管理系统(附源码)

1&#xff0c; 概述 1.1 课题背景 本系统由说书客面向广大民营药店、县区级医院、个体诊所等群体的药品和客户等信息的管理需求&#xff0c;采用SpringSpringMVCMybatisEasyui架构实现&#xff0c;为单体药店、批发企业、零售连锁企业&#xff0c;提供有针对性的信息数据管理…

第十四篇【传奇开心果系列】Python的OpenCV库技术点案例示例:图像特征提取与描述

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文目录前言一、OpenCV图像特征提取与描述介绍二、OpenCV图像特征提取与描述初步示例代码三、扩展思路介绍四、特征点筛选和匹配优化示例代码五、多尺度特征提取示例代码六、非局部特征描述子示例代码…

七、类与对象

文章目录 类与对象1.1 自定义类1.2 第一个类1.3 private变量1.4 变量默认值1.5 构造方法1.6 类和对象的生命周期 类与对象 本文为书籍《Java编程的逻辑》1和《剑指Java&#xff1a;核心原理与应用实践》2阅读笔记 将客观世界中存在的一切可以描述的事物称为对象&#xff08;实…

Unity3D判断屏幕中某个坐标点的位置是否在指定UI区域内

系列文章目录 unity工具 文章目录 系列文章目录前言一、使用rect.Contains()判断1-1、转换坐标1-2、代码如下&#xff1a;1-3、注意事项1-3、测试效果如下 二、使用坐标计算在不在区域内2-1、方法如下&#xff1a;2-2、注意事项 三、使用RectTransformUtility.ScreenPointToLo…

【PTA函数题】6-2 约瑟夫环之循环链表

n个人围成一圈&#xff08;编号依次为&#xff1a;0,1,2...n-1&#xff09;,从第一个人开始报数&#xff0c;1&#xff0c;2&#xff0c;……数到m者出列&#xff0c;再从下一个开始重新报数&#xff0c;数到m者再出列……。 下面的程序中&#xff0c;用不带附加表头的循环单链…