
目录1. 引言2. 优势3. 效果4. OCR 模型选择与准备5. WinForm 界面实现6. 下载1. 引言本方案旨在利用 DirectML 的硬件加速能力在 Windows 系统上部署和运行一个轻量级、高精度的 OCR 模型。2. 优势其核心优势在于跨硬件兼容DirectML 支持集成显卡、独立显卡NVIDIA/AMD/Intel以及 CPU确保在不同 Windows 设备上都能获得加速。低延迟推理通过 DirectX 12 底层接口直接与硬件通信减少中间层开销实现端到端的高效推理。易于集成提供 C 和 WinRT API可方便地集成到桌面应用、UWP 应用或服务中。模型格式支持支持 ONNX 模型格式便于使用主流深度学习框架如 PyTorch, TensorFlow训练并导出的模型。3. 效果4. OCR 模型选择与准备PaddleOCR 轻量级模型百度开源的 OCR 工具包提供了优秀的轻量级中英文识别模型。5. WinForm 界面实现为了将 OCR 推理功能集成到 Windows 桌面应用中我们可以使用 WinForm 创建一个简单的图形界面。以下是一个基本的 WinForm 窗体实现包含图像选择、OCR 识别和结果显示功能。namespace OnnxRuntimeForm { public partial class Form1 : Form { private IntPtr engine IntPtr.Zero; private Bitmap currentImage null; private string imgPath null; private ListOcrResult ltOCRResult new ListOcrResult(); private StringBuilder OCRResultInfo new StringBuilder(); private StringBuilder OCRResultAllInfo new StringBuilder(); private Pen pen new Pen(Brushes.Red, 2f); private const string BaseModelDir inference; public class OcrResult { public ListListdouble box { get; set; } public double score { get; set; } public string text { get; set; } } [DllImport(OnnxRuntimeDll.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Unicode)] static extern int init(out IntPtr engine, [MarshalAs(UnmanagedType.U1)] 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.U1)] bool use_dilation, [MarshalAs(UnmanagedType.U1)] bool cls, [MarshalAs(UnmanagedType.U1)] 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(OnnxRuntimeDll.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Unicode)] static extern int ocr(IntPtr engine, int rows, int cols, int channels, IntPtr data, StringBuilder msg, out IntPtr ocr_result, out int ocr_result_len); [DllImport(OnnxRuntimeDll.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Unicode)] static extern int destroy(IntPtr engine, StringBuilder msg); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { rdoV6Tiny.Checked true; chkGPU.Checked false; chkCls.Checked false; UpdateModelPaths(); txtUnclipRatio.Text 1.8; LoadModel(); } private void rdoModel_CheckedChanged(object sender, EventArgs e) { if (((RadioButton)sender).Checked) { UpdateModelPaths(); } } private void UpdateModelPaths() { string det ; string rec ; string dict ; bool v6 false; if (rdoV5Mobile.Checked) { det Path.Combine(BaseModelDir, PP-OCRv5_mobile_det_onnx.onnx); rec Path.Combine(BaseModelDir, PP-OCRv5_mobile_rec_onnx.onnx); dict Path.Combine(BaseModelDir, ppocrv5_dict.txt); } else if (rdoV5Server.Checked) { det Path.Combine(BaseModelDir, PP-OCRv5_server_det_infer.onnx); rec Path.Combine(BaseModelDir, PP-OCRv5_server_rec_infer.onnx); dict Path.Combine(BaseModelDir, ppocrv5_dict.txt); } else if (rdoV6Tiny.Checked) { det Path.Combine(BaseModelDir, PP-OCRv6_tiny_det.onnx); rec Path.Combine(BaseModelDir, PP-OCRv6_tiny_rec.onnx); dict Path.Combine(BaseModelDir, PP-OCRv6_tiny_rec_dict.txt); v6 true; } else if (rdoV6Small.Checked) { det Path.Combine(BaseModelDir, PP-OCRv6_small_det.onnx); rec Path.Combine(BaseModelDir, PP-OCRv6_small_rec.onnx); dict Path.Combine(BaseModelDir, PP-OCRv6_small_rec_dict.txt); v6 true; } else if (rdoV6Medium.Checked) { det Path.Combine(BaseModelDir, PP-OCRv6_medium_det.onnx); rec Path.Combine(BaseModelDir, PP-OCRv6_medium_rec.onnx); dict Path.Combine(BaseModelDir, PP-OCRv6_medium_rec_dict.txt); v6 true; } txtDetModel.Text det; txtRecModel.Text rec; txtDict.Text dict; if (v6) { chkCls.Checked false; chkCls.Enabled false; } else { chkCls.Enabled true; } } private void btnInit_Click(object sender, EventArgs e) { richTextBox1.Clear(); if (engine ! IntPtr.Zero) { UnloadModel(); } LoadModel(); } private void btnSelect_Click(object sender, EventArgs e) { using (OpenFileDialog ofd new OpenFileDialog()) { ofd.Filter 图片|*.bmp;*.jpg;*.jpeg;*.tiff;*.tif;*.png;*.gif|所有文件|*.*; if (ofd.ShowDialog() DialogResult.OK) { imgPath ofd.FileName; currentImage?.Dispose(); currentImage new Bitmap(imgPath); pictureBox1.Image currentImage; richTextBoxResult.Clear(); } } } private void btnOCR_Click(object sender, EventArgs e) { if (engine IntPtr.Zero) { MessageBox.Show(请先初始化模型); return; } if (imgPath null || currentImage null) { MessageBox.Show(请先选择图片); return; } btnSelect.Enabled false; btnOCR.Enabled false; richTextBoxResult.Clear(); OCRResultInfo.Clear(); OCRResultAllInfo.Clear(); var msgTemp new StringBuilder(1024); var stopwatch System.Diagnostics.Stopwatch.StartNew(); 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(engine, 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 ParseOCRResults(ocr_result); ltOCRResult.Sort((a, b) { double ax1 a.box[0][0], ay1 a.box[0][1], ax2 a.box[0][0], ay2 a.box[0][1]; double bx1 b.box[0][0], by1 b.box[0][1], bx2 b.box[0][0], by2 b.box[0][1]; foreach (var p in a.box) { ax1 Math.Min(ax1, p[0]); ay1 Math.Min(ay1, p[1]); ax2 Math.Max(ax2, p[0]); ay2 Math.Max(ay2, p[1]); } foreach (var p in b.box) { bx1 Math.Min(bx1, p[0]); by1 Math.Min(by1, p[1]); bx2 Math.Max(bx2, p[0]); by2 Math.Max(by2, p[1]); } double y_overlap Math.Max(0, Math.Min(ay2, by2) - Math.Max(ay1, by1)); double h Math.Min(ay2 - ay1, by2 - by1); if (h 0 y_overlap / h 0.5) return ax1.CompareTo(bx1); return ay1.CompareTo(by1); }); OCRResultAllInfo.Append(FormatJson(ocr_result)); if (ltOCRResult.Count 0) { OCRResultInfo.AppendLine([未识别到文字]); OCRResultAllInfo.AppendLine([未识别到文字]); } using (Graphics graphics Graphics.FromImage(currentImage)) { foreach (OcrResult item in ltOCRResult) { OCRResultInfo.AppendLine(item.text); if (item.box ! null item.box.Count 4) { Point[] points item.box.Select(p new Point((int)p[0], (int)p[1])).ToArray(); graphics.DrawPolygon(pen, points); } } } pictureBox1.Image null; pictureBox1.Image currentImage; UpdateRichText(); } else { if (strPtr ! IntPtr.Zero) Marshal.FreeCoTaskMem(strPtr); MessageBox.Show(识别失败 msgTemp.ToString()); } btnSelect.Enabled true; btnOCR.Enabled true; } private void chkAllInfo_CheckedChanged(object sender, EventArgs e) { UpdateRichText(); } private void UpdateRichText() { richTextBoxResult.Text chkAllInfo.Checked ? OCRResultAllInfo.ToString() : OCRResultInfo.ToString(); } private double ParseDouble(TextBox tb, double defaultValue) { if (double.TryParse(tb.Text.Trim(), out double v)) return v; return defaultValue; } private int ParseInt(TextBox tb, int defaultValue) { if (int.TryParse(tb.Text.Trim(), out int v)) return v; return defaultValue; } private void LoadModel() { var msgTemp new StringBuilder(1024); bool use_gpu chkGPU.Checked; int gpu_id 0; string det_model_dir txtDetModel.Text.Trim(); int limit_side_len ParseInt(txtLimitSideLen, 960); double det_db_thresh ParseDouble(txtDbThresh, 0.3); double det_db_box_thresh ParseDouble(txtBoxThresh, 0.6); double det_db_unclip_ratio ParseDouble(txtUnclipRatio, 1.6); bool use_dilation true; bool use_angle_cls chkCls.Checked chkCls.Enabled; bool cls use_angle_cls; string cls_model_dir use_angle_cls ? Path.Combine(BaseModelDir, PP-OCRv5_mobile_cls_onnx.onnx) : ; double cls_thresh 0.9; double cls_batch_num 1; string rec_model_dir txtRecModel.Text.Trim(); string rec_char_dict_path txtDict.Text.Trim(); int rec_batch_num ParseInt(txtRecBatchNum, 8); int rec_img_h 48; int rec_img_w 960; int predictor_num 4; AppendStatus(正在初始化模型...); AppendStatus(det: det_model_dir); AppendStatus(rec: rec_model_dir); AppendStatus(dict: rec_char_dict_path); AppendStatus(device: (use_gpu ? GPU : CPU)); AppendStatus($db_thresh{det_db_thresh}, box_thresh{det_db_box_thresh}, unclip{det_db_unclip_ratio}); int res init(out engine, 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 { AppendStatus(模型加载失败: msgTemp.ToString()); MessageBox.Show(模型加载失败 msgTemp.ToString()); engine IntPtr.Zero; } } private void UnloadModel() { if (engine ! IntPtr.Zero) { var msgTemp new StringBuilder(1024); destroy(engine, msgTemp); AppendStatus(释放成功: msgTemp.ToString()); engine IntPtr.Zero; } } 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 ListOcrResult ParseOCRResults(string json) { try { var serializer new JavaScriptSerializer(); return serializer.DeserializeListOcrResult(json) ?? new ListOcrResult(); } catch (Exception ex) { MessageBox.Show(JSON 解析失败 ex.Message); return new ListOcrResult(); } } private string FormatJson(string json) { try { var serializer new JavaScriptSerializer(); var obj serializer.Deserializeobject(json); return serializer.Serialize(obj); } catch { return json; } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { UnloadModel(); currentImage?.Dispose(); pen?.Dispose(); } } }6. 下载通过网盘分享的文件OnnxRuntime链接: https://pan.baidu.com/s/1OnmaLUQs-TIQBWzk83j4Jg 提取码: fx8n