0

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

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

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

其中一个文件是 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 页面的最后一次更新是几年前,CustomVision 是否推荐用于 2021 年的生产用途?我应该寻找其他东西吗?这个想法是能够使用低/零代码方法构建/训练高质量的图像分类模型,然后将模型部署到本地计算机上以用于低延迟应用程序。

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

4

1 回答 1

0

我终于能够利用从 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 上的互联网资源上的各种示例的混合

于 2021-12-21T17:08:58.273 回答