基于ONNX Runtime的PaddleOCR本地C#部署流程

一、OCR应用需求

       光学字符识别(Optical Character Recognition, OCR),是指对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。随着人工智能和计算机视觉技术的不断发展,OCR技术在各个领域得到了广泛应用,如:证件识别,车牌识别、票据识别、办公文档数字化等,极大地提高了处理效率。

       目前OCR技术已日渐成熟,对于常见应用,很多厂商如阿里、百度、腾讯等都提供了第三方服务,识别的速度、准确度也非常不错。但API调用是收费的,对于服务调用频次高的个人和企业,这个费用会显得比较高昂,另一方面,很多单位的敏感内容也不宜连接互联网。因而寻求一种可以部署在本地的OCR服务显得尤为重要。同时,随着深度学习技术的不断深入和普及,开源OCR的识别性能也大幅度提升,达到实用水平,成为一个非常不错的选择。

      在PaddleOCR、CnOCR、chinese_lite OCR、EasyOCR、Tesseract OCR和chineseocr等支持中文的开源OCR中,整体来看PaddleOCR和CnOCR在识别性能、速度和易用性等方面相对来说比较有优势,推荐使用。因而我们这里采用PaddleOCR。

二 、PaddleOCR 简介

       PaddleOCR是一个基于PaddlePaddle框架开发的OCR系统,其技术体系包括文字检测、文字识别、文本方向检测和图像处理等模块。PaddleOCR具有高精度、高效性、易用性、多语种支持和鲁棒性等优点,为用户提供了一个强大的OCR解决方案。PaddleOCR依托于PaddlePaddle核心框架,在模型算法、预训练模型库、工业级部署等层面均提供了丰富的解决方案,并且提供了数据合成、半自动数据标注工具,满足开发者的数据生产需求。

     PaddleOCR支持五种部署方案,分别为服务化Paddle Serving、服务端/云端Paddle Inference、移动端/边缘端Paddle Lite、网页前端Paddle.js。在工业级部署层面,PaddleOCR提供了基于Paddle Inference的服务器端预测方案,基于Paddle Serving的服务化部署方案,以及基于Paddle-Lite的端侧部署方案,满足不同硬件环境下的部署需求,同时提供了基于PaddleSlim的模型压缩方案,可以进一步压缩模型大小。以上部署方式都完成了训推一体全流程打通,以保障开发者可以高效部署,稳定可靠。

     上面提到,PaddleOCR依托于PaddlePaddle框架的,在部署时往往需要同时安装PaddlePaddle框架以及相应的底层CUDA等驱动程序,安装部署的工作量大,学习曲线陡峭,给一般的开发者带来很大困扰(Paddle Inference可以脱离PaddlePaddle框架,但是在对CUDA的支持上,存在版本少,兼容性不足等问题,而动辄1个多G的动态库也不是个事呀)。而且,部署环境复杂时,也容易带来环境兼容性、内存占用等一系列问题[1]。

       那,有没有更加简洁高效的部署方式呢?

       有,那就是ONNX。

三、 ONNX简介

       通常我们在训练模型时可以使用很多不同的框架,比如有的喜欢用 Pytorch,有的喜欢 TensorFLow,也有的喜欢 MXNet,以及深度学习最开始流行的 Caffe等,这样不同的训练框架就导致了产生不同的模型结果包,在模型进行部署推理时就需要不同的依赖库,而且同一个框架不同的版本之间的差异较大,为了解决这个混乱问题,LF AI 联合Facebook, MicroSoft等公司制定了机器学习模型的标准,这个标准叫做开放神经网络交换(Open Neural Network Exchange)简称ONNX,所谓开放就是ONNX定义了一组和环境,平台均无关的标准格式,来增强各种AI模型的可交互性。

       ONNX 定义了一种可扩展的计算图模型、一系列内置的运算单元(OP)和标准数据类型。每一个计算流图都定义为由节点组成的列表,并构建有向无环图。其中每一个节点都有一个或多个输入与输出,每一个节点称之为一个 OP。这相当于一种通用的计算图,不同深度学习框架构建的计算图都能转化为它。

      有了ONNX,经过工业界和学术界数年的探索,模型部署有了一条基于ONNX的推理流水线,这一流水线解决了模型部署中的两大问题:使用对接深度学习框架和推理引擎的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。

四、ONNX Runtime安装部署

       ONNX Runtime (ORT)是Microsoft开源的一个项目,用于跨平台的机器学习模型推理,支持多种编程语言和框架、操作系统及硬件平台。当一个模型从PyTorch、TensorFlow等框架转换为ONNX模型后,使用ONNX Runtime即可进行模型推理,而不再需要原先的训练框架。这使得模型的部署更为便捷和通用。此外,ONNX Runtime通过内置的图优化策略和集成的硬件加速库,可以获得更快的推理速度。即使是在相同的硬件平台,ONNX Runtime也可以获得比PyTorch和TensorFlow更好的运行速度。

