共计 2897 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点与 DML 优势
OCR(光学字符识别)在文档处理、票据识别等场景应用广泛,但传统 CPU 推理常遇到两个核心问题:

- 计算延迟高:CNN 网络的多层卷积操作在 CPU 上串行执行,单张图片推理耗时常超过 200ms
- 吞吐量瓶颈:批量处理时受限于 CPU 的并行计算能力,无法充分利用硬件资源
DirectML(DML)作为微软推出的 DirectX 12 底层加速接口,在 Windows 平台具备独特优势:
- 硬件普适性:支持所有兼容 DirectX 12 的 GPU(包括集成显卡)
- 驱动免配置:Windows 10+ 系统自带运行时,无需单独安装 CUDA
- 计算图优化:自动融合算子(如 Conv+ReLU)提升指令密度
后端技术选型对比
| 后端类型 | 适用平台 | 安装复杂度 | 模型支持度 |
|---|---|---|---|
| CUDA | NVIDIA GPU | 高 | 最佳 |
| TensorRT | NVIDIA GPU | 极高 | 需转换专属格式 |
| DirectML | 所有 DX12 GPU | 低 | 支持主流算子 |
| CPU | 全平台 | 无 | 完全兼容 |
对于 Windows 平台的 C# 开发者,DML 在易用性与性能间取得了最佳平衡。
环境配置与核心实现
1. 基础环境搭建
通过 NuGet 安装必需组件:
Install-Package Microsoft.ML.OnnxRuntime.DirectML -Version 1.15.0
Install-Package SixLabors.ImageSharp -Version 3.0.1 # 图像处理
2. 推理会话封装
/// <summary>
/// 封装 ONNX Runtime 的 DML 推理会话
/// 实现 IDisposable 确保显存释放
/// </summary>
public class DmlOcrEngine : IDisposable
{
private InferenceSession _session;
private bool _disposed = false;
public DmlOcrEngine(string modelPath)
{var options = SessionOptions.MakeSessionOptionWithDmlProvider(0); // 0 表示默认 GPU
_session = new InferenceSession(modelPath, options);
}
protected virtual void Dispose(bool disposing)
{if (!_disposed)
{if (disposing)
{_session?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{Dispose(true);
GC.SuppressFinalize(this);
}
}
3. GPU 加速的预处理
// 使用 ImageSharp 进行 GPU 友好的张量转换
public static float[] PreprocessImage(Image<Rgb24> image)
{
// 缩放到模型输入尺寸
image.Mutate(x => x.Resize(640, 480));
// 连续内存布局提升拷贝效率
var tensor = new float[3 * 480 * 640];
for (int y = 0; y < 480; y++)
{for (int x = 0; x < 640; x++)
{var pixel = image[x, y];
tensor[y * 640 + x] = pixel.R / 255.0f;
tensor[640*480 + y*640 + x] = pixel.G / 255.0f;
tensor[2*640*480 + y*640 + x] = pixel.B / 255.0f;
}
}
return tensor;
}
性能优化实战
批处理与内存池
// 使用 MemoryPool 减少 GC 压力
private static readonly MemoryPool<float> _memoryPool = MemoryPool<float>.Shared;
public IList<string> BatchPredict(IEnumerable<Image<Rgb24>> images)
{var results = new ConcurrentBag<string>();
Parallel.ForEach(images, image =>
{using (var memoryOwner = _memoryPool.Rent(3 * 480 * 640))
{var tensor = PreprocessImage(image);
tensor.CopyTo(memoryOwner.Memory.Span);
var inputs = new List<NamedOnnxValue>
{NamedOnnxValue.CreateFromTensor("input", new DenseTensor<float>(memoryOwner.Memory, new[] {1, 3, 480, 640}))
};
using (var outputs = _session.Run(inputs))
{var text = DecodeOutput(outputs);
results.Add(text);
}
}
});
return results.ToList();}
性能测试数据
使用 BenchmarkDotNet 测试结果(RTX 3060 vs i7-12700K):
| 指标 | CPU 后端 | DML 后端 | 提升倍数 |
|---|---|---|---|
| 单张耗时(ms) | 215 | 58 | 3.7x |
| 批量吞吐(FPS) | 12 | 63 | 5.25x |
| 峰值显存(MB) | – | 780 | – |
常见问题解决方案
显存不足处理
// 创建会话时设置备用 CPU 设备
var options = SessionOptions.MakeSessionOptionWithDmlProvider(0);
options.AppendExecutionProvider_CPU(ExecutionDevicePreference.Strong);
线程安全策略
- 方案 1:每个线程独立创建 InferenceSession(消耗显存)
- 方案 2:使用锁保护共享 Session(降低吞吐)
- 推荐方案:结合
ConcurrentQueue实现会话池
public class SessionPool : IDisposable
{private ConcurrentQueue<InferenceSession> _pool = new();
public InferenceSession Rent()
{if (_pool.TryDequeue(out var session))
return session;
return CreateNewSession();}
public void Return(InferenceSession session)
{_pool.Enqueue(session);
}
}
扩展应用方向
本方案可迁移到其他计算机视觉任务:
- 目标检测:修改输入分辨率与后处理逻辑
- 图像分割:增加 DML 支持的转置卷积算子
- 超分辨率:注意 FP16 精度下的颜色偏差问题
关键调整点:
- 模型输入 / 输出张量 (Tensor) 维度适配
- 后处理中的非极大抑制 (NMS) 改为 GPU 实现
- 使用 ONNX Runtime 的 IO Binding 减少内存拷贝
通过本文方案,开发者可在 Windows 平台快速构建高性能 OCR 应用,同时掌握 DML 加速的核心方法论。
正文完
发表至: 技术分享
近两天内
