共计 2950 个字符,预计需要花费 8 分钟才能阅读完成。
在计算机视觉应用中,目标检测是一个基础且重要的任务。对于 C# 开发者来说,如何在 .NET 生态中高效地实现目标检测功能一直是个挑战。本文将带你了解如何使用 ONNX 运行时在 C# 中实现高效的目标检测。

1. 背景与痛点
传统上,在 C# 中实现目标检测主要有以下几种方式:
- 使用 OpenCV 的 DNN 模块
- 通过 TensorFlow.NET 等绑定库
- 自己实现神经网络推理
但这些方法都存在明显的不足:
- OpenCV 的 DNN 模块功能有限,性能一般
- TensorFlow.NET 等绑定库版本兼容性问题严重
- 自己实现推理引擎开发成本高
ONNX(Open Neural Network Exchange)作为一种开放的模型格式,完美解决了这些问题。它提供了跨框架的模型互操作性,同时 ONNX 运行时针对不同平台都做了深度优化。
2. 技术选型
在选择推理引擎时,我们需要考虑几个关键因素:
- 性能:推理速度、内存占用
- 易用性:API 设计、文档完整性
- 生态:模型支持、社区活跃度
ONNX 运行时在这几个方面表现都很出色:
- 性能优异 :支持多线程和 GPU 加速
- 跨平台 :Windows/Linux/macOS 全支持
- 模型丰富 :支持大多数主流视觉模型
- 内存高效 :专门为生产环境优化
相比之下,TensorFlow.NET 虽然功能强大,但版本兼容性问题严重,且性能不如 ONNX 运行时。
3. 核心实现
3.1 模型转换
大多数目标检测模型(如 YOLOv5)都是用 Python 训练的。我们需要将其转换为 ONNX 格式:
import torch
from models.experimental import attempt_load
# 加载预训练模型
model = attempt_load('yolov5s.pt', map_location=torch.device('cpu'))
# 转换为 ONNX 格式
torch.onnx.export(
model,
torch.randn(1, 3, 640, 640),
'yolov5s.onnx',
opset_version=12,
input_names=['images'],
output_names=['output'],
dynamic_axes={'images': {0: 'batch'}, 'output': {0: 'batch'}}
)
转换时需要注意:
- 确保输入输出维度正确
- 选择适当的 opset 版本
- 考虑是否支持动态 batch
3.2 C# 加载与推理
在 C# 中使用 ONNX 运行时需要先安装 NuGet 包:
Install-Package Microsoft.ML.OnnxRuntime
下面是完整的推理代码:
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
public class ObjectDetector : IDisposable
{
private readonly InferenceSession _session;
private readonly float[] _inputBuffer;
private readonly int _inputWidth;
private readonly int _inputHeight;
public ObjectDetector(string modelPath, int width = 640, int height = 640)
{
// 初始化 ONNX 运行时会话
var options = new SessionOptions()
{GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL};
_session = new InferenceSession(modelPath, options);
_inputWidth = width;
_inputHeight = height;
_inputBuffer = new float[3 * width * height];
}
public List<DetectionResult> Detect(Image image)
{
// 预处理图像
PreprocessImage(image);
// 创建输入张量
var inputTensor = new DenseTensor<float>(_inputBuffer, new[] {1, 3, _inputHeight, _inputWidth});
var inputs = new List<NamedOnnxValue>
{NamedOnnxValue.CreateFromTensor("images", inputTensor)
};
// 执行推理
using var results = _session.Run(inputs);
// 后处理获取检测结果
return PostProcess(results);
}
private void PreprocessImage(Image image)
{
// 实现图像缩放、归一化等预处理
// ...
}
private List<DetectionResult> PostProcess(IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results)
{
// 解析模型输出,过滤低置信度检测
// ...
}
public void Dispose()
{_session?.Dispose();
}
}
关键点说明:
InferenceSession是核心类,负责模型加载和推理- 输入数据需要转换为正确的形状和类型
- 必须正确处理资源的释放
4. 性能优化
4.1 多线程推理
ONNX 运行时原生支持多线程推理,可以通过配置 SessionOptions 来启用:
var options = new SessionOptions()
{
ExecutionMode = ExecutionMode.ORT_PARALLEL,
InterOpNumThreads = 4, // 线程间并行
IntraOpNumThreads = 4 // 操作内并行
};
4.2 GPU 加速
如果需要使用 GPU 加速,需要安装 CUDA/cuDNN 并配置:
var options = SessionOptions.MakeSessionOptionWithCudaProvider(0); // 使用第一个 GPU
性能测试数据(模拟):
| 配置 | FPS (640×640) | 内存占用 |
|---|---|---|
| CPU 单线程 | 12 | 800MB |
| CPU 多线程 | 35 | 1.2GB |
| GPU | 60+ | 1.5GB |
5. 避坑指南
5.1 常见模型转换错误
- Shape 不匹配 :确保导出时的输入尺寸与推理时一致
- opset 版本问题 :尝试不同的 opset 版本
- 自定义算子不支持 :可能需要实现自定义算子
5.2 内存泄漏预防
- 确保所有实现了 IDisposable 的对象都被正确释放
- 避免在循环中重复创建 InferenceSession
- 使用内存分析工具定期检查
6. 总结与延伸
通过 ONNX 运行时,我们可以在 C# 中高效地实现目标检测功能。相比其他方案,它具有以下优势:
- 性能优异,支持多种加速方式
- 模型兼容性好
- 内存管理高效
进一步的优化方向:
- 模型量化:减小模型体积,提升推理速度
- 尝试其他视觉任务:如实例分割、姿态估计等
- 探索 ONNX 运行时的其他特性:如 IO Binding
ONNX 生态正在快速发展,相信未来会有更多强大的功能加入。希望本文能帮助你在 C# 项目中成功应用目标检测技术。
正文完
