You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Emgu.CV.Dnn SegmentationModel调用YOLOv8分割模型报错排查

问题排查与解决方案

异常原因

Emgu.CV的SegmentationModel.Segment方法是为传统单输出分割模型(如DeepLab、FCN等)设计的,这类模型仅输出掩码张量。但YOLOv8分割的ONNX模型会输出两个独立张量:

  • 第一个张量:检测结果(包含边界框、置信度、类别ID)
  • 第二个张量:掩码原型(用于生成目标掩码)

调用Segment方法时,它会强制校验模型输出数量是否为1,而YOLOv8分割模型实际输出是2个,因此触发outs.size() == 1异常。而Predict方法仅返回所有输出张量,不做格式校验,所以能正常运行。

解决方案

放弃使用Segment方法,直接通过Predict获取输出后手动计算目标掩码,步骤如下:

  1. 从Predict的输出中分离检测结果和掩码原型
  2. 解析检测结果,筛选置信度达标的目标
  3. 用目标对应的掩码系数与掩码原型做矩阵运算,生成目标掩码
  4. 将掩码缩放至原图尺寸并可视化

修改后的示例代码

try
{
    var img = CvInvoke.Imread(@"C:\data\1.jpg", ImreadModes.Color);
    var modelPath = @"C:\data\yolov8n-seg.onnx";
    var net = DnnInvoke.ReadNetFromONNX(modelPath);
    
    var mdl = new SegmentationModel(net);
    mdl.SetPreferableBackend(Emgu.CV.Dnn.Backend.OpenCV);
    mdl.SetPreferableTarget(Target.Cpu);
    mdl.SetInputSwapRB(true);
    mdl.SetInputScale(1.0 / 255.0); // YOLOv8需要归一化,原代码遗漏此步骤会影响精度
    
    VectorOfMat outputs = new VectorOfMat();
    mdl.Predict(img, outputs);

    // 分离YOLOv8分割的两个输出张量
    Mat detOutput = outputs[0]; // 形状: [1, 8400, 85]
    Mat protoOutput = outputs[1]; // 形状: [1, 32, 160, 160]

    // 解析检测结果:筛选置信度>0.5的目标
    float[] detData = new float[detOutput.Rows * detOutput.Cols];
    detOutput.CopyTo(detData);
    List<(RectangleF Box, float Confidence, int ClassId, float[] MaskCoeffs)> results = new List<(RectangleF, float, int, float[])>();

    for (int i = 0; i < detOutput.Rows; i++)
    {
        float conf = detData[i * 85 + 4];
        if (conf < 0.5f) continue;

        // 转换YOLOv8的x,y,w,h输出为左上角坐标+宽高
        float x = detData[i * 85];
        float y = detData[i * 85 + 1];
        float w = detData[i * 85 + 2];
        float h = detData[i * 85 + 3];
        RectangleF box = new RectangleF(x - w/2, y - h/2, w, h);

        // 获取置信度最高的类别ID
        float[] classScores = detData.Skip(i * 85 + 5).Take(80).ToArray();
        int classId = Array.IndexOf(classScores, classScores.Max());

        // 获取当前目标的32位掩码系数
        float[] maskCoeffs = detData.Skip(i * 85 + 5 + 80).Take(32).ToArray();

        results.Add((box, conf, classId, maskCoeffs));
    }

    // 生成并绘制目标掩码
    foreach (var result in results)
    {
        // 掩码系数与原型矩阵运算,通过sigmoid转换为0-1范围
        Mat maskProto = protoOutput.Reshape(1, 32);
        Mat coeffsMat = new Mat(1, 32, DepthType.Cv32F, result.MaskCoeffs);
        Mat maskMat = coeffsMat * maskProto;
        maskMat = maskMat.Reshape(1, 160);
        CvInvoke.Sigmoid(maskMat, maskMat);

        // 将掩码缩放至原图尺寸
        Mat resizedMask = new Mat();
        CvInvoke.Resize(maskMat, resizedMask, img.Size, interpolation: Inter.Cubic);

        // 二值化掩码并提取目标区域
        Mat roiMask = new Mat(resizedMask, new Rectangle((int)result.Box.X, (int)result.Box.Y, (int)result.Box.Width, (int)result.Box.Height));
        roiMask = roiMask > 0.5f;

        // 在原图上绘制掩码(随机颜色)
        Scalar color = new Scalar(Random.Shared.Next(0,255), Random.Shared.Next(0,255), Random.Shared.Next(0,255));
        img.SetTo(color, roiMask);
    }

    // 显示结果
    CvInvoke.Imshow("YOLOv8 Segmentation Result", img);
    CvInvoke.WaitKey(0);
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

注意事项

  • YOLOv8输入必须做归一化处理(SetInputScale(1.0/255.0)),原代码遗漏此步骤会导致检测精度大幅下降
  • 掩码二值化的阈值(0.5)可根据实际场景调整
  • 若追求更高性能,可改用Emgu.CV原生矩阵API替代部分数组操作

内容的提问来源于stack exchange,提问作者Noname

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:06:30