C#实战:从零开始集成YOLOv11目标检测模型

1次阅读
没有评论

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

image.webp

背景痛点:为什么 C# 调用 AI 模型这么难?

在.NET 生态中调用 Python 训练的 AI 模型时,开发者常遇到三个典型问题:

C# 实战:从零开始集成 YOLOv11 目标检测模型

  • 环境隔离问题 :Python 的虚拟环境和.NET 的运行环境天然隔离,依赖管理复杂
  • 数据格式转换 :Python 的 NumPy 数组与 C# 的矩阵数据结构差异大,转换效率低
  • 性能损耗 :跨语言调用会产生额外序列化 / 反序列化开销,特别是实时视频流场景

技术选型:ONNX Runtime 为什么胜出?

对比两种主流方案:

  1. ONNX Runtime
  2. 优点:跨平台支持好、性能优化充分(支持 CUDA/OpenVINO 等 EP)、内存占用低
  3. 缺点:模型转换需要额外步骤

  4. TorchSharp

  5. 优点:直接加载 PyTorch 模型
  6. 缺点:依赖 Python 运行时、GPU 支持不稳定

决策关键 :ONNX 格式已成为行业标准,微软官方维护的 ONNX Runtime 对.NET 支持最完善,且 YOLOv11 官方已提供 ONNX 导出工具。

核心实现步骤

1. 环境准备

安装必要 NuGet 包:

// 包引用示例
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.15.1" />
<PackageReference Include="OpenCvSharp4" Version="4.7.0.20230115" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.7.0.20230115" />

2. 图像预处理(OpenCVSharp)

// 输入图像预处理
using OpenCvSharp;

Mat Preprocess(Mat image, int targetSize = 640)
{
    // 保持宽高比 resize
    float scale = Math.Min(targetSize / (float)image.Width, targetSize / (float)image.Height);
    Size newSize = new Size((int)(image.Width * scale), (int)(image.Height * scale));
    Mat resized = new Mat();
    Cv2.Resize(image, resized, newSize);

    // 填充到正方形
    Mat padded = new Mat(targetSize, targetSize, MatType.CV_8UC3, new Scalar(114, 114, 114));
    resized.CopyTo(padded[new Rect(0, 0, resized.Width, resized.Height)]);

    // 转换为 RGB 并归一化
    Mat normalized = new Mat();
    padded.ConvertTo(normalized, MatType.CV_32FC3, 1.0 / 255);
    Cv2.CvtColor(normalized, normalized, ColorConversionCodes.BGR2RGB);

    return normalized;
}

3. ONNX Runtime 推理

using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;

float[] RunInference(Mat processedImage)
{
    // 创建输入 Tensor
    var input = new DenseTensor<float>(new[] {1, 3, 640, 640});
    for (int y = 0; y < 640; y++)
        for (int x = 0; x < 640; x++)
            for (int c = 0; c < 3; c++)
                input[0, c, y, x] = processedImage.At<Vec3f>(y, x)[c];

    // 创建推理会话
    using var session = new InferenceSession("yolov11.onnx");
    using var inputs = new List<NamedOnnxValue>
    {NamedOnnxValue.CreateFromTensor("images", input)
    };

    // 执行推理
    try 
    {using var results = session.Run(inputs);
        return results.First().AsTensor<float>().ToArray();}
    catch (Exception ex)
    {Console.WriteLine($"推理失败: {ex.Message}");
        return Array.Empty<float>();}
}

4. 后处理(含 NMS 实现)

// 非极大值抑制 (Non-Maximum Suppression)
List<Rect> NMS(List<Detection> detections, float iouThreshold = 0.5f)
{
    // 按置信度降序排序
    detections.Sort((a, b) => b.Confidence.CompareTo(a.Confidence));

    var results = new List<Rect>();
    while (detections.Count > 0)
    {
        // 取当前最高分检测框
        var current = detections[0];
        results.Add(current.Box);
        detections.RemoveAt(0);

        // 计算与其他框的 IoU
        for (int i = detections.Count - 1; i >= 0; i--)
        {float iou = CalculateIoU(current.Box, detections[i].Box);
            if (iou > iouThreshold)
                detections.RemoveAt(i);
        }
    }
    return results;
}

float CalculateIoU(Rect a, Rect b)
{// 计算交并比 (Intersection over Union)
    Rect intersection = a & b;
    float intersectArea = intersection.Width * intersection.Height;
    float unionArea = a.Width * a.Height + b.Width * b.Height - intersectArea;
    return intersectArea / unionArea;
}

性能优化技巧

1. 启用 CUDA 加速

var options = SessionOptions.MakeSessionOptionWithCudaProvider();
using var session = new InferenceSession("yolov11.onnx", options);

2. 内存池复用

// 全局初始化
static readonly MemoryInfo memoryInfo = OrtMemoryInfo.DefaultInstance;

// 每次推理复用内存
var inputOrtValue = OrtValue.CreateTensorValueFromMemory(memoryInfo, input.Buffer, input.Dimensions);

3. 批处理优化

// 修改输入维度为支持 batch
var input = new DenseTensor<float>(new[] {batchSize, 3, 640, 640});

避坑指南

  1. 输入尺寸问题 :
  2. YOLOv11 默认训练尺寸为 640×640
  3. 如果修改输入尺寸,需要同时修改模型最后的检测头

  4. 颜色通道顺序 :

  5. OpenCV 默认 BGR,ONNX 模型通常需要 RGB
  6. 预处理时务必转换通道顺序

  7. 多线程安全 :

  8. ONNX Session 不是线程安全的
  9. 推荐方案:每个线程创建独立 Session,或使用 Session 池

代码规范建议

  1. 所有公开方法添加 XML 注释

    /// <summary>
    /// 执行非极大值抑制过滤
    /// </summary>
    /// <param name="detections"> 检测结果列表 </param>
    /// <param name="iouThreshold"> 交并比阈值 </param>
    /// <returns> 过滤后的检测框 </returns>

  2. 使用 C# 命名规范:

  3. 方法名:PascalCase
  4. 局部变量:camelCase
  5. 常量:ALL_CAPS

延伸思考

如何动态调整检测置信度阈值?

可以考虑以下实现方案:
1. 将 NMS 方法中的置信度阈值参数化
2. 使用滑动条实时调节(适用于 GUI 应用)
3. 根据场景复杂度自动调整(如检测目标数量少时降低阈值)
4. 将阈值保存在配置文件中支持热更新

完整示例项目已开源在 GitHub:https://github.com/example/yolov11-csharp-demo

希望这篇指南能帮助你顺利集成 YOLOv11 到 C# 项目中。如果遇到问题,欢迎在评论区交流讨论!

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