OpenCV 5 + PP-OCRv6 + OpenVINO:C# 本地 OCR 推理更快、更稳、更好集成

目录

效果

[OCR 识别再提速:基于 OpenVINO 的 PPOCRSharp 来了](#OCR 识别再提速:基于 OpenVINO 的 PPOCRSharp 来了)

[OpenVINO 推理,速度提升明显](#OpenVINO 推理,速度提升明显)

[支持 PP-OCRv5 / PP-OCRv6 模型](#支持 PP-OCRv5 / PP-OCRv6 模型)

[C# 调用更方便](# 调用更方便)

[本地 OCR,更适合这些场景](#本地 OCR,更适合这些场景)

[为什么选择 OpenVINO 版本?](#为什么选择 OpenVINO 版本?)

项目特点

C#调用源码

下载


效果

OCR 识别再提速:基于 OpenVINO 的 PPOCRSharp 来了

做 OCR 项目,很多人第一反应是:模型效果重要,速度也同样重要。

尤其是在发票识别、证件识别、截图文字提取、批量图片处理这些场景里,识别速度慢一点,用户体验就会差很多。之前我们已经做过基于 OpenCV DNN 的 OCR 推理版本,整体可用性不错,但在性能上还有继续优化的空间。

这一次,基于 OpenVINO 的 lw.OpenVINO.PPOCRSharp 版本来了。

OpenVINO 推理,速度提升明显

OpenVINO 是 Intel 推出的深度学习推理优化工具套件,针对 CPU 推理做了大量优化,包括算子融合、图优化、线程调度、指令集加速等。

在实际测试中,同样的 OCR 模型,使用 OpenVINO 推理后,速度相比 OpenCV DNN 有明显提升。

对于本地部署 OCR 来说,这一点非常关键:

  • 不依赖云服务

  • 不需要上传图片

  • 保护数据隐私

  • 响应速度更快

  • 更适合桌面软件、本地工具、内网系统集成

支持 PP-OCRv5 / PP-OCRv6 模型

本项目基于 PaddleOCR 系列模型封装,支持常见的文本检测和文字识别流程。

目前测试程序中已集成多种模型选择:

  • PP-OCRv5 mobile

  • PP-OCRv5 server

  • PP-OCRv6 tiny

  • PP-OCRv6 small

  • PP-OCRv6 medium

可以根据实际业务场景选择不同模型。

如果追求速度,可以选择轻量模型;如果追求识别效果,可以选择更大的模型。

C# 调用更方便

lw.OpenVINO.PPOCRSharp 提供 DLL 接口,C# 程序可以直接调用。

测试程序中已经封装好:

  • 模型初始化

  • 图片选择

  • OCR 识别

  • JSON 结果返回

  • 识别框绘制

  • 耗时统计

对于 C# 开发者来说,可以更方便地集成到 WinForm、WPF、桌面工具、业务系统中。

不用重新写 C++ 推理逻辑,也不用研究复杂的模型部署细节,直接调用 DLL 即可。

本地 OCR,更适合这些场景

这个版本尤其适合:

  • 身份证、驾驶证、营业执照识别

  • 发票、单据、表格图片识别

  • 批量图片文字提取

  • 截图 OCR 工具

  • 内网系统 OCR 集成

  • 桌面端 OCR 软件开发

  • 不能上传图片到云端的私有化项目

如果你的项目对隐私、安全、离线部署有要求,本地 OCR 会是更合适的方案。

为什么选择 OpenVINO 版本?

相比 OpenCV DNN 版本,OpenVINO 的优势主要体现在推理性能上。

简单来说:

OpenCV DNN 更通用,部署简单;

OpenVINO 更偏向高性能推理,尤其适合 CPU 本地部署。

在 OCR 这种需要频繁调用 det 和 rec 模型的任务中,推理速度提升会直接反映到整体识别耗时上。

实际体验就是:同样一张图,OpenVINO 版本更快。

项目特点

  • 基于 C++ 封装 OCR 推理

  • 支持 C# 调用

  • 支持 OpenVINO 后端

  • 支持 PP-OCRv5 / PP-OCRv6 模型

  • 支持本地离线识别

  • 返回 JSON 识别结果

  • 测试程序可直接运行

  • 适合二次开发和项目集成

C#调用源码

复制代码
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace OCRTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        const string DllName = "lw.OpenVINO.PPOCRSharp.dll";

        //初始化
        [DllImport(DllName, EntryPoint = "init", CallingConvention = CallingConvention.Cdecl)]
        public extern static int init(ref IntPtr engine
            , [MarshalAs(UnmanagedType.I1)] bool use_gpu
            , int gpu_id

            , string det_model_dir
            , int limit_side_len
            , double det_db_thresh
            , double det_db_box_thresh
            , double det_db_unclip_ratio
            , [MarshalAs(UnmanagedType.I1)] bool use_dilation

            , [MarshalAs(UnmanagedType.I1)] bool cls
            , [MarshalAs(UnmanagedType.I1)] bool use_angle_cls
            , string cls_model_dir
            , double cls_thresh
            , double cls_batch_num

            , string rec_model_dir
            , string rec_char_dict_path
            , int rec_batch_num
            , int rec_img_h
            , int rec_img_w
            , int predictor_num

            , StringBuilder msg);

        //识别
        [DllImport(DllName, EntryPoint = "ocr", CallingConvention = CallingConvention.Cdecl)]
        public extern static int ocr(IntPtr engine, IntPtr image, StringBuilder msg, out IntPtr ocr_result, out int ocr_result_len);

        //识别,按图像内存传入,避免托管层依赖 C++ Mat ABI
        [DllImport(DllName, EntryPoint = "ocr2", CallingConvention = CallingConvention.Cdecl)]
        public extern static int ocr2(IntPtr engine, int rows, int cols, int channels, IntPtr data, StringBuilder msg, out IntPtr ocr_result, out int ocr_result_len);

        //释放
        [DllImport(DllName, EntryPoint = "destroy", CallingConvention = CallingConvention.Cdecl)]
        public extern static int destroy(IntPtr engine, StringBuilder msg);

        static IntPtr OCREngine;

        private Bitmap bmp;

        private String imgPath = null;

        private List<OCRResult> ltOCRResult;

        private string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tif;*.png";

        private StringBuilder OCRResultInfo = new StringBuilder();
        private StringBuilder OCRResultAllInfo = new StringBuilder();

        Pen pen = new Pen(Brushes.Red, 2f);

        /// <summary>
        /// 选择图片
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = fileFilter;
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                imgPath = ofd.FileName;
                bmp?.Dispose();
                bmp = new Bitmap(imgPath);
                pictureBox1.Image = bmp;
                richTextBox1.Clear();

                button2_Click(null, null);
            }
        }

        /// <summary>
        /// 识别
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            if (OCREngine == IntPtr.Zero)
            {
                MessageBox.Show("请先初始化!!!");
                return;
            }

            if (imgPath == null)
            {
                MessageBox.Show("请先选择图片!!!");
                return;
            }

            button1.Enabled = false;
            button2.Enabled = false;
            richTextBox1.Clear();
            OCRResultInfo.Clear();
            OCRResultAllInfo.Clear();

            StringBuilder msgTemp = new StringBuilder(128);

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            IntPtr strPtr = IntPtr.Zero;
            int ocr_result_len = 0;
            string ocr_result = string.Empty;

            int res;
            byte[] bgrData;
            using (Bitmap bgrBitmap = LoadBgrBitmap(imgPath, out bgrData))
            {
                GCHandle handle = GCHandle.Alloc(bgrData, GCHandleType.Pinned);
                try
                {
                    res = ocr2(OCREngine, bgrBitmap.Height, bgrBitmap.Width, 3, handle.AddrOfPinnedObject(), msgTemp, out strPtr, out ocr_result_len);
                }
                finally
                {
                    handle.Free();
                }
            }

            if (strPtr != IntPtr.Zero && ocr_result_len > 0)
            {
                byte[] buffer = new byte[ocr_result_len];
                Marshal.Copy(strPtr, buffer, 0, ocr_result_len);
                ocr_result = Encoding.UTF8.GetString(buffer);
                Marshal.FreeCoTaskMem(strPtr);
                strPtr = IntPtr.Zero;
            }
            stopwatch.Stop();
            double totalTime = stopwatch.Elapsed.TotalMilliseconds;

            OCRResultAllInfo.AppendLine($"耗时: {totalTime:F2}ms");
            OCRResultAllInfo.AppendLine("---------------------------");

            OCRResultInfo.AppendLine($"耗时: {totalTime:F2}ms");
            OCRResultInfo.AppendLine("---------------------------");

            if (res == 0)
            {
                ltOCRResult = Newtonsoft.Json.JsonConvert.DeserializeObject<List<OCRResult>>(ocr_result);
                OCRResultAllInfo.Append(JsonConvert.SerializeObject(ltOCRResult, Newtonsoft.Json.Formatting.Indented));
                Graphics graphics = Graphics.FromImage(bmp);

                foreach (OCRResult item in ltOCRResult)
                {
                    OCRResultInfo.AppendLine(item.text);
                    System.Drawing.Point[] pt = new System.Drawing.Point[] {
                              new System.Drawing.Point(item.x1, item.y1)
                            , new System.Drawing.Point(item.x2, item.y2)
                            , new System.Drawing.Point(item.x3, item.y3)
                            , new System.Drawing.Point(item.x4, item.y4)
                        };
                    graphics.DrawPolygon(pen, pt);
                }
                graphics.Dispose();

                if (checkBox1.Checked)
                {
                    richTextBox1.Text = OCRResultAllInfo.ToString();
                }
                else
                {
                    richTextBox1.Text = OCRResultInfo.ToString();
                }

                pictureBox1.Image = null;
                pictureBox1.Image = bmp;
            }
            else
            {
                if (strPtr != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(strPtr);
                }
                MessageBox.Show("识别失败," + msgTemp.ToString());
            }

            button1.Enabled = true;
            button2.Enabled = true;
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            rdov6tiny.Checked = true;
            chkcls.Checked = false;
            LoadDefaultImage();
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            richTextBox1.Clear();
            if (checkBox1.Checked)
            {
                richTextBox1.Text = OCRResultAllInfo.ToString();
            }
            else
            {
                richTextBox1.Text = OCRResultInfo.ToString();
            }
        }

        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            RadioButton rb = sender as RadioButton;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            UnloadModel();
        }

        private void btnDestroy_Click(object sender, EventArgs e)
        {
            UnloadModel();
        }

        void UnloadModel()
        {
            if (OCREngine != IntPtr.Zero)
            {
                StringBuilder msgTemp = new StringBuilder(128);
                destroy(OCREngine, msgTemp);

                AppendStatus("释放成功: " + msgTemp.ToString());

                OCREngine = IntPtr.Zero;
            }
        }

        private void btnInit_Click(object sender, EventArgs e)
        {
            richTextBox1.Text = "";

            if (OCREngine != IntPtr.Zero)
            {
                StringBuilder msgTemp = new StringBuilder(128);
                destroy(OCREngine, msgTemp);

                AppendStatus("释放成功: " + msgTemp.ToString());

                OCREngine = IntPtr.Zero;

                LoadModel();
            }
            else
            {
                LoadModel();
            }
        }

        void LoadModel()
        {
            StringBuilder msgTemp = new StringBuilder(128);

            bool use_gpu = false;
            int gpu_id = 0;

            string det_model_dir = "";
            int limit_side_len = 960;
            double det_db_thresh = 0.3;
            double det_db_box_thresh = 0.6;
            double det_db_unclip_ratio = 1.2;
            bool use_dilation = false;

            bool cls = true;
            bool use_angle_cls = true;
            string cls_model_dir = "";
            double cls_thresh = 0.9;
            int cls_batch_num = 1;

            string rec_model_dir = "";
            string rec_char_dict_path = "inference/ppocrv5_dict.txt";
            
            int rec_batch_num = 8;
            int rec_img_h = 48;
            int rec_img_w = 320;
            int predictor_num = 4;

            det_db_thresh = Convert.ToDouble(txtdet_db_thresh.Text.ToString());
            det_db_box_thresh = Convert.ToDouble(txtdet_db_box_thresh.Text.ToString());
            det_db_unclip_ratio = Convert.ToDouble(txtdet_db_unclip_ratio.Text.ToString());

            if (chkcls.Checked == true)
            {
                cls = true;
            }
            else
            {
                cls = false;
            }
            cls_batch_num = Convert.ToInt32(txtcls_batch_num.Text.ToString());

            rec_batch_num = Convert.ToInt32(txtrec_batch_num.Text.ToString());
            predictor_num = Convert.ToInt32(txtpredictor_num.Text.ToString());

            if (rdomobile.Checked)
            {
                det_model_dir = "inference/PP-OCRv5_mobile_det_onnx.onnx";
                rec_model_dir = "inference/PP-OCRv5_mobile_rec_onnx.onnx";
                cls_model_dir = "inference/PP-OCRv5_mobile_cls_onnx.onnx";
                rec_char_dict_path = "inference/ppocrv5_dict.txt";
            }
            elseif (rdov6small.Checked)
            {
                det_model_dir = "inference/PP-OCRv6_small_det.onnx";
                rec_model_dir = "inference/PP-OCRv6_small_rec.onnx";
                rec_char_dict_path = "inference/PP-OCRv6_small_rec_dict.txt";
                cls = false;
                use_angle_cls = false;
            }
            elseif (rdov6tiny.Checked)
            {
                det_model_dir = "inference/PP-OCRv6_tiny_det.onnx";
                rec_model_dir = "inference/PP-OCRv6_tiny_rec.onnx";
                rec_char_dict_path = "inference/PP-OCRv6_tiny_rec_dict.txt";
                cls = false;
                use_angle_cls = false;
            }
            elseif (rdov6medium.Checked)
            {
                det_model_dir = "inference/PP-OCRv6_medium_det.onnx";
                rec_model_dir = "inference/PP-OCRv6_medium_rec.onnx";
                rec_char_dict_path = "inference/PP-OCRv6_medium_rec_dict.txt";
                cls = false;
                use_angle_cls = false;
            }
            else
            {
                det_model_dir = "inference/PP-OCRv5_server_det_infer.onnx";
                rec_model_dir = "inference/PP-OCRv5_server_rec_infer.onnx";
                rec_char_dict_path = "inference/ppocrv5_dict.txt";
                cls = false;
                use_angle_cls = false;
            }

            AppendStatus("正在初始化模型...");
            AppendStatus("det: " + det_model_dir);
            AppendStatus("rec: " + rec_model_dir);
            AppendStatus("dict: " + rec_char_dict_path);
            AppendStatus("device: CPU");

            int res = init(ref OCREngine
                        , use_gpu
                        , gpu_id

                        , det_model_dir
                        , limit_side_len
                        , det_db_thresh
                        , det_db_box_thresh
                        , det_db_unclip_ratio
                        , use_dilation

                        , cls
                        , use_angle_cls
                        , cls_model_dir
                        , cls_thresh
                        , cls_batch_num

                        , rec_model_dir
                        , rec_char_dict_path
                        , rec_batch_num
                        , rec_img_h
                        , rec_img_w
                        , predictor_num

                        , msgTemp);

            if (res == 0)
            {
                AppendStatus("模型加载成功: " + msgTemp.ToString());
            }
            else
            {
                string msg = msgTemp.ToString();
                AppendStatus("模型加载失败: " + msg);
                MessageBox.Show("模型加载失败," + msg);
            }
        }

        private void AppendStatus(string text)
        {
            richTextBox1.AppendText($"[{DateTime.Now:HH:mm:ss}] {text}{Environment.NewLine}");
        }

        private Bitmap LoadBgrBitmap(string path, out byte[] bgrData)
        {
            using (Bitmap source = new Bitmap(path))
            {
                Bitmap bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format24bppRgb);
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    g.DrawImage(source, 0, 0, source.Width, source.Height);
                }

                Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
                BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                try
                {
                    int rowBytes = bitmap.Width * 3;
                    bgrData = new byte[rowBytes * bitmap.Height];
                    for (int y = 0; y < bitmap.Height; y++)
                    {
                        IntPtr src = IntPtr.Add(data.Scan0, y * data.Stride);
                        Marshal.Copy(src, bgrData, y * rowBytes, rowBytes);
                    }
                }
                finally
                {
                    bitmap.UnlockBits(data);
                }

                return bitmap;
            }
        }

        private void LoadDefaultImage()
        {
            string defaultImagePath = Path.Combine(Application.StartupPath, "3.jpg");
            if (!File.Exists(defaultImagePath))
            {
                defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "3.jpg");
            }

            if (!File.Exists(defaultImagePath))
            {
                return;
            }

            imgPath = defaultImagePath;
            bmp?.Dispose();
            bmp = new Bitmap(imgPath);
            pictureBox1.Image = bmp;
        }

    }

}

下载

通过网盘分享的文件:lw.OpenVINO.PPOCRSharp.Test 链接: https://pan.baidu.com/s/1UzhCDIRDMV-Ykwpe__X4og 提取码: efya

相关推荐
Maydaycxc1 小时前
Python 实现 RPA + AI 自动化:大模型 OCR + 网页操作完整源码实战
人工智能·python·opencv·selenium·自动化·ocr·rpa
Jasonakeke1 小时前
CLion + OpenCV + Utf8 终极解决方案
人工智能·opencv·计算机视觉
Chris _data3 小时前
c#学习WPF笔记(一)
学习·c#·wpf
花北城16 小时前
【C#】ABP框架服务端开发
开发语言·c#·abp
xiaoshuaishuai818 小时前
C# vCenter跨云迁移的核心问题
开发语言·c#
旧物有情19 小时前
C#异步编程
网络·rpc·c#
菩提树下的凡夫20 小时前
新版OpenCV5.0在ONNX模型的推理应用
opencv·算法
影寂ldy21 小时前
C# 三大内置委托(Action / Func / Predicate)+ Lambda
c++·算法·c#
m沐沐1 天前
【计算机视觉】OpenCV 模板匹配银行卡数字识别---上
人工智能·后端·python·opencv·计算机视觉·pycharm·numpy