1. Paddle模型转换为ONNX

      PaddlePaddle支持将训练完成后的模型转换为ONNX格式,可以从PaddlePaddle官方的Paddle2ONNXhttps://github.com/PaddlePaddle/Paddle2ONNXicon-default.png?t=N7T8https://github.com/PaddlePaddle/Paddle2ONNX下载和编译,其步骤如下:

1). 打开VS命令行工具

系统菜单中,找到x64 Native Tools Command Prompt for VS 2019,打开

2). 安装Protobuf

git clone https://github.com/protocolbuffers/protobuf.git

cd protobuf

git checkout v3.16.0   

cmake -G "Visual Studio 16 2019" -A x64

-DCMAKE_INSTALL_PREFIX=D:\Paddle\installed_protobuf_lib  

-Dprotobuf_MSVC_STATIC_RUNTIME=OFF -Dprotobuf_BUILD_SHARED_LIBS=OFF

-Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_EXAMPLES=OFF .

msbuild protobuf.sln /m /p:Configuration=Release /p:Platform=x64

msbuild INSTALL.vcxproj /p:Configuration=Release /p:Platform=x64

       注意当前Paddle2Onnx工程应该只能支持3.16.0这个版本的protobuf,高版本的protobuf安装后反而有问题。

3). 安装Paddle2ONNX

git clone https://github.com/PaddlePaddle/Paddle2ONNX.git

cd Paddle2ONNX

git submodule init

git submodule update

python setup.py install

4). 使用 Paddle2ONNX 将Paddle静态图模型转换为ONNX模型格式:

paddle2onnx --model_dir .\ch_PP-OCRv3_det_infer --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file .\ch_PP-OCRv3_det_infer\model.onnx --opset_version 10 --input_shape_dict="{'x':[-1,3,-1,-1]}"  --enable_onnx_checker True

当然,如果嫌麻烦的话,可以直接下载转换好的ONNX模型。

2. ONNX Runtime包安装

      有了ONNX模型后,我们就可以部署ONNX Runtime环境了。我这里重点演示C#版本的推理程序(C#构建界面还是方便啊),C++,Python版本的参见[2]。

1).  首先,当然是下载和安装Onnx Runtime,打开 Visual Studio,创建一个PaddleOCRTestOnnx的工程,然后打开程序包管理控制台,输入以下命令:

Install-package Microsoft.ML.OnnxRuntime

        包管理器将自动下载和安装Microsoft.ML.OnnxRuntime包,当出现类似下述的文字时,表明包已经成功安装。

已将“Microsoft.ML.OnnxRuntime.Managed 1.17.3”成功安装到 PaddleOCRTestOnnx

执行 nuget 操作花费时间 107 毫秒

         同时为了方便操控图像,这里需要安装OpenCvSharp

Install-package OpenCvSharp4

Install-package OpenCvSharp4.runtime.win

       我们可以使用get-package来查看已经安装好的库:

        注意,OpenCvSharp4.runtime.win是OpenCvSharp4的运行时,必须同步安装。否则会出现以下错误:

内部异常:

DllNotFoundException: Unable to load DLL 'OpenCvSharpExtern' or one of its dependencies: 找不到指定的模块。 (0x8007007E)
3. ONNX Runtime推理流程

       ONNX Runtime的推理过程非常简单,可以分为加载模型,准备输入数据,执行推理和处理输出数据四个部分,其骨干代码如下所示:

static void Main()
{

    // 加载模型
    var modelPath = "your_model.onnx";
    var onnxModel = new InferenceSession(modelPath);

    // 准备输入数据
    float[] inputValues = new float[] { 1.0f, 2.0f, 3.0f, 4.0f };
    var inputTensor = new DenseTensor<float>(inputValues, new int[] { 1, 4 });

    // 执行推理
    var inputs = new List<NamedOnnxValue>
    {
        NamedOnnxValue.CreateFromTensor("input", inputTensor)
    };

    using (var results = onnxModel.Run(inputs))
    {
        var outputTensor = results.First().AsTensor<float>();
        float[] outputValues = outputTensor.ToArray();
        // 处理输出数据
    }
}
4. PaddleOCR ONNX Runtime 推理适配

       在使用ONNX Runtime推理时,准备输入数据和输出数据处理是大量工作所在,往往首先了解模型的工作原理,搞清模型对输入数据的要求,以及输出数据的含义。这里边包含了输入和输出张量的维度,数据是否需要归一化处理等等。

      PaddleOCR包含了文字(位置)检测,文字方向判断和字符识别三个部分,分别由三个模型(网络)承担。系统从骨干网选择与调整、预测头设计、数据增强、学习率变换策略、正则化参数选择、预训练模型使用、模型自动裁剪与量化等8个方面采用19种有效策略,优化瘦身。

 

      为了适配每个网络的输入和输出,我们可以通过查找官方技术文档,或者利用

NetronVisualizer for neural network, deep learning and machine learning models.icon-default.png?t=N7T8https://netron.app/来查看网络的输入和输出结构,然后以此适配输入和输出,下图是文本检测网络的输入和输出:

Input
name: x
tensor: float32[-1,3,?,?]

