news 2026/7/23 13:17:38

基于DirectML的Windows OCR推理方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于DirectML的Windows OCR推理方案

目录

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 List<OcrResult> ltOCRResult = new List<OcrResult>(); 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 List<List<double>> 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 List<OcrResult> ParseOCRResults(string json) { try { var serializer = new JavaScriptSerializer(); return serializer.Deserialize<List<OcrResult>>(json) ?? new List<OcrResult>(); } catch (Exception ex) { MessageBox.Show("JSON 解析失败:" + ex.Message); return new List<OcrResult>(); } } private string FormatJson(string json) { try { var serializer = new JavaScriptSerializer(); var obj = serializer.Deserialize<object>(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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 13:15:01

企业级AI Agent架构设计与安全实践指南

1. 企业级AI Agent的核心架构解析在企业级AI Agent的架构设计中&#xff0c;我们需要构建一个既能处理复杂任务又能确保系统稳定性的技术框架。现代AI Agent架构通常采用分层设计理念&#xff0c;将系统划分为以下几个关键层次&#xff1a;1.1 基础模型层基础模型层是整个AI Ag…

作者头像 李华
网站建设 2026/7/23 13:14:37

三项技术突破 苏州高新区“云智共生”产业链加速成形

#移动云#海山数据库#AI原生数据库#国产化数据库#中国移动自主研发数据库#HaishanDB#量子计算云平台拿下联合国WSIS冠军奖&#xff0c;云原生数据库率先通过国家标准测试&#xff0c;两项AI应用入选工信部典型案例&#xff0c;就在这个7月&#xff0c;移动云迎来了“三项技术突破…

作者头像 李华
网站建设 2026/7/23 13:13:53

AI音乐和弦优化:从技术到情感的进阶之路

1. 为什么AI音乐总差一口气&#xff1f;和弦的锅跑不掉最近在音乐制作社区里&#xff0c;有个现象越来越明显&#xff1a;很多用AI生成的歌曲&#xff0c;旋律流畅、节奏准确&#xff0c;但就是听起来"没内味"。就像喝了一杯兑了十次水的咖啡&#xff0c;技术指标都达…

作者头像 李华
网站建设 2026/7/23 13:13:16

别再用虚拟机了!在Ubuntu 20.04上用Docker给双网口小主机装OpenWrt软路由(保姆级避坑)

双网口小主机轻量化部署:Docker版OpenWrt软路由实战指南 为什么选择Docker而非虚拟机? 每次看到朋友在低配小主机上吃力地运行虚拟机部署软路由,我都忍不住想安利Docker方案。J4125、N5105这类迷你主机的性能本就有限,再套上虚拟化层的性能损耗,简直就是让小学生去跑马拉…

作者头像 李华
网站建设 2026/7/23 13:13:15

OpenAI技术演进:从GPT到多模态大模型的十年突破

1. OpenAI的十年技术演进轨迹2015年那个旧金山的冬天&#xff0c;当Elon Musk和Sam Altman等人宣布成立OpenAI时&#xff0c;他们或许没想到这家非营利性AI研究机构会在十年间经历如此戏剧性的蜕变。从最初开源GPT-2模型的犹豫不决&#xff0c;到ChatGPT引爆全球AI军备竞赛&…

作者头像 李华
网站建设 2026/7/23 13:13:03

视频平台的CDN架构优化:从回源策略到边缘节点的缓存命中率提升

视频平台的CDN架构优化&#xff1a;从回源策略到边缘节点的缓存命中率提升 一、背景与问题定义 视频平台的流量特性与普通 Web 应用有本质区别&#xff1a;单次请求传输的数据量不在 KB 级别&#xff0c;而在 MB 甚至 GB 级别。一个 1080P、10 分钟的视频体积约 200MB&#xff…

作者头像 李华