使用Emgu.CV.Dnn SegmentationModel调用YOLOv8分割模型报错排查
问题排查与解决方案
异常原因
Emgu.CV的SegmentationModel.Segment方法是为传统单输出分割模型(如DeepLab、FCN等)设计的,这类模型仅输出掩码张量。但YOLOv8分割的ONNX模型会输出两个独立张量:
- 第一个张量:检测结果(包含边界框、置信度、类别ID)
- 第二个张量:掩码原型(用于生成目标掩码)
调用Segment方法时,它会强制校验模型输出数量是否为1,而YOLOv8分割模型实际输出是2个,因此触发outs.size() == 1异常。而Predict方法仅返回所有输出张量,不做格式校验,所以能正常运行。
解决方案
放弃使用Segment方法,直接通过Predict获取输出后手动计算目标掩码,步骤如下:
- 从
Predict的输出中分离检测结果和掩码原型 - 解析检测结果,筛选置信度达标的目标
- 用目标对应的掩码系数与掩码原型做矩阵运算,生成目标掩码
- 将掩码缩放至原图尺寸并可视化
修改后的示例代码
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
相关产品推荐
相关产品推荐