Output
name: sigmoid_0.tmp_0
tensor: float32[-1,1,-1,-1]
4.1 输入适配

      首先打开图像文件,转换为RGB形式,根据事先设定的比例要求重新设置宽度和高度,然后将0-255的值归一化为0-1之间的浮点数,最后构建输入张量。

//1. 打开图像,并转换为RGB形式
Mat srcImg = Cv2.ImRead(textBoxImageFile.Text);
Cv2.CvtColor(srcImg, srcImg, ColorConversionCodes.BGR2RGB);

//2. 图像大小适配
int h = srcImg.Rows;
int w = srcImg.Cols;
float scaleH = 1;
float scaleW = 1;
if (h < w)
{
    scaleH = (float)this.shortSize / (float)h;
    float tarW = (float)w * scaleH;
    tarW = tarW - (int)tarW % 32;
    tarW = Math.Max((float)32, tarW);
    scaleW = tarW / (float)w;
}
else
{
    scaleW = (float)this.shortSize / (float)w;
    float tarH = (float)h * scaleW;
    tarH = tarH - (int)tarH % 32;
    tarH = Math.Max((float)32, tarH);
    scaleH = tarH / (float)h;
}
Cv2.Resize(dstImg, dstImg, new OpenCvSharp.Size((int)(scaleW * dstImg.Cols), (int)(scaleH * dstImg.Rows)), interpolation: InterpolationFlags.Linear);

//3. 像素值归一化
for (int c = 0; c < 3; c++)
{
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            float pix = dstImg.Get<Vec3b>(i, j)[c];
            this.inputImage.Add((pix / 255.0f - this.meanValues[c]) / this.normValues[c]);
        }
    }
}

//4. 构建输入张量
int[] inputShape = { 1, 3, dstImg.Rows, dstImg.Cols };
var inputTensor = new DenseTensor<float>(this.inputImage.ToArray(), inputShape);
var inputs = new List<NamedOnnxValue>
{
    NamedOnnxValue.CreateFromTensor(this.inputNames[0], inputTensor)
};
4.2 输出解码

      检测网络输出对应的是归一化图像的边界点,获取推理输出,然后利用轮廓检测等一系列方法,获得文本区域列表。

//1. 获取输出值
var floatArray = outputs[0].AsTensor<float>().ToArray();
int outputCount = 1;
foreach (var dim in outputs[0].AsTensor<float>().Dimensions)
{
    outputCount *= dim;
}

//2. bitmap表示二值化矩阵
Mat binary = new Mat(dstImg.Rows, dstImg.Cols, MatType.CV_8UC1);
Mat bitmap = new Mat(dstImg.Rows, dstImg.Cols, MatType.CV_8UC1);
for (int y = 0; y < dstImg.Rows; y++)
{
    for (int x = 0; x < dstImg.Cols; x++)
    {
        binary.Set<byte>(y, x, (byte)(255.0 * floatArray[y* dstImg.Cols+x]));  
        bitmap.Set<byte>(y, x, (byte)(floatArray[y * dstImg.Cols + x] > this.binaryThreshold ? 255:0)); 
    }
}

float scaleHeight = (float)(h) / (float)(bitmap.Size(0));
float scaleWidth = (float)(w) / (float)(bitmap.Size(1));

//3. 轮廓发现
OpenCvSharp.Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(bitmap, out contours, out hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxSimple);

//4. 轮廓过滤
int numCandidate = Math.Min(contours.Length, this.maxCandidates > 0 ? this.maxCandidates : int.MaxValue);
var confidences = new List<float>();
var results = new List<List<Point2f>>();
for (int i = 0; i < numCandidate; i++)
{
    var contour = contours[i];

    //计算轮廓分值,并忽略分值小于设定阈值的
    if (this.ContourScore(binary, contour) < this.polygonThreshold)
        continue;

    // Rescale
    var contourScaled = new List<OpenCvSharp.Point>();
    contourScaled.AddRange(contour.Select(p => new OpenCvSharp.Point((int)(p.X * scaleWidth), (int)(p.Y * scaleHeight))));

	// 计算轮廓的最小外接矩形(可旋转的)
    var box = Cv2.MinAreaRect(contourScaled);
    float shortSide = Math.Min(box.Size.Width, box.Size.Height);
    if (shortSide < this.shortSideThresh)
        continue;

    results.Add(polygon);
}

       类似地,方向分类器和字符识别网络也要根据模型输入进行适配,并对输出进行解码,这里不再一一赘述。工程代码详见:PaddleOCR-Onnx-CSharp

       找了一个官方样例和程序运行结果如下图:

小结

       将训练完成的神经网络模型转换为通用的标准ONNX格式,可以脱离原先的训练环境,在部署上带来更大的灵活性。本文以PaddleOCR ONNX模型在.NET环境下基于ONNX Runtime的推理为例,简要介绍了ONNX Runtime安装和输入适配和输出解码过程。

参考文献

  1. https://blog.csdn.net/Castlehe/article/details/117356343

     2.  https://github.com/PaddlePaddle/Paddle2ONNX

     3. https://onnxruntime.ai/docs/

     4. https://zhuanlan.zhihu.com/p/265359676

     5. https://blog.csdn.net/bugang4663/article/details/131720149

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值