C# Onnx Yolov8 Seg 分割

文章讲述了如何使用Ultralytics的YOLOv8模型进行图像分割,并提供了C++代码示例,包括模型加载、输入处理和推理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

效果

模型信息

项目

代码

下载 


效果

模型信息

Model Properties
-------------------------
date:2023-09-07T17:11:46.798385
description:Ultralytics YOLOv8n-seg model trained on coco.yaml
author:Ultralytics
task:segment
license:AGPL-3.0 https://ultralytics.com/license
version:8.0.172
stride:32
batch:1
imgsz:[640, 640]
names:{0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant', 11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'donut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'}
---------------------------------------------------------------

Inputs
-------------------------
name:images
tensor:Float[1, 3, 640, 640]
---------------------------------------------------------------

Outputs
-------------------------
name:output0
tensor:Float[1, 116, 8400]
name:output1
tensor:Float[1, 32, 160, 160]
---------------------------------------------------------------

项目

代码

// 图片缩放
image = new Mat(image_path);
int max_image_length = image.Cols > image.Rows ? image.Cols : image.Rows;
Mat max_image = Mat.Zeros(new OpenCvSharp.Size(max_image_length, max_image_length), MatType.CV_8UC3);
Rect roi = new Rect(0, 0, image.Cols, image.Rows);
image.CopyTo(new Mat(max_image, roi));

float[] det_result_array = new float[8400 * 116];
float[] proto_result_array = new float[32 * 160 * 160];
float[] factors = new float[4];
factors[0] = factors[1] = (float)(max_image_length / 640.0);
factors[2] = image.Rows;
factors[3] = image.Cols;

// 将图片转为RGB通道
Mat image_rgb = new Mat();
Cv2.CvtColor(max_image, image_rgb, ColorConversionCodes.BGR2RGB);
Mat resize_image = new Mat();
Cv2.Resize(image_rgb, resize_image, new OpenCvSharp.Size(640, 640));

// 输入Tensor
// input_tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });
for (int y = 0; y < resize_image.Height; y++)
{
    for (int x = 0; x < resize_image.Width; x++)
    {
        input_tensor[0, 0, y, x] = resize_image.At<Vec3b>(y, x)[0] / 255f;
        input_tensor[0, 1, y, x] = resize_image.At<Vec3b>(y, x)[1] / 255f;
        input_tensor[0, 2, y, x] = resize_image.At<Vec3b>(y, x)[2] / 255f;
    }
}

//将 input_tensor 放入一个输入参数的容器,并指定名称
input_ontainer.Add(NamedOnnxValue.CreateFromTensor("images", input_tensor));

dt1 = DateTime.Now;
//运行 Inference 并获取结果
result_infer = onnx_session.Run(input_ontainer);
dt2 = DateTime.Now;

using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

namespace Onnx_Yolov8_Demo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";
        string image_path = "";
        string startupPath;
        string classer_path;
        DateTime dt1 = DateTime.Now;
        DateTime dt2 = DateTime.Now;
        string model_path;
        Mat image;
        SegmentationResult result_pro;
        Mat result_image;

        SessionOptions options;
        InferenceSession onnx_session;
        Tensor<float> input_tensor;
        List<NamedOnnxValue> input_ontainer;
        IDisposableReadOnlyCollection<DisposableNamedOnnxValue> result_infer;
        DisposableNamedOnnxValue[] results_onnxvalue;

        Tensor<float> result_tensors_det;
        Tensor<float> result_tensors_proto;

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = fileFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return;
            pictureBox1.Image = null;
            image_path = ofd.FileName;
            pictureBox1.Image = new Bitmap(image_path);
            textBox1.Text = "";
            image = new Mat(image_path);
            pictureBox2.Image = null;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (image_path == "")
            {
                return;
            }

            // 配置图片数据
            image = new Mat(image_path);
            int max_image_length = image.Cols > image.Rows ? image.Cols : image.Rows;
            Mat max_image = Mat.Zeros(new OpenCvSharp.Size(max_image_length, max_image_length), MatType.CV_8UC3);
            Rect roi = new Rect(0, 0, image.Cols, image.Rows);
            image.CopyTo(new Mat(max_image, roi));

            float[] det_result_array = new float[8400 * 116];
            float[] proto_result_array = new float[32 * 160 * 160];
            float[] factors = new float[4];
            factors[0] = factors[1] = (float)(max_image_length / 640.0);
            factors[2] = image.Rows;
            factors[3] = image.Cols;

            // 将图片转为RGB通道
            Mat image_rgb = new Mat();
            Cv2.CvtColor(max_image, image_rgb, ColorConversionCodes.BGR2RGB);
            Mat resize_image = new Mat();
            Cv2.Resize(image_rgb, resize_image, new OpenCvSharp.Size(640, 640));

            // 输入Tensor
            // input_tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });
            for (int y = 0; y < resize_image.Height; y++)
            {
                for (int x = 0; x < resize_image.Width; x++)
                {
                    input_tensor[0, 0, y, x] = resize_image.At<Vec3b>(y, x)[0] / 255f;
                    input_tensor[0, 1, y, x] = resize_image.At<Vec3b>(y, x)[1] / 255f;
                    input_tensor[0, 2, y, x] = resize_image.At<Vec3b>(y, x)[2] / 255f;
                }
            }

            //将 input_tensor 放入一个输入参数的容器,并指定名称
            input_ontainer.Add(NamedOnnxValue.CreateFromTensor("images", input_tensor));

            dt1 = DateTime.Now;
            //运行 Inference 并获取结果
            result_infer = onnx_session.Run(input_ontainer);

            dt2 = DateTime.Now;

            // 将输出结果转为DisposableNamedOnnxValue数组
            results_onnxvalue = result_infer.ToArray();

            // 读取第一个节点输出并转为Tensor数据
            result_tensors_det = results_onnxvalue[0].AsTensor<float>();
            result_tensors_proto = results_onnxvalue[1].AsTensor<float>();

            det_result_array = result_tensors_det.ToArray();
            proto_result_array = result_tensors_proto.ToArray();

            resize_image.Dispose();
            image_rgb.Dispose();

            result_pro = new SegmentationResult(classer_path, factors);
            result_image = result_pro.draw_result(result_pro.process_result(det_result_array, proto_result_array), image.Clone());

            if (!result_image.Empty())
            {
                pictureBox2.Image = new Bitmap(result_image.ToMemoryStream());
                textBox1.Text = "推理耗时:" + (dt2 - dt1).TotalMilliseconds + "ms";
            }
            else
            {
                textBox1.Text = "无信息";
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            startupPath = System.Windows.Forms.Application.StartupPath;
            model_path = startupPath + "\\yolov8n-seg.onnx";
            classer_path = startupPath + "\\yolov8-detect-lable.txt";

            // 创建输出会话,用于输出模型读取信息
            options = new SessionOptions();
            options.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO;
            // 设置为CPU上运行
            options.AppendExecutionProvider_CPU(0);

            // 创建推理模型类,读取本地模型文件
            onnx_session = new InferenceSession(model_path, options);//model_path 为onnx模型文件的路径

            // 输入Tensor
            input_tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });

            // 创建输入容器
            input_ontainer = new List<NamedOnnxValue>();

        }
    }
}

下载 

完整Demo下载

exe程序下载

<think>我们参考引用[1]和引用[3]的内容,它们都涉及使用OpenCV部署YOLO模型的实例分割。特别是引用[1]提到使用纯OpenCV部署YOLOv11-seg实例分割onnx模型,引用[3]则提供了使用OpenCV dnn模块加载YOLOv8实例分割onnx模型的例子。我们可以借鉴这些方法来实现YOLOv11的实例分割部署。 步骤概述: 1. 准备ONNX模型:首先需要获得YOLOv11-segONNX格式模型文件。 2. 使用OpenCV的DNN模块加载模型。 3. 预处理输入图像:将图像转换为模型输入所需的格式(例如,调整大小、归一化、转换为blob等)。 4. 运行模型推理。 5. 后处理输出:解析模型输出的两个部分(检测框和分割掩码),并应用非极大值抑制(NMS)等后处理步骤。 具体实现步骤: 1. 加载模型: ```cpp #include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> cv::dnn::Net net = cv::dnn::readNetFromONNX("yolov11-seg.onnx"); // 如果有GPU,可以设置后端为CUDA net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); ``` 2. 图像预处理: ```cpp cv::Mat image = cv::imread("image.jpg"); cv::Mat blob; // 假设模型输入大小为640x640 cv::Size input_size(640, 640); // 将图像转换为blob,并进行归一化(0-1范围),注意YOLO通常使用0-1归一化 cv::dnn::blobFromImage(image, blob, 1.0 / 255.0, input_size, cv::Scalar(), true, false); ``` 3. 设置模型输入并执行推理: ```cpp net.setInput(blob); // 运行前向传播,获取输出 // 注意:YOLOv11-seg的输出可能有两个:一个是检测输出(包含框、置信度、类别),另一个是分割掩码输出 std::vector<cv::Mat> outputs; net.forward(outputs, net.getUnconnectedOutLayersNames()); ``` 4. 后处理: 后处理是实例分割中最复杂的部分。YOLO的实例分割模型通常输出两个部分: - 检测输出:形状为[1, num_detections, 85](对于YOLOv885=4+1+80,即4个坐标,1个置信度,80个类别)。但YOLOv11可能类似,不过具体输出维度需要根据模型确定。 - 分割输出:通常是掩码原型(prototype masks),需要与检测输出的掩码系数(mask coefficients)结合。 具体步骤: a. 解析检测输出,得到检测框、置信度和类别。 b. 应用置信度阈值和NMS。 c. 处理分割输出:将检测框对应的掩码系数与掩码原型相乘,然后通过sigmoid函数得到每个实例的掩码。 注意:由于YOLOv11的具体输出结构可能和YOLOv8有所不同,我们需要根据模型的实际输出进行调整。 这里我们假设输出有两个: - 第一个输出`outputs[0]`是检测输出,形状为[1, num_detections, 85](这里85=4+1+80,但实际类别数可能不同,需要根据模型调整) - 第二个输出`outputs[1]`是掩码原型,形状为[1, 32, 160, 160](这个维度可能因模型而异) 后处理代码示例(基于YOLOv8的实例分割后处理,需要根据YOLOv11调整): ```cpp // 假设outputs[0]是检测输出,outputs[1]是掩码原型 cv::Mat detection_output = outputs[0]; cv::Mat mask_protos = outputs[1]; // 将检测输出转换为一个矩阵,每一行是一个检测结果 cv::Mat det_mat(detection_output.size[1], detection_output.size[2], CV_32F, detection_output.ptr<float>()); // 存储最终的检测结果 std::vector<cv::Rect> boxes; std::vector<float> confidences; std::vector<int> class_ids; std::vector<cv::Mat> masks; // 遍历所有检测结果 for (int i = 0; i < det_mat.rows; i++) { float confidence = det_mat.at<float>(i, 4); // 置信度在索引4的位置 if (confidence > confidence_threshold) { // 提取类别概率(从第5个开始) cv::Mat scores = det_mat.row(i).colRange(5, 85); // 假设80个类别 cv::Point class_id_point; double max_class_score; cv::minMaxLoc(scores, 0, &max_class_score, 0, &class_id_point); if (max_class_score > class_threshold) { // 提取框坐标(cx, cy, w, h) float cx = det_mat.at<float>(i, 0); float cy = det_mat.at<float>(i, 1); float w = det_mat.at<float>(i, 2); float h = det_mat.at<float>(i, 3); // 转换为左上角坐标 int left = static_cast<int>((cx - w / 2) * image.cols / input_size.width); // 注意:需要将归一化坐标转换回原图坐标 int top = static_cast<int>((cy - h / 2) * image.rows / input_size.height); int width = static_cast<int>(w * image.cols / input_size.width); int height = static_cast<int>(h * image.rows / input_size.height); boxes.push_back(cv::Rect(left, top, width, height)); confidences.push_back(confidence * max_class_score); class_ids.push_back(class_id_point.x); // 提取掩码系数(假设在85之后,有32个系数) cv::Mat mask_coefficients = det_mat.row(i).colRange(85, 85+32); // 32个系数 // 计算掩码:mask = sigmoid(mask_coefficients * mask_protos) // 注意:mask_protos的维度是[1,32,160,160],我们需要先调整mask_coefficients的维度为[32,1],然后与mask_protos(调整为[32, 160*160])相乘 cv::Mat mask_weights = mask_coefficients.reshape(1, 32); // 32x1 cv::Mat mask_protos_reshaped = mask_protos.reshape(1, 32); // 32x(160*160) cv::Mat mask = mask_weights.t() * mask_protos_reshaped; // 1x(160*160) // 应用sigmoid cv::exp(-mask, mask); mask = 1.0 / (1.0 + mask); // 将mask调整为160x160 cv::Mat mask_160 = mask.reshape(1, 160); // 将掩码缩放到原始图像大小(注意:这里需要根据检测框的位置进行裁剪和调整) // 首先,将掩码调整到检测框的大小(在原始图像中的大小) cv::Mat mask_roi; cv::resize(mask_160, mask_roi, cv::Size(width, height)); // 然后,应用阈值(例如0.5)进行二值化 cv::threshold(mask_roi, mask_roi, 0.5, 255, cv::THRESH_BINARY); masks.push_back(mask_roi); } } } // 应用非极大值抑制 std::vector<int> indices; cv::dnn::NMSBoxes(boxes, confidences, confidence_threshold, nms_threshold, indices); ``` 5. 绘制结果: 遍历NMS后的索引,绘制检测框和掩码。 注意:上述代码仅为示例,实际应用中需要根据YOLOv11-seg模型的输出结构进行调整。特别是输出张量的维度和顺序,以及分割部分的处理方式。 由于YOLOv11的具体结构可能和YOLOv8有所不同,建议先查看模型输出的维度信息,然后调整后处理代码。 相关问题: 1. 如何将YOLOv11的PyTorch模型转换为ONNX格式? 2. 在C++中如何调试OpenCV DNN模块加载ONNX模型时的错误? 3. 如何优化YOLOv11实例分割模型在C++中的推理速度?</think>### 使用C++实现基于ONNX模型的YOLOv11实例分割 #### 实现步骤 1. **环境准备** - OpenCV 4.5+(带DNN模块) - ONNX Runtime 1.8+(可选,用于备用方案) - YOLOv11-seg ONNX模型文件(需提前转换获得) 2. **模型加载** ```cpp #include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> cv::dnn::Net load_model(const std::string& model_path) { cv::dnn::Net net = cv::dnn::readNetFromONNX(model_path); net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); // 使用CUDA加速 net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); return net; } ``` 3. **预处理输入** ```cpp cv::Mat preprocess(const cv::Mat& image, int input_size = 640) { cv::Mat blob; cv::dnn::blobFromImage(image, blob, 1/255.0, cv::Size(input_size, input_size), cv::Scalar(), true, false); return blob; } ``` 4. **模型推理** ```cpp std::vector<cv::Mat> run_inference(cv::dnn::Net& net, const cv::Mat& blob) { net.setInput(blob); std::vector<cv::Mat> outputs; // YOLOv11-seg输出包含检测框和分割掩码 net.forward(outputs, {"output0", "output1"}); return outputs; } ``` 5. **后处理(核心步骤)** ```cpp struct Detection { cv::Rect box; int class_id; float confidence; cv::Mat mask; }; std::vector<Detection> post_process( const std::vector<cv::Mat>& outputs, const cv::Size& orig_size, float conf_thresh = 0.5, float iou_thresh = 0.5 ) { // 解析检测输出 (output0: [1,116,8400]) cv::Mat detections = outputs[0].reshape(1, outputs[0].size[2]); // 解析分割输出 (output1: [1,32,160,160]) cv::Mat mask_protos = outputs[1]; std::vector<Detection> results; std::vector<cv::Rect> boxes; std::vector<float> confidences; std::vector<int> class_ids; std::vector<cv::Mat> masks; // 遍历所有检测结果 for (int i = 0; i < detections.rows; ++i) { cv::Mat scores = detections.row(i).colRange(4, 84); cv::Point class_id; double confidence; cv::minMaxLoc(scores, 0, &confidence, 0, &class_id); if (confidence > conf_thresh) { // 解析边界框坐标 (cx,cy,w,h) float cx = detections.at<float>(i, 0); float cy = detections.at<float>(i, 1); float w = detections.at<float>(i, 2); float h = detections.at<float>(i, 3); // 转换为像素坐标 int left = static_cast<int>((cx - w/2) * orig_size.width); int top = static_cast<int>((cy - h/2) * orig_size.height); int width = static_cast<int>(w * orig_size.width); int height = static_cast<int>(h * orig_size.height); // 提取掩码系数 (32维向量) cv::Mat mask_coeff = detections.row(i).colRange(84, 116); // 计算实例分割掩码 cv::Mat mask = mask_coeff * mask_protos.reshape(1, 32); cv::Mat seg_mask; cv::resize(mask.reshape(1, 160), seg_mask, orig_size); cv::threshold(seg_mask, seg_mask, 0.5, 255, cv::THRESH_BINARY); // 保存结果 boxes.emplace_back(left, top, width, height); confidences.push_back(confidence); class_ids.push_back(class_id.x); masks.push_back(seg_mask); } } // 应用非极大值抑制(NMS) std::vector<int> indices; cv::dnn::NMSBoxes(boxes, confidences, conf_thresh, iou_thresh, indices); // 生成最终检测结果 for (int idx : indices) { Detection res; res.box = boxes[idx]; res.confidence = confidences[idx]; res.class_id = class_ids[idx]; res.mask = masks[idx]; results.push_back(res); } return results; } ``` 6. **结果可视化** ```cpp void visualize(cv::Mat& image, const std::vector<Detection>& results) { for (const auto& res : results) { // 绘制边界框 cv::rectangle(image, res.box, cv::Scalar(0,255,0), 2); // 应用分割掩码 cv::Mat colored_mask = cv::Mat::zeros(image.size(), CV_8UC3); colored_mask.setTo(cv::Scalar(0,255,0), res.mask); cv::addWeighted(image, 0.7, colored_mask, 0.3, 0, image); } } ``` #### 关键注意事项 1. **模型转换**:需将PyTorch训练的YOLOv11-seg模型转为ONNX格式,确保包含分割头输出[^1] 2. **输出层适配**:YOLOv11的输出维度可能因版本而异,需通过`net.getUnconnectedOutLayersNames()`验证实际输出层名称 3. **掩码计算**:分割结果通过矩阵乘法 $ \text{mask} = \text{mask\_coeff} \times \text{mask\_protos} $ 生成,需调整阈值获取二值掩码[^3] 4. **性能优化**: - 使用半精度推理:在模型转换时添加`--half`参数 - 启用TensorRT后端:通过ONNX Runtime加速(需额外配置) - 批处理支持:修改blob生成逻辑支持多图输入 #### 完整流程示例 ```cpp int main() { // 1. 加载模型 cv::dnn::Net net = load_model("yolov11-seg.onnx"); // 2. 读取并预处理图像 cv::Mat image = cv::imread("test.jpg"); cv::Mat blob = preprocess(image); // 3. 执行推理 std::vector<cv::Mat> outputs = run_inference(net, blob); // 4. 后处理 std::vector<Detection> results = post_process(outputs, image.size()); // 5. 可视化结果 visualize(image, results); cv::imwrite("result.jpg", image); return 0; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天代码码天天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值