莠畅缕 发表于 2026-1-30 20:35:00

使用C#调用Yolo26模型的ONNX

ONNX 和 ONNX Runtime

ONNX,即开放式神经网络交换,是由 Facebook 和 Microsoft 最初开发的社区项目。ONNX 的持续开发是一项协作努力,得到了 IBM、Amazon(通过 AWS)和 Google 等各种组织的支持。该项目旨在创建一种开放文件格式,用于以允许跨不同 AI 框架和硬件使用机器学习模型的方式来表示它们。将 Ultralytics YOLO26 模型导出为 ONNX 格式可简化部署,并确保在各种环境中实现最佳性能。由于讨厌CSDN的收费文章,作者自己研究了如何在C#中使用yolo导出的onnx模型实现图像检测功能,并支持最新的yolo26,免费分享给大家。
演示效果如:

 代码:
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace onnxRuner
{
    public partial class Form1 : Form
    {
      string image_path;
      string mode_path= "modes/";//模型文件路径yolo26n.onnxyolov8n.onnx yolo11n.onnx
      public Form1()
      {
            InitializeComponent();
            cmbModes.SelectedIndex = 0;
      }
      private void btnOpenFile_Click(object sender, EventArgs e)
      {
            OpenFileDialog ofd=new OpenFileDialog();
            ofd.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;";
            if (ofd.ShowDialog() == DialogResult.OK) {
                image_path = ofd.FileName;
                pictureBox1.Image = new Bitmap(image_path);
            }
            
      }
      private void btnRunYoloOnnx_Click(object sender, EventArgs e)
      {
            if (image_path == "")
            {
                return;
            }
            pictureBox2.Image = null;
            lbmsg.Text = "";
            Application.DoEvents();

            //初始化YOLO实例   参数填你的onnx模型路径即可
            using (var yolo = new YoloOnnxDetector(mode_path+cmbModes.Text))
            {
                // 加载待检测图像
                using (var image = new Mat(image_path))
                {
                  // 进行推理
                  DateTime dt1 = DateTime.Now;
                  List<Prediction> predictions = yolo.Predict(image);

                  // 在图像上绘制检测结果
                  foreach (var pred in predictions)
                  {
                        Cv2.Rectangle(image, pred.Box, Scalar.Red, 2);
                        string label = $"{pred.Label} ({pred.Confidence:P2})";
                        Cv2.PutText(image, label, new OpenCvSharp.Point(pred.Box.X, pred.Box.Y - 5),
                                    HersheyFonts.HersheySimplex, 0.5, Scalar.Red, 1);
                  }

                  // 显示或保存结果图像
                  pictureBox2.Image = new Bitmap(image.ToMemoryStream());
                  lbmsg.Text =$"共检测出{predictions.Count}个结果,耗时:{(DateTime.Now-dt1).TotalMilliseconds}ms";
                }
            }
      }
    }
}核心类:
using Microsoft.ML.OnnxRuntime;using Microsoft.ML.OnnxRuntime.Tensors;using OpenCvSharp;using System;using System.Collections.Generic;using System.Linq;namespace onnxRuner{    ///   /// YOLO ONNX 目标检测器类    /// 实现完整的图像预处理、模型推理、后处理流程    ///   public class YoloOnnxDetector : IDisposable    {      private InferenceSession _session;      // ONNX Runtime 推理会话实例      private readonly Size _modelSize = new Size(640, 640); // YOLOv8标准输入尺寸      bool _isYolo26 = false;//yolo26特殊格式      public Dictionary _Names = new Dictionary(0);//类别名称字典      ///         /// 构造函数 - 初始化 YOLOv8 ONNX 检测器      /// 功能:创建ONNX推理会话,加载类别标签,准备模型运行环境      /// 注意:此构造函数会加载整个模型到内存,耗时操作应在程序初始化时执行      ///         /// ONNX模型文件路径(.onnx文件)      public YoloOnnxDetector(string modelPath)      {            // 初始化ONNX Runtime推理会话,加载模型文件            _session = new InferenceSession(modelPath);            var metadata = _session.ModelMetadata.CustomMetadataMap;            if (metadata.ContainsKey("description"))            {                _isYolo26 = metadata["description"].Contains("YOLO26");            }            if (metadata.ContainsKey("names"))            {                _Names = ParseNames(metadata["names"]);            }      }      privateDictionary ParseNames(string names)      {            var nameList = names.TrimStart('{').TrimEnd('}').Split(',');            var list = new Dictionary(nameList.Length);            foreach (var it in nameList)            {                int index = it.IndexOf(":");                if (int.TryParse(it.Substring(0, index), out int i))                  list.Add(i, it.Substring(index + 2).Trim('\''));            }            return list;      }      ///         /// 主预测函数 - 执行完整的目标检测流程      /// 功能:协调预处理、模型推理、后处理三个核心步骤      /// 这是类的主要对外接口,接收原始图像返回检测结果      ///         /// 输入的OpenCV Mat图像对象      /// 检测结果列表,包含边界框、置信度、类别标签      public List Predict(Mat image)      {            // 步骤1:图像预处理 - 将原始图像转换为模型输入格式            var input = PreprocessImage(image);            // 步骤2:准备模型输入 - 创建ONNX Runtime可识别的输入对象            var inputs = new List {                NamedOnnxValue.CreateFromTensor("images", input) // 输入名称必须与模型匹配            };            // 步骤3:模型推理 - 执行ONNX模型前向计算            using (IDisposableReadOnlyCollection results = _session.Run(inputs))            {                // 步骤4:后处理 - 解析模型输出,应用过滤和优化                return Postprocess(results, image);            }      }      ///         /// 图像预处理函数      /// 功能:将原始BGR图像转换为YOLOv8模型期望的输入格式      /// 处理流程:      /// 1. 调整图像尺寸到640x640(保持长宽比可能会丢失,实际应用可改进)      /// 2. 转换色彩空间BGR→RGB(模型训练通常使用RGB格式)      /// 3. 像素值归一化到范围(提高模型数值稳定性)      /// 4. 转换为NCHW格式张量(模型标准输入格式)      ///         /// 原始OpenCV图像(BGR格式,任意尺寸)      /// 预处理后的4维张量,可直接输入ONNX模型      private DenseTensor PreprocessImage(Mat image)      {            // 步骤1:调整图像尺寸到模型输入大小(640x640)            // 注意:此处直接缩放可能失真,生产环境建议保持宽高比            Mat resized = new Mat();            Cv2.Resize(image, resized, _modelSize);            // 步骤2:转换色彩空间 BGR → RGB            // OpenCV默认BGR格式,但大多数模型训练使用RGB格式            Mat rgb = new Mat();            Cv2.CvtColor(resized, rgb, ColorConversionCodes.BGR2RGB);            // 步骤3:创建4维张量             var tensor = new DenseTensor(new[] { 1, 3, _modelSize.Height, _modelSize.Width });                        // 步骤4:逐像素处理,填充张量数据            // 使用嵌套循环确保数据布局正确,避免内存拷贝错误            for (int y = 0; y < rgb.Height; y++)            {                for (int x = 0; x < rgb.Width; x++)                {                  // 获取RGB像素值                  Vec3b pixel = rgb.At(y, x);                  // 归一化到并按照NCHW格式填充                  tensor = pixel / 255.0f; // R通道                  tensor = pixel / 255.0f; // G通道                      tensor = pixel / 255.0f; // B通道                }            }            return tensor;      }      ///         /// 后处理函数 - 解析模型原始输出并提取有意义信息      /// 功能:将模型输出的数值张量转换为实际检测结果      /// 处理流程:      /// 1. 提取模型输出张量(格式)      /// 2. 解析每个检测框的坐标和类别置信度      /// 3. 应用置信度阈值过滤低质量检测      /// 4. 将归一化坐标转换回原始图像像素坐标      /// 5. 应用非极大值抑制去除重复检测      ///         /// ONNX Runtime推理结果集合      /// 原始图像(用于坐标映射)      /// 结构化检测结果列表      private List Postprocess(IDisposableReadOnlyCollection results, Mat originalImage)      {            var predictions = new List();            float confidenceThreshold = 0.5f;// 置信度阈值,过滤不可靠检测            // 步骤1:获取模型输出张量(假设第一个输出包含检测结果)            if (_isYolo26)            {                if (results.Value is DenseTensor tensor)                {                  // 检查维度: ,YOLO26模型输出格式                  if (tensor.Dimensions.Length < 3 || tensor.Dimensions != 6) return null;                  int detectionsCount = tensor.Dimensions; // 检测框数量                  int featureSize = 6; // 每个检测框的特征数量:x1,y1,x2,y2,confidence,class                  var tensorSpan = tensor.Buffer.Span;                  for (int i = 0; i < detectionsCount; i++)                  {                        int offset = i * featureSize;                        float score = tensorSpan; // 置信度                        if (scoremaxConfidence)                        {                            maxConfidence = confidence;                            classId = j - 4;// 减去4个坐标维度得到类别索引                        }                  }                  // 步骤2.2:应用置信度阈值过滤                  if (maxConfidence > confidenceThreshold && classId >= 0)                  {                        // 步骤2.3:解析边界框坐标                         float cx = output;// 边界框中心x坐标(归一化)                        float cy = output;// 边界框中心y坐标(归一化)                        float w = output;   // 边界框宽度(归一化)                        float h = output;   // 边界框高度(归一化)                        // 步骤2.4:将归一化坐标转换为原始图像像素坐标                        // 从中心点格式转换为左上角坐标格式                        float x1 = (cx - w / 2) * originalImage.Width / _modelSize.Width;                        float y1 = (cy - h / 2) * originalImage.Height / _modelSize.Height;                        float x2 = (cx + w / 2) * originalImage.Width / _modelSize.Width;                        float y2 = (cy + h / 2) * originalImage.Height / _modelSize.Height;                        // 步骤2.5:确保坐标在图像边界内(防止越界错误)                        x1 = Math.Max(0, Math.Min(x1, originalImage.Width));                        y1 = Math.Max(0, Math.Min(y1, originalImage.Height));                        x2 = Math.Max(0, Math.Min(x2, originalImage.Width));                        y2 = Math.Max(0, Math.Min(y2, originalImage.Height));                        // 步骤2.6:创建检测结果对象并添加到列表                        predictions.Add(new Prediction                        {                            Box = new Rect((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1)),                            Confidence = maxConfidence,                            Label = _Names                        });                  }                }            }            // 步骤3:应用非极大值抑制去除重叠检测框            return ApplyNMS(predictions);      }      ///         /// 非极大值抑制函数 (NMS - Non-Maximum Suppression)      /// 功能:消除重叠的检测框,保留每个物体最好的检测结果      /// 算法原理:      /// 1. 按置信度降序排序所有检测框      /// 2. 选择置信度最高的框作为基准      /// 3. 计算其他框与基准框的IoU(交并比)      /// 4. 移除IoU超过阈值的框(认为检测的是同一物体)      /// 5. 重复2-4步骤直到处理完所有框      ///         /// 原始检测结果列表(可能包含重叠框)      /// IoU阈值,默认0.5(超过此值认为重叠需要抑制)      /// 过滤后的检测结果列表(无重叠框)      private List ApplyNMS(List predictions, float iouThreshold = 0.5f)      {            // 步骤1:按置信度降序排序(置信度高的优先处理)            var sorted = predictions.OrderByDescending(p => p.Confidence).ToList();            var selected = new List();// 最终选择的检测框            // 步骤2:迭代处理,直到所有框都被检查            while (sorted.Count > 0)            {                // 取出当前置信度最高的框(总是列表第一个)                var current = sorted;                selected.Add(current);      // 添加到最终结果                sorted.RemoveAt(0);         // 从待处理列表移除                // 步骤3:检查剩余框与当前框的重叠度                // 倒序遍历避免索引错位问题                for (int i = sorted.Count - 1; i >= 0; i--)                {                  // 计算当前框与待检查框的IoU                  if (CalculateIoU(current.Box, sorted.Box) > iouThreshold)                  {                        // IoU超过阈值,认为检测的是同一物体,移除置信度较低的框                        sorted.RemoveAt(i);                  }                }            }            return selected;      }      ///         /// 交并比计算函数 (IoU - Intersection over Union)      /// 功能:计算两个矩形框的重叠程度,用于衡量检测框的相似性      /// 数学公式:IoU = 交集面积 / 并集面积      /// 取值范围:,0表示无重叠,1表示完全重叠      ///         /// 第一个矩形框      /// 第二个矩形框      /// IoU值,范围0-1,值越大表示重叠越多      private float CalculateIoU(Rect a, Rect b)      {            // 步骤1:计算两个矩形的交集区域            var inter = a.Intersect(b);            // 步骤2:检查是否有有效交集(宽度或高度为0表示无交集)            if (inter.Width

鞠古香 发表于 2026-2-8 10:24:07

yyds。多谢分享

钱艷芳 发表于 2026-2-10 06:37:29

谢谢楼主提供!

澹台吉星 发表于 2026-2-10 09:14:56

不错,里面软件多更新就更好了

科元料 发表于 2026-2-11 18:31:42

谢谢分享,辛苦了

慢秤 发表于 2026-2-12 04:36:52

谢谢分享,辛苦了

揭荸 发表于 2026-2-12 11:25:31

谢谢分享,试用一下

艾曼语 发表于 2026-2-13 20:40:03

感谢分享,学习下。

余思洁 发表于 2026-2-25 11:52:42

用心讨论,共获提升!

鞠古香 发表于 2026-3-2 04:30:11

新版吗?好像是停更了吧。

虹姥 发表于 2026-3-6 05:48:27

yyds。多谢分享

全叶农 发表于 2026-3-10 04:04:30

懂技术并乐意极积无私分享的人越来越少。珍惜

绘纵 发表于 2026-3-10 08:51:31

谢谢分享,试用一下

夔新梅 发表于 前天 21:34

新版吗?好像是停更了吧。

贼瘁 发表于 前天 22:09

鼓励转贴优秀软件安全工具和文档!

扒钒 发表于 昨天 22:13

感谢,下载保存了
页: [1]
查看完整版本: 使用C#调用Yolo26模型的ONNX