目录说明效果项目特点适用场景与其他方案对比部署优势性能建议C#调用源码下载说明做 OCR 项目时很多 Windows 客户端都会遇到一个现实问题客户电脑里的显卡不一定是 NVIDIA。有的是 NVIDIA有的是 AMD有的是 Intel 核显也有的是普通办公电脑。如果只做 TensorRT速度很快但只能覆盖 NVIDIA GPU如果只做 CPU兼容性好但速度又不够理想。所以我们做了一个更适合 Windows 通用部署的版本lw.OnnxRuntime.PPOCRSharp_dml它基于ONNX Runtime DirectML目标是让 PP-OCR 在 Windows 环境下可以更方便地调用 GPU 推理同时兼容更多显卡。效果项目特点支持 Windows 多品牌 GPUDirectML 是微软提供的 GPU 推理后端可以在 Windows 上调用多种 GPUNVIDIAAMDIntel 核显Intel 独显相比 CUDA / TensorRT它不强绑定 NVIDIA更适合普通 Windows 客户端软件部署。基于 ONNX Runtime项目使用 ONNX Runtime 加载 OCR 模型模型格式采用 ONNX部署流程清晰PaddleOCR 模型 - ONNX 模型 - ONNX Runtime DirectML 推理不需要提前转换 TensorRT engine也不需要针对不同显卡重新生成模型文件。C DLL 封装C# 简单调用底层推理封装在 C DLL 中C# 界面通过 P/Invoke 调用init() ocr() destroy()这样既保留了 C 推理性能也方便 C# 桌面程序、业务系统、WinForms / WPF 项目集成。支持 PP-OCR 多版本模型测试程序中可以切换多组 OCR 模型例如PP-OCRv5 mobilePP-OCRv5 serverPP-OCRv6 tinyPP-OCRv6 smallPP-OCRv6 medium可以根据不同场景选择速度优先或精度优先。支持 det / rec / cls 三阶段 OCR完整支持文本检测 det文本识别 rec方向分类 cls实际项目中如果图片方向固定也可以关闭 cls进一步减少耗时。保留 GPU ID 设置界面保留use_gpu和gpu_id方便在多 GPU 环境下选择指定显卡。适合通用 Windows 客户端部署相比 TensorRTDML 版本不需要生成 engine 文件也不需要绑定 CUDA、TensorRT 版本对客户电脑环境更友好。适用场景lw.OnnxRuntime.PPOCRSharp_dml适合Windows 桌面 OCR 软件C# 项目调用 OCR DLL客户电脑显卡品牌不固定希望 GPU 加速但不想绑定 NVIDIA票据识别、表单识别、截图 OCR工业视觉文字识别普通办公电脑 OCR 部署与其他方案对比一句话来说OpenCV DNN部署最简单OpenVINOIntel 平台更强TensorRTNVIDIA GPU 上最快DirectMLWindows 多品牌 GPU 兼容性最好DML 版本的定位非常明确不是追求单一平台极限速度而是追求 Windows 客户端 GPU 加速的通用兼容。部署优势对于软件开发者来说DML 版本最大的价值是降低部署门槛。客户不需要安装 CUDA不需要安装 TensorRT也不需要提前转换 engine。只要显卡和系统支持 DirectML就有机会启用 GPU 推理。这对面向多个客户、多个硬件环境的软件尤其重要。性能建议为了获得更稳定的速度建议优先使用较新的显卡驱动Windows 电源模式设置为高性能第一次推理作为预热不计入测速多 predictor 并发数量不要盲目调大根据显存和图片中文字数量调整 batch如果客户是 NVIDIA 并追求极限速度可考虑 TensorRT 版本C#调用源码using Newtonsoft.Json; using OpenCvSharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; 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.OnnxRuntime.PPOCRSharp_dml.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 ListOCRResult 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 namesender/param /// param namee/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 namesender/param /// param namee/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 -1; try { using (Mat img Cv2.ImRead(imgPath, ImreadModes.Color)) { res ocr2(OCREngine, img.Rows, img.Cols, img.Channels(), img.Data, msgTemp, out strPtr, out ocr_result_len); } 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); } 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.DeserializeObjectListOCRResult(ocr_result); OCRResultAllInfo.Append(JsonConvert.SerializeObject(ltOCRResult, Newtonsoft.Json.Formatting.Indented)); using (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); } } if (checkBox1.Checked) { richTextBox1.Text OCRResultAllInfo.ToString(); } else { richTextBox1.Text OCRResultInfo.ToString(); } pictureBox1.Image null; pictureBox1.Image bmp; } else { MessageBox.Show(识别失败 msgTemp.ToString()); } } finally { stopwatch.Stop(); if (strPtr ! IntPtr.Zero) { Marshal.FreeCoTaskMem(strPtr); } button1.Enabled true; button2.Enabled true; } } /// summary /// 初始化 /// /summary /// param namesender/param /// param namee/param private void Form1_Load(object sender, EventArgs e) { rdov6tiny.Checked true; chkcls.Checked false; chkuse_gpu.Checked true; txtgpu_id.Text 1; 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 chkcls_CheckedChanged(object sender, EventArgs e) { if (OCREngine IntPtr.Zero) { return; } richTextBox1.Text ; Console.WriteLine(cls设置已改变正在重新初始化模型...); UnloadModel(); LoadModel(); } 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); Console.WriteLine(释放成功: msgTemp.ToString()); OCREngine IntPtr.Zero; } } private void btnInit_Click(object sender, EventArgs e) { if (OCREngine ! IntPtr.Zero) { StringBuilder msgTemp new StringBuilder(128); destroy(OCREngine, msgTemp); Console.WriteLine(释放成功: msgTemp.ToString()); OCREngine IntPtr.Zero; LoadModel(); } else { LoadModel(); } } void LoadModel() { StringBuilder msgTemp new StringBuilder(128); bool use_gpu true; 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 inference/PP-OCRv5_mobile_cls_onnx.onnx; 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; //赋值 if (chkuse_gpu.Checked true) { use_gpu true; } else { use_gpu false; } gpu_id Convert.ToInt32(txtgpu_id.Text.ToString()); 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; } elseif (rdoserver.Checked) { det_model_dir inference/PP-OCRv5_server_det_infer.onnx; rec_model_dir inference/PP-OCRv5_server_rec_infer.onnx; cls_model_dir inference/PP-OCRv5_server_cls_infer.onnx; } 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; } 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; } else { 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; } Console.WriteLine(det model: det_model_dir); if (cls !File.Exists(Path.Combine(Application.StartupPath, cls_model_dir.Replace(/, Path.DirectorySeparatorChar)))) { Console.WriteLine(cls model missing, disabled: cls_model_dir); cls false; use_angle_cls false; } Console.WriteLine(cls model: (cls ? cls_model_dir : disabled)); Console.WriteLine(rec model: rec_model_dir); Console.WriteLine(rec dict : rec_char_dict_path); 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) { Console.WriteLine(模型加载成功); } else { string msg msgTemp.ToString(); Console.WriteLine(模型加载失败 msg); } } 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.OnnxRuntime.PPOCRSharp_dml_Test 链接: https://pan.baidu.com/s/1xSzlQg7Nvnt8KYk1UVq3aQ 提取码: n4yt引入地址