目录
前言
个人认为百度在OCR这方面一直处于业界领先地位,我在上家公司工作的时候,由于市场部有几个同事对文档/表格识别的需要,我就顺手调用了百度的API,自己集成了一下做了个应用出来。有需要的可以自行去注册百度开发者账号后,按照样例代码自行开发,如果读者中对这需求的多的话我就将我的代码整理下公布出来。
这次主要是在VS2019跑PaddleOCR这个开源项目,因为搭建工程中有些小错误,估计主要是因为写代码的和维护这个rep的不是同一个人,所以教程里面有些许出入,所以我顺带记录一下。
我的环境:
- Visual Studio 2019
- CUDA 11.0,cudnn 8.0 (仅在使用GPU版本的预测库时需要)
- CMake 3.17.1
一.模型解读
官方的文档教程写的非常详细。
二.模型训练
模型训练部分更新于2022/5/19
这一部分主要还是依据PaddleOCR仓库下的各个环节的教程来进行叙述的,我主要是记录一下我自己使用的思路。
2.1 准备数据
我估计看到这的读者都是想训练自己的数据集,而且用官方提供的权重文件推理自己的数据时很有可能效果不是很好,比如我要识别的数据是塑料包装瓶身的喷码字符,但这个字体基本是很难在找的到。
数据准备还是要用PPOCRLabelv2进行标注,不过我没有按官方的步骤每张图每个bbox宽一个个的标注并检测确认,而是自己写了个脚本合成了数据,并产生了对应的标签。
-
制作数据背景:我找了若干张背景不一样的数据,接着用一个抱抱脸上的Inpainting模型将字符去除掉生成空白的模板。
-
下载合适的字符字体文件:其实windows用户可以在C:\Windows\Fonts文件夹下去寻找合适的字体,但我的数据比较特殊,所以是在网上下了一个看上去比较相近的字体。
-
写脚本合成:我贴一下我的代码,如果想照我的思路做可以改下相关配置。
import os.path
from PIL import Image, ImageDraw, ImageFont
from string import ascii_letters
import textwrap
import random
import numpy as np
# f2、f3、f4都是标注软件产生的标签相关数据,并且和图像数据放置在同一个文件夹下(3个都要修改)
f2 = open(r'D:\...\path_to_yourdataset\Label.txt', 'w', encoding='utf-8')
f3 = open(r'D:\...\path_to_yourdataset\Cache.cach', 'w', encoding='utf-8')
f4 = open(r'D:\...\path_to_yourdataset\fileState.txt', 'w', encoding='utf-8')
# rec_gt_line =
ll, tt, rr, bb = np.random.randint(-2, 2), np.random.randint(-1, 3), np.random.randint(-2, 2), np.random.randint(-1, 3)
Label_line = [
{"transcription": "", "points": [[702+ll, 706+tt], [1134+rr, 706+tt], [1134+rr, 786+bb], [702+ll, 786+bb]], "difficult": False},
{"transcription": "", "points": [[590, 890], [1236, 890], [1236, 970], [590, 970]], "difficult": False}]
for j in range(0, 3000):
# image list
image_list = ['blue', 'dark', 'dblue', 'o2p',
'orange2', 'purple', 'white', 'yellow', ]
index = random.sample(range(0, 7), 1)
# Open model image,载入模板图(此处要修改)
img = Image.open(fp=r'D:\...\path_to_modelimage' + '/' + image_list[index[0]] + '.jpg', mode='r')
# Load custom font(此处要修改)
font = ImageFont.truetype(font=r"D:\...\path_to_customfont\pg.ttf", size=72)
# Create DrawText object
draw = ImageDraw.Draw(im=img)
# Define our text
# text = """Simplicity--the art of maximizing the amount of work not done--is essential."""
text = """Simplicity--the art of maximizing the amount of work not done--is essential."""
PG_DEMO = ["""12345678""", """90ABCDEFGH T"""]
PG_DEMO[0] = '202' + str(random.randint(10000, 99999))
# 随机字母+数字验证码
PG_DEMO[1] = ''
for i in range(10):
current = random.randrange(0, 4)
if current == i:
tmp = chr(random.randint(65, 90))
else:
tmp = random.randint(0, 9)
PG_DEMO[1] += str(tmp)
PG_DEMO[1] += '-T'
Label_line[0]["transcription"] = PG_DEMO[0]
Label_line[1]["transcription"] = PG_DEMO[1]
# Calculate the average length of a single character of our font.
# Note: this takes into account the specific font and font size.
avg_char_width = sum(font.getsize(char)[0] for char in ascii_letters) / len(ascii_letters)
# Translate this average length into a character count
max_char_count = int(img.size[0] * .618 / avg_char_width)
init_offset = 12
for item in PG_DEMO:
# Create a wrapped text object using scaled character count
# text = textwrap.fill(text=text, width=max_char_count)
text = textwrap.fill(text=item, width=max_char_count)
# Add text to the image
draw.text(xy=(img.size[0] * 9 / 20, img.size[1] * init_offset / 20), text=text, font=font, fill='#000000',
anchor='mm', spacing=1, stroke_width=1, stroke_fill="black")
init_offset = init_offset + 3
# view the result
# img.show()
# write the image
new_name = str(j) + '.jpg'
# 合成新数据(此处要修改)
gen_7680_path = os.path.join(r'D:\...\path_to_syntacticdataset', new_name)
f2.write('gen_7680/' + new_name + '\t' + str(Label_line) + '\n')
f3.write('gen_7680/' + new_name + '\t' + str(Label_line) + '\n')
f4.write(gen_7680_path + '\t' + '1' + '\n')
img.save(gen_7680_path)
print(j)
f2.close()
f3.close()
f4.close()
代码运行完之后就会生成如图所示的数据
- 启动标注工具:因为脚本运行出来的标签只是检测用的数据与标签,所以还需要标注工具产生识别的数据与标签。常用的操作步骤还是要看PPOCRLabelv2里面的指导,不过由于是我们自动生成的数据,所以无限点确认按钮(可以用鼠标连点器),最后可以通过菜单中“文件-导出标记结果”手动导出,同时也可以点击“文件 - 自动导出标记结果”开启自动导出。手动确认过的标记将会被存放在所打开图片文件夹下的Label.txt中。在菜单栏点击 “文件” - "导出识别结果"后,会将此类图片的识别训练数据保存在crop_img文件夹下,识别标签保存在rec_gt.txt中。
下图是生成的识别数据集。
2.2 训练检测模型
这块我训练的效果还没有官方提供的好🤦♂️…
如果数据不是很奇怪,我觉得检测这块可以直接用官方提供的。
这里我还是记录一下我的训练过程,主要参考这个文档。
预训练模型我用的是ResNet50_vd_ssld_pretrained.pdparams,与之对应的配置文件是configs/det/det_r50_vd_db.yml。配置文件除了数据集和标签的路径,其它地方我基本都没改。
训练:
python tools/train.py -c configs/det/det_r50_vd_db_PG.yml -o Global.pretrained_model=./pretrain_models/ResNet50_vd_ssld_pretrained
测试:
python tools/infer_det.py -c configs/det/det_r50_vd_db_PG.yml -o Global.infer_img="./doc/imgs_words/PG/3.jpg" Global.pretrained_model="./output/det_r50_vd/best_accuracy" PostProcess.box_thresh=0.6 PostProcess.unclip_ratio=2.0
模型导出:
python tools/export_model.py -c configs/det/det_r50_vd_db_PG.yml -o Global.pretrained_model="./output/det_r50_vd/best_accuracy" Global.save_inference_dir="./inference/PG_det/"
2.3 训练识别模型
主要参考这个文档
这里要注意我们用的字典,我是直接在utility.py里把*–rec_char_dict_path的默认值改为./ppocr/utils/en_dict.txt*,其它的和上面类似。
训练:
python tools/train.py -c configs/rec/PP-OCRv3/en_PP-OCRv3_rec.yml -o Global.checkpoints=./output/v3_en_mobile/best_accuracy.pdparams
预测英文结果(切分图)
python tools/infer_rec.py -c configs/rec/PP-OCRv3/en_PP-OCRv3_rec.yml -o Global.pretrained_model=./output/v3_en_mobile/best_accuracy Global.infer_img=doc/imgs_words/PG/3_crop_1.JPG
模型导出
python tools/export_model.py -c configs/rec/PP-OCRv3/en_PP-OCRv3_rec.yml -o Global.pretrained_model=./output/v3_en_mobile/best_accuracy Global.save_inference_dir=./inference/PG/
可视化:
visualdl --logdir ./output --port 8080
2.4 串联推理
参考,我是用的是不使用方向分类器,并且记住命令加上*–rec_char_dict_path=‘ppocr/utils/en_dict.txt’*指定字典路径,不然会识别成中文乱码(上一步在utility.py更改了的可以忽略)。
python tools/infer/predict_system.py --image_dir="./doc/imgs_words/PG/3.jpg" --det_model_dir="./ch_PP-OCRv3_det_infer/" --rec_model_dir="./inference/PG/" --use_angle_cls=false
三.VS2019运行C++预测
Step0: 搭建环境
可参考搭建python的工程环境,在最后一步安装PaddlePaddle的时候,建议到官网找合适的命令安装。然后克隆PaddleOCR repo代码到本地,
git clone https://github.com/PaddlePaddle/PaddleOCR
如果因为网络问题无法pull成功,也可选择使用码云上的托管:
git clone https://gitee.com/paddlepaddle/PaddleOCR
注:码云托管代码可能无法实时同步本github项目更新,存在3~5天延时,请优先使用推荐方式。
Step1: 下载PaddlePaddle C++ 预测库 fluid_inference
Step2: 相关配置安装
主要是下述库:
opencv
dirent (https://github.com/tronkko/dirent, 下载解压:https://codeload.github.com/tronkko/dirent/zip/refs/heads/master)
tensorrt(可选)
Step3: 使用Visual Studio 2019直接编译CMake
主要看博客教程:里面对应的第三步,其中有部分内容我自己有改动。
- CMakeLists.txt
第7行 OFF改成ON, 如果不用trt可以忽略。
添加两行:
SET(Dirent_INCLUDE_DIRS “path\to\your dirent\include”)
include_directories(${Dirent_INCLUDE_DIRS}) - CMakeSettings.json
- 报错C3861“lstat”: 找不到标识符
将lstat中的l去掉, 变为stat
Step4: 运行
提前下载模型,
我将上述python训练下的导出的权重,这三个模型按大中小/lms下了下来 ,重新配置main.cpp:
- 第51、60、63行, 改成对应模型所在的文件夹.
- 第65行,将ppocr/utils/en_dict.txt里的这个中文字典的文本文档复制过来,然后直接改为这个文本文档
上述Visual Studio 2019编译产出的可执行文件在out\build\x64-Release目录下,打开cmd,并切换到该目录(如果代码有更改,建议删除exe后再重新生成执行):
cd D:\projects\PaddleOCR\deploy\cpp_infer\out\build\x64-Release
ppocr.exe system --image_dir=Crop_20210321210136296.jpg --max_side_len=1920
去除掉第一张的warm_up,平均一张推理耗时要15ms左右。