【问题标题】:Cannot get correct predictions from ONNX model from Customvision无法从 Customvision 的 ONNX 模型中获得正确的预测
【发布时间】:2021-07-19 08:16:41
【问题描述】:

我正在评估 customvision.ai 以训练图像分类模型,然后将该模型下载为将在 .Net Windows 窗体应用程序中使用的 onnx 文件。

我创建了一个新项目,上传了几张图片,标记了它们,并能够从 Customvision.ai 中的模型中获取预测。模型的准确性是可以接受的。 CustomVision 允许您将模型下载为可以部署在跨平台应用程序中的 ONNX 文件。就我而言,我计划在 Windows 窗体应用程序中部署和使用模型。

当我将模型下载为 onnx 时,我收到一个 zip 文件,其中包含 .onnx 文件和其他一些文件。

其中一个文件是Metadata_properties.json,内容如下:

{
    "CustomVision.Metadata.AdditionalModelInfo": "",
    "CustomVision.Metadata.Version": "1.2",
    "CustomVision.Postprocess.Method": "ClassificationMultiClass",
    "CustomVision.Postprocess.Yolo.Biases": "[]",
    "CustomVision.Postprocess.Yolo.NmsThreshold": "0.0",
    "CustomVision.Preprocess.CropHeight": "0",
    "CustomVision.Preprocess.CropMethod": "FullImageShorterSide",
    "CustomVision.Preprocess.CropWidth": "0",
    "CustomVision.Preprocess.MaxDimension": "0",
    "CustomVision.Preprocess.MaxScale": "0.0",
    "CustomVision.Preprocess.MinDimension": "0",
    "CustomVision.Preprocess.MinScale": "0.0",
    "CustomVision.Preprocess.NormalizeMean": "[0.0, 0.0, 0.0]",
    "CustomVision.Preprocess.NormalizeStd": "[1.0, 1.0, 1.0]",
    "CustomVision.Preprocess.ResizeMethod": "Stretch",
    "CustomVision.Preprocess.TargetHeight": "300",
    "CustomVision.Preprocess.TargetWidth": "300",
    "Image.BitmapPixelFormat": "Rgb8",
    "Image.ColorSpaceGamma": "SRGB",
    "Image.NominalPixelRange": "Normalized_0_1"
}

我从这个文件中了解到,将提供给模型进行推理的最终张量需要拉伸调整为 300x300,在 0 和 1 之间进行归一化,平均值设置为零,标准差设置为 1。为了为了在我的代码中使用这个模型,以下是我从各种在线资源中收集的内容:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
//using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.ML.OnnxRuntime;
using System.IO;

namespace TestONNXRunner
{
   

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            RunModel();
        }

        public void RunModel()
        {
            // Read paths
            string modelFilePath = @"C:\ImageMLProjects\MarbleImagesDataset\OnnxModel\onnxdataset\model.onnx";
            var LabelsDict = GetLabelMap(@"C:\ImageMLProjects\MarbleImagesDataset\OnnxModel\onnxdataset\labels.txt");


            string imageFilePath = @"";
            OpenFileDialog openFileDialog1 = new OpenFileDialog
            {
                InitialDirectory = @"C:\",
                Title = "Browse Image Files",

                CheckFileExists = true,
                CheckPathExists = true,

                FilterIndex = 2,
                RestoreDirectory = true,

                ReadOnlyChecked = true,
                ShowReadOnly = true
            };

            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                imageFilePath = openFileDialog1.FileName;

                // Read image
                using Image<Rgb24> image = Image.Load<Rgb24>(imageFilePath);

                // Resize image
                image.Mutate(x =>
                {
                    x.Resize(new ResizeOptions
                    {
                        Size = new SixLabors.ImageSharp.Size(300, 300),
                        Mode = ResizeMode.Stretch
                    });
                });

                // Preprocess image
                Tensor<float> input = new DenseTensor<float>(new[] { 1, 3, image.Height, image.Width });
                var mean = new[] { 0f, 0f, 0f };
                var stddev = new[] { 1f, 1f, 1f };
                for (int y = 0; y < image.Height; y++)
                {
                    Span<Rgb24> pixelSpan = image.GetPixelRowSpan(y);
                    for (int x = 0; x < image.Width; x++)
                    {
                        input[0, 0, x, y] = ((pixelSpan[x].R / 255f) - mean[0]) / stddev[0];
                        input[0, 1, x, y] = ((pixelSpan[x].G / 255f) - mean[1]) / stddev[1];
                        input[0, 2, x, y] = ((pixelSpan[x].B / 255f) - mean[2]) / stddev[2];
                    }
                }

                // Setup inputs
                var inputs = new List<NamedOnnxValue>
                {
                    NamedOnnxValue.CreateFromTensor("data", input)
                };

                // Run inference
                //int gpuDeviceId = 0; // The GPU device ID to execute on
                //var session = new InferenceSession("model.onnx", SessionOptions.MakeSessionOptionWithCudaProvider(gpuDeviceId));
                using var session = new InferenceSession(modelFilePath);
                using IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results = session.Run(inputs);

                // Postprocess to get softmax vector
                IEnumerable<float> output = results.First().AsEnumerable<float>();
                float sum = output.Sum(x => (float)Math.Exp(x));
                IEnumerable<float> softmax = output.Select(x => (float)Math.Exp(x) / sum);

                // Extract top 10 predicted classes
                IEnumerable<Prediction> top10 = softmax.Select((x, i) => new Prediction { Label = LabelsDict[i], Confidence = x })
                                   .OrderByDescending(x => x.Confidence)
                                   .Take(10);

                // Print results to console
                Console.WriteLine("Top 10 predictions for ResNet50 v2...");
                Console.WriteLine("--------------------------------------------------------------");
                foreach (var t in top10)
                {
                    Console.WriteLine($"Label: {t.Label}, Confidence: {t.Confidence}");
                }
            }
        }


        public Dictionary<int, string> GetLabelMap(string LabelMapFile)
        {
            Dictionary<int, string> labelsDict = new Dictionary<int, string>();
            if(File.Exists(LabelMapFile))
            {
                string data = File.ReadAllText(LabelMapFile);

                string[] labels = data.Split('\n');
                int i = 0;
                foreach (var label in labels)
                {
                    labelsDict.Add(i, label);
                    i++;
                }
            }
            return labelsDict;
        }
        internal class Prediction
        {
            public string Label { get; set; }
            public float Confidence { get; set; }
        }


      
    }
}

现在有什么问题?

我没有发现任何错误,无论我使用什么图像进行推理,我都会得到相同的结果。

问题

  1. 我应该以不同的方式构造张量吗?我不确定这是否与张量的结构方式有关。
  2. Github 上 Customvision 页面的最后一次更新是几年前,是否建议在 2021 年将 CustomVision 用于生产用途?我应该寻找其他东西吗?我们的想法是能够使用低/零代码方法构建/训练高质量的图像分类模型,然后将模型部署到本地计算机上以用于低延迟应用程序。

在这方面的任何帮助将不胜感激

【问题讨论】:

    标签: azureml microsoft-custom-vision onnxruntime


    【解决方案1】:

    我终于能够利用从 Azure Custom Vision 导出的 Onnx 模型从图像分类模型中获得分数。为此,我使用 ML.NET 和 OnnxRuntime。代码完美运行,下面的示例可用于使用 .Net 控制台应用程序和从 CustomVision 导出的 .ONNX 模型对存储在文件夹中的图像进行大规模推理。

    public class OnnxModelScorer
    {
    
    public class ImageInputData
    {
        [ImageType(300, 300)]
        public Bitmap Image { get; set; }
    }
    
    public class ImagePrediction
    {
            
        [ColumnName("model_output")]
        public float[] PredictedLabels;
    }
    
    PredictionEngine<ImageInputData, ImagePrediction> predictionEngine;
    ModelMetadataPropertiesClass modelprops;
    Dictionary<int, string> ModelLabels = new Dictionary<int, string>();
    
    public void SetupPredictionEngine(string modelFolderPath, out string errors)
    {
        errors = "";
        predictionEngine = null;
        try
        {
            var mlContext = new MLContext();
    
            modelprops = LoadProperties(modelFolderPath + "metadata_properties.json", out string error);
    
            var pipeline = mlContext.Transforms
                            .ResizeImages("image", modelprops.CustomVisionPreprocessTargetWidth, modelprops.CustomVisionPreprocessTargetHeight, nameof(ImageInputData.Image), ImageResizingEstimator.ResizingKind.Fill)
                            .Append(mlContext.Transforms.ExtractPixels("data", "image"))
                            .Append(mlContext.Transforms.ApplyOnnxModel("model_output", "data", modelFolderPath + @"model.onnx"));
    
            var data = mlContext.Data.LoadFromEnumerable(new List<ImageInputData>());
            var model = pipeline.Fit(data);
    
            predictionEngine = mlContext.Model.CreatePredictionEngine<ImageInputData, ImagePrediction>(model);
    
            string[] labels = File.ReadAllText(modelFolderPath + @"labels.txt").Split('\n');
    
            int i = 0;
            foreach (var label in labels)
            {
                ModelLabels.Add(i, label);
                i++;
            }
        }
        catch (Exception ex)
        {
            errors = "Model Loading Failed: " + ex.ToString();
        }
            
    }
    
    public PredictionResultClass GetModelPrediction(Bitmap sample, out string error)
    {
        PredictionResultClass pr = new PredictionResultClass();
        error = "";
        if (predictionEngine != null)
        {
            var input = new ImageInputData { Image = sample };
    
            var prediction = predictionEngine.Predict(input);
            Dictionary<int, PredictionResultClass> predictionResults = new Dictionary<int, PredictionResultClass>();
            int indexofMaxProb = -1;
            float maxProbability = 0;
            for (int i = 0; i < prediction.PredictedLabels.Count(); i++)
            {
                predictionResults.Add(i,new PredictionResultClass() { Label = ModelLabels[i], probability = prediction.PredictedLabels[i] });
    
                if(prediction.PredictedLabels[i]>maxProbability)
                {
                    maxProbability = prediction.PredictedLabels[i];
                    indexofMaxProb = i;
                }
            }
    
            pr = predictionResults[indexofMaxProb];
    
        }
        else error = "Prediction Engine Not initialized";
    
        return pr;
    }
    public class PredictionResultClass
    {
        public string Label = "";
        public float probability = 0;
    }
    
    public void ModelMassTest(string samplesfolder)
    {
            
        string[] inputfiles = Directory.GetFiles(samplesfolder);
        List<double> analysistimes = new List<double>();
        foreach (var fl in inputfiles)
        {
    
            //Emgu.CV.Image<Emgu.CV.Structure.Bgr, byte> Img = new Emgu.CV.Image<Emgu.CV.Structure.Bgr, byte>(fl);
            // Img.ROI = JsonConvert.DeserializeObject<Rectangle>("\"450, 288, 420, 1478\"");
            // string savePath = @"C:\ImageMLProjects\Tresseme200Ml Soiling Experiment\Tresseme200MlImages\ROIApplied\Bad\" + Path.GetFileName(fl);
            // Img.Save(savePath);
    
            //Bitmap bitmap = Emgu.CV.BitmapExtension.ToBitmap(Img); // your source of a bitmap
            Bitmap bitmap = new Bitmap(fl);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            var res =  GetModelPrediction(bitmap, out string error);
    
            sw.Stop();
            PrintResultsonConsole(res, Path.GetFileName(fl));
    
    
    
    
            Console.WriteLine($"Analysis Time(ms): {sw.ElapsedMilliseconds}");
            analysistimes.Add(sw.ElapsedMilliseconds);
    
        }
    
        if(analysistimes.Count()>0)
            Console.WriteLine($"Average Analysis Time(ms): {analysistimes.Average()}");
    }
    
    
    public static ModelMetadataPropertiesClass LoadProperties(string MetadatePropertiesFilepath, out string error)
    {
        string propertiesText = File.ReadAllText(MetadatePropertiesFilepath);
        error = "";
        ModelMetadataPropertiesClass mtp = new ModelMetadataPropertiesClass();
    
        try
        {
            mtp = JsonConvert.DeserializeObject<ModelMetadataPropertiesClass>(propertiesText);
        }
        catch (Exception ex)
        {
            error = ex.ToString();
            mtp = null;
        }
    
        return mtp;
    }
    public class ModelMetadataPropertiesClass
    {
        [JsonProperty("CustomVision.Metadata.AdditionalModelInfo")]
        public string CustomVisionMetadataAdditionalModelInfo { get; set; }
    
        [JsonProperty("CustomVision.Metadata.Version")]
        public string CustomVisionMetadataVersion { get; set; }
    
        [JsonProperty("CustomVision.Postprocess.Method")]
        public string CustomVisionPostprocessMethod { get; set; }
    
        [JsonProperty("CustomVision.Postprocess.Yolo.Biases")]
        public string CustomVisionPostprocessYoloBiases { get; set; }
    
        [JsonProperty("CustomVision.Postprocess.Yolo.NmsThreshold")]
        public string CustomVisionPostprocessYoloNmsThreshold { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.CropHeight")]
        public string CustomVisionPreprocessCropHeight { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.CropMethod")]
        public string CustomVisionPreprocessCropMethod { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.CropWidth")]
        public string CustomVisionPreprocessCropWidth { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.MaxDimension")]
        public string CustomVisionPreprocessMaxDimension { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.MaxScale")]
        public string CustomVisionPreprocessMaxScale { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.MinDimension")]
        public string CustomVisionPreprocessMinDimension { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.MinScale")]
        public string CustomVisionPreprocessMinScale { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.NormalizeMean")]
        public string CustomVisionPreprocessNormalizeMean { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.NormalizeStd")]
        public string CustomVisionPreprocessNormalizeStd { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.ResizeMethod")]
        public string CustomVisionPreprocessResizeMethod { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.TargetHeight")]
        public int CustomVisionPreprocessTargetHeight { get; set; }
    
        [JsonProperty("CustomVision.Preprocess.TargetWidth")]
        public int CustomVisionPreprocessTargetWidth { get; set; }
    
        [JsonProperty("Image.BitmapPixelFormat")]
        public string ImageBitmapPixelFormat { get; set; }
    
        [JsonProperty("Image.ColorSpaceGamma")]
        public string ImageColorSpaceGamma { get; set; }
    
        [JsonProperty("Image.NominalPixelRange")]
        public string ImageNominalPixelRange { get; set; }
    }
    
    
    public static void PrintResultsonConsole( PredictionResultClass pr,string  filePath)
    {
        var defaultForeground = Console.ForegroundColor;
        var labelColor = ConsoleColor.Magenta;
        var probColor = ConsoleColor.Blue;
        var exactLabel = ConsoleColor.Green;
        var failLabel = ConsoleColor.Red;
    
        Console.Write("ImagePath: ");
        Console.ForegroundColor = labelColor;
        Console.Write($"{Path.GetFileName(filePath)}");
        Console.ForegroundColor = defaultForeground;
    
        Console.ForegroundColor = defaultForeground;
        Console.Write(" predicted as ");
        Console.ForegroundColor = exactLabel;
        Console.Write($"{pr.Label}");
    
        Console.ForegroundColor = defaultForeground;
        Console.Write(" with probability ");
        Console.ForegroundColor = probColor;
        Console.Write(pr.probability);
        Console.ForegroundColor = defaultForeground;
        Console.WriteLine("");
    }
    }
    

    为了初始化预测引擎并使用 onnx 模型获得分数,我使用以下代码:

    static void Main(string[] args)
    {
    
    
    
    var onnxModelScorer = new OnnxModelScorer();
    
    onnxModelScorer.SetupPredictionEngine(@"C:\ModelFileFolder\OnnxModel\",out string error);
    
    // Download the onnx model zip file from CustomVision and extract all in same folder( the labels, metadata and onnx files are utilized to initialize the prediction engine)
    
    onnxModelScorer.ModelMassTest(@"C:\SampleImagesFolder\");
    //
    
            
    ConsoleHelpers.ConsolePressAnyKey();
    }
    

    仅供参考,重要的是,平均推理时间约为 40 毫秒,使用 Onnx DirectML 或 GPU 包无助于改善这一点。

    注意:这个答案是 onnxruntime Github repo、Stackoverflow 和 ML.NET 上的互联网资源中的各种示例的混合

    【讨论】:

      猜你喜欢
      • 2019-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-23
      • 2021-10-18
      • 1970-01-01
      • 2021-08-12
      相关资源
      最近更新 更多