C#中集成YOLO算法的实战指南:从模型加载到性能优化

1次阅读
没有评论

共计 3193 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

背景痛点:为什么 C# 调用 YOLO 这么难?

最近在做一个智能监控项目时需要集成 YOLOv5,作为.NET 开发者首先想到用 C# 直接调用,结果踩了不少坑:

C# 中集成 YOLO 算法的实战指南:从模型加载到性能优化

  • 模型转换损耗:PyTorch 训练的.pt 模型转 ONNX 时容易丢失预处理参数
  • 内存占用高:每帧图片推理后未及时释放的张量会累积导致 OOM
  • GPU 竞争:多线程调用时显存分配冲突引发 CUDA error
  • 跨平台差异:开发机跑得好好的,部署到 Docker 容器就报错

技术选型:ONNX Runtime 还是 ML.NET?

实测对比两种方案在 YOLOv5s 模型上的表现:

指标 ONNX Runtime ML.NET
单帧延迟(1080P) 78ms 210ms
内存占用 1.2GB 2.4GB
Linux 支持 √ ×
多模型实例 需手动管理 自动隔离

结论:ONNX Runtime 更适合生产环境,尤其需要低延迟的场景

核心实现四步走

1. 图像预处理(OpenCVSharp 版)

// 转换为 640x640 输入尺寸并归一化
using OpenCvSharp;

Mat Preprocess(Mat src)
{
    // 保持宽高比的 resize
    var ratio = Math.Min(640.0 / src.Width, 640.0 / src.Height);
    var resized = new Mat();
    Cv2.Resize(src, resized, new Size(src.Width * ratio, src.Height * ratio));

    // 填充灰边
    var padded = new Mat(640, 640, MatType.CV_8UC3, new Scalar(114, 114, 114));
    resized.CopyTo(new Mat(padded, new Rect(0, 0, resized.Width, resized.Height)));

    // BGR 转 RGB + 归一化
    padded.ConvertTo(padded, MatType.CV_32FC3, 1.0 / 255);
    Cv2.CvtColor(padded, padded, ColorConversionCodes.BGR2RGB);

    return padded;
}

2. ONNX 模型加载

using Microsoft.ML.OnnxRuntime;

var options = new SessionOptions()
{
    GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
    EnableMemoryPattern = true,
    ExecutionMode = ExecutionMode.ORT_PARALLEL
};

// GPU 加速(需安装 CUDA 版的 ONNX Runtime)options.AppendExecutionProvider_CUDA();

// 加载模型(注意设置模型路径)var session = new InferenceSession("yolov5s.onnx", options);

3. 执行推理

// 将 Mat 转为 Tensor
var inputTensor = new DenseTensor<float>(new Memory<float>(padded.Data, 0, 640 * 640 * 3),
    new[] { 1, 3, 640, 640}); // NCHW 格式

// 创建输入容器
var inputs = new List<NamedOnnxValue> 
{NamedOnnxValue.CreateFromTensor("images", inputTensor) 
};

// 执行推理(O(1)时间复杂度)using var results = session.Run(inputs);

4. 后处理(含 NMS)

// 解析输出张量(YOLOv5 输出格式为 1x25200x85)var output = results.First().AsTensor<float>();

// 筛选置信度 >0.5 的检测框
var boxes = new List<Rect>();
var scores = new List<float>();

for (int i = 0; i < output.Dimensions[1]; i++)
{float confidence = output[0, i, 4];
    if (confidence > 0.5)
    {
        // 解析坐标和类别...
        boxes.Add(ParseBox(output, i));
        scores.Add(confidence);
    }
}

// 非极大值抑制(Non-Maximum Suppression)
var indices = NMS(boxes, scores, 0.45f);

性能优化三板斧

1. 异步推理管道

// 使用 Channel 实现生产者 - 消费者模式
private Channel<Mat> _imageChannel = Channel.CreateBounded<Mat>(10);

// 后台处理线程
_ = Task.Run(async () =>
{await foreach (var image in _imageChannel.Reader.ReadAllAsync())
    {using var processed = Preprocess(image);
        var result = await Task.Run(() => Infer(processed));
        Postprocess(result);
    }
});

2. 内存池优化

// 使用 ArrayPool 减少 GC
var pool = ArrayPool<float>.Shared;
var buffer = pool.Rent(640 * 640 * 3);

try
{
    // 将图像数据复制到缓冲池
    Marshal.Copy(padded.Data, buffer, 0, buffer.Length);
    var inputTensor = new DenseTensor<float>(buffer, new[] {1, 3, 640, 640});
    // ... 推理代码
}
finally
{pool.Return(buffer);
}

3. 多模型 GPU 分配

// 为每个线程创建独立的 Session
[ThreadStatic]
private static InferenceSession _threadLocalSession;

public InferenceSession GetSession()
{if (_threadLocalSession == null)
    {var options = new SessionOptions();
        options.AppendExecutionProvider_CUDA(new OrtCUDAProviderOptions() 
            {DeviceId = Thread.CurrentThread.ManagedThreadId % 4});
        _threadLocalSession = new InferenceSession("model.onnx", options);
    }
    return _threadLocalSession;
}

避坑指南

  1. 通道顺序问题:OpenCV 默认 BGR,而 YOLO 训练时用 RGB
  2. 解决方案:预处理时显式调用 Cv2.CvtColor 转换

  3. 模型热更新泄漏 :直接替换InferenceSession 会导致 GPU 内存泄漏

  4. 正确做法:先 Dispose 旧实例再创建新实例

  5. 监控指标埋点 :使用System.Diagnostics.Metrics 记录 P99 延迟

延伸思考

想要更极致的性能?可以尝试:

  1. TensorRT 加速:将 ONNX 转为 TensorRT 引擎,速度可提升 3 - 5 倍
  2. 自定义算子:用 C ++ 实现特殊层后通过 P /Invoke 调用
  3. 模型量化:FP16/INT8 量化减小模型体积

结语

经过以上优化,我们的监控系统在 RTX 3060 上实现了:
– 平均延迟:<50ms/ 帧
– 内存占用:稳定在 1.5GB 以内
– 支持 8 路视频流并行处理

关键收获:C# 跑 YOLO 完全可行,但需要注意内存管理和线程安全。希望这篇实战指南能帮你少走弯路!

正文完
 0
评论(没有评论)