【问题标题】:How to impelement post-proccesing for yolo v3 or v4 onnx models in ML.Net如何在 ML.Net 中实现 yolo v3 或 v4 onnx 模型的后处理
【发布时间】:2021-02-01 01:52:36
【问题描述】:

我关注了this microsoft tutorial,没有问题。但我想将模型更改为 yolo v3 或 v4。我从onnx/models 获得了 YOLOv4 onnx 模型,并且能够获得 yolov4 onnx 模型的所有三个浮点输出数组,但问题在于后处理,我无法从这些输出中获得正确的边界框。

我在 microsoft tutorial src 代码中更改了所有内容,例如锚点、步幅、输出网格大小、一些功能和...以与 yolov4 兼容。但我无法得到正确的结果。 我用python implementation 检查了我所有的代码,但我不知道问题出在哪里。 有没有人有链接或知道如何使用 ML.Net 在 c# 中实现 yolo v3 或 v4 onnx 模型

任何帮助将不胜感激

【问题讨论】:

    标签: yolo ml.net onnx post-processing


    【解决方案1】:

    我认为将微软的教程从 YOLO v2 直接移植到 v3 是不可能的,因为它依赖于每个模型的输入和输出。

    作为旁注,我在this GitHub repo: 'YOLOv3MLNet' 中将另一个 YOLO v3 模型移植到 ML.Net。它包含一个功能齐全的 ML.Net 管道。

    我还在这里提供了此答案的代码:

    回到您的模型,我将以 YOLO v3(可在 onnx/models 存储库中获得)为例。可以在here找到一个很好的模型解释。

    第一个建议是使用Netron 查看模型。这样做,您将看到输入和输出层。他们还在 onnx/models 文档中描述了这些层。

    Netron's yolov3-10 screenshot

    (我在 Netron 中看到,这个特定的 YOLO v3 模型还通过执行非最大抑制步骤进行了一些后处​​理。)

    • 输入层名称:input_1, image_shape
    • 输出层名称:yolonms_layer_1/ExpandDims_1:0、yolonms_layer_1/ExpandDims_3:0、yolonms_layer_1/concat_2:0

    根据模型文档,输入形状为:

    调整大小的图像 (1x3x416x416) 原始图像大小 (1x2) 即 [image.size['1], image.size[0]]

    我们首先需要定义 ML.Net 的输入和输出类如下:

    public class YoloV3BitmapData
    {
        [ColumnName("bitmap")]
        [ImageType(416, 416)]
        public Bitmap Image { get; set; }
    
        [ColumnName("width")]
        public float ImageWidth => Image.Width;
    
        [ColumnName("height")]
        public float ImageHeight => Image.Height;
    }
    
    public class YoloV3Prediction
    {
        /// <summary>
        /// ((52 x 52) + (26 x 26) + 13 x 13)) x 3 = 10,647.
        /// </summary>
        public const int YoloV3BboxPredictionCount = 10_647;
    
        /// <summary>
        /// Boxes
        /// </summary>
        [ColumnName("yolonms_layer_1/ExpandDims_1:0")]
        public float[] Boxes { get; set; }
    
        /// <summary>
        /// Scores
        /// </summary>
        [ColumnName("yolonms_layer_1/ExpandDims_3:0")]
        public float[] Scores { get; set; }
    
        /// <summary>
        /// Concat
        /// </summary>
        [ColumnName("yolonms_layer_1/concat_2:0")]
        public int[] Concat { get; set; }
    }
    

    然后我们创建 ML.Net 管道并加载预测引擎:

    // Define scoring pipeline
    var pipeline = mlContext.Transforms.ResizeImages(inputColumnName: "bitmap", outputColumnName: "input_1", imageWidth: 416, imageHeight: 416, resizing: ResizingKind.IsoPad)
        .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input_1", outputAsFloatArray: true, scaleImage: 1f / 255f))
        .Append(mlContext.Transforms.Concatenate("image_shape", "height", "width"))
        .Append(mlContext.Transforms.ApplyOnnxModel(shapeDictionary: new Dictionary<string, int[]>() { { "input_1", new[] { 1, 3, 416, 416 } } },
                        inputColumnNames: new[]
                        {
                            "input_1",
                            "image_shape"
                        },
                        outputColumnNames: new[]
                        {
                            "yolonms_layer_1/ExpandDims_1:0",
                            "yolonms_layer_1/ExpandDims_3:0",
                            "yolonms_layer_1/concat_2:0"
                        },
                        modelFile: @"D:\yolov3-10.onnx"));
    
    // Fit on empty list to obtain input data schema
    var model = pipeline.Fit(mlContext.Data.LoadFromEnumerable(new List<YoloV3BitmapData>()));
    
    // Create prediction engine
    var predictionEngine = mlContext.Model.CreatePredictionEngine<YoloV3BitmapData, YoloV3Prediction>(model);
    

    注意:我们需要定义shapeDictionary 参数,因为它们在模型中没有完全定义。

    根据模型文档,输出形状为:

    模型有 3 个输出。 box:(1x'n_candidates'x4),所有anchor box的坐标,scores:(1x80x'n_candidates'),每类所有anchor box的分数,indices:('nbox'x3),从boxes tensor中选择的索引.选择的索引格式为(batch_index, class_index, box_index)。

    下面的函数会帮你处理结果,我留给你微调。

    public IReadOnlyList<YoloV3Result> GetResults(YoloV3Prediction prediction, string[] categories)
    {
        if (prediction.Concat == null || prediction.Concat.Length == 0)
        {
            return new List<YoloV3Result>();
        }
    
        if (prediction.Boxes.Length != YoloV3Prediction.YoloV3BboxPredictionCount * 4)
        {
            throw new ArgumentException();
        }
    
        if (prediction.Scores.Length != YoloV3Prediction.YoloV3BboxPredictionCount * categories.Length)
        {
            throw new ArgumentException();
        }
    
        List<YoloV3Result> results = new List<YoloV3Result>();
    
        // Concat size is 'nbox'x3 (batch_index, class_index, box_index)
        int resulstCount = prediction.Concat.Length / 3;
        for (int c = 0; c < resulstCount; c++)
        {
            var res = prediction.Concat.Skip(c * 3).Take(3).ToArray();
    
            var batch_index = res[0];
            var class_index = res[1];
            var box_index = res[2];
    
            var label = categories[class_index];
            var bbox = new float[]
            {
                prediction.Boxes[box_index * 4],
                prediction.Boxes[box_index * 4 + 1],
                prediction.Boxes[box_index * 4 + 2],
                prediction.Boxes[box_index * 4 + 3],
            };
            var score = prediction.Scores[box_index + class_index * YoloV3Prediction.YoloV3BboxPredictionCount];
    
            results.Add(new YoloV3Result(bbox, label, score));
        }
    
        return results;
    }
    

    在这个版本的模型中,它们是 80 个类(有关链接,请参见模型的 GitHub 文档)。

    你可以像这样使用上面的:

    // load image
    string imageName = "dog_cat.jpg";
    using (var bitmap = new Bitmap(Image.FromFile(Path.Combine(imageFolder, imageName))))
    {
        // predict
        var predict = predictionEngine.Predict(new YoloV3BitmapData() { Image = bitmap });
    
        var results = GetResults(predict, classesNames);
    
        // draw predictions
        using (var g = Graphics.FromImage(bitmap))
        {
            foreach (var result in results)
            {
                var y1 = result.BBox[0];
                var x1 = result.BBox[1];
                var y2 = result.BBox[2];
                var x2 = result.BBox[3];
    
                g.DrawRectangle(Pens.Red, x1, y1, x2-x1, y2-y1);
                using (var brushes = new SolidBrush(Color.FromArgb(50, Color.Red)))
                {
                    g.FillRectangle(brushes, x1, y1, x2 - x1, y2 - y1);
                }
    
                g.DrawString(result.Label + " " + result.Confidence.ToString("0.00"),
                             new Font("Arial", 12), Brushes.Blue, new PointF(x1, y1));
            }
    
            bitmap.Save(Path.Combine(imageOutputFolder, Path.ChangeExtension(imageName, "_processed" + Path.GetExtension(imageName))));
        }
    }
    

    您可以找到result example here。

    【讨论】:

    • 我在stackoverflow中看到的最好的答案??非常感谢
    • 你好。我已尝试遵循您制作的 YoloV5 解决方案。 yolo5_incl 分支,但代码中有错误。而且,使用您的输入数据(模式等)它仍然无法编译。但是一旦修改 ApplyOnnxModel 并添加 recursionLimit 就可以了。但结果只是一个充满盒子的图像。我可以看到绘制的所有标签和框的图像。有什么想法可以继续吗?我非常感谢您的支持。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多