深度学习模型试跑(十一):PaddleOCR(c++,vs2019)

2 篇文章 0 订阅
1 篇文章 0 订阅
本文介绍了如何利用PaddleOCR进行塑料包装瓶身喷码字符识别,包括数据预处理、模型训练(检测与识别)、C++预测环境搭建及实战步骤。作者分享了自定义数据合成和模型调整经验,重点讲述了在VS2019中使用PaddlePaddle C++预测库的实践过程。
摘要由CSDN通过智能技术生成

前言

个人认为百度在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

主要看博客教程:里面对应的第三步,其中有部分内容我自己有改动。

  1. CMakeLists.txt
    第7行 OFF改成ON, 如果不用trt可以忽略。
    添加两行:
    SET(Dirent_INCLUDE_DIRS “path\to\your dirent\include”)
    include_directories(${Dirent_INCLUDE_DIRS})请添加图片描述
  2. CMakeSettings.json
    请添加图片描述
  3. 报错C3861“lstat”: 找不到标识符
    将lstat中的l去掉, 变为stat

Step4: 运行

提前下载模型
请添加图片描述
我将上述python训练下的导出的权重,这三个模型按大中小/lms下了下来 ,重新配置main.cpp:

  1. 第51、60、63行, 改成对应模型所在的文件夹.
    请添加图片描述
  2. 第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左右。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值