Teilen über


Lernprogramm: Erkennen von Objekten mithilfe von ONNX in ML.NET

Erfahren Sie, wie Sie ein vortrainiertes ONNX-Modell in ML.NET verwenden, um Objekte in Bildern zu erkennen.

Das Training eines Objekterkennungsmodells erfordert das Festlegen von Millionen von Parametern, eine große Menge bezeichneter Schulungsdaten und eine große Menge an Rechenressourcen (Hunderte von GPU-Stunden). Durch die Verwendung eines vorab trainierten Modells können Sie den Trainingsprozess abkürzen.

In diesem Tutorial erfahren Sie, wie:

  • Verstehen des Problems
  • Erfahren Sie, was ONNX ist und wie es mit ML.NET zusammenarbeitet.
  • Grundlegendes zum Modell
  • Wiederverwenden des vortrainierten Modells
  • Erkennen von Objekten mit einem geladenen Modell

Voraussetzungen

Übersicht über das ONNX-Objekterkennungsbeispiel

In diesem Beispiel wird eine .NET Core-Konsolenanwendung erstellt, die Objekte innerhalb eines Bilds mithilfe eines vordefinierten Deep Learning-ONNX-Modells erkennt. Der Code für dieses Beispiel finden Sie im Repository "dotnet/machinelearning-samples " auf GitHub.

Was ist die Objekterkennung?

Objekterkennung ist ein Computervisionsproblem. Während die Objekterkennung eng mit der Bildklassifizierung verknüpft ist, führt die Objekterkennung die Bildklassifizierung in einer differenzierteren Skalierung durch. Die Objekterkennung sucht und kategorisiert Entitäten innerhalb von Bildern. Objekterkennungsmodelle werden häufig mithilfe von Deep Learning und neuralen Netzwerken trainiert. Weitere Informationen finden Sie unter Deep Learning vs Machine Learning .

Verwenden Sie die Objekterkennung, wenn Bilder mehrere Objekte unterschiedlicher Typen enthalten.

Screenshots mit der Bildklassifizierung im Vergleich zur Objektklassifizierung.

Einige Anwendungsfälle für die Objekterkennung umfassen:

  • Autonome Fahrzeuge
  • Robotertechnik
  • Gesichtserkennung
  • Sicherheit am Arbeitsplatz
  • Objektzählung
  • Aktivitätserkennung

Auswählen eines Deep Learning-Modells

Deep Learning ist eine Teilmenge des maschinellen Lernens. Um Deep Learning-Modelle zu trainieren, sind große Datenmengen erforderlich. Muster in den Daten werden durch eine Reihe von Ebenen dargestellt. Die Beziehungen in den Daten werden als Verbindungen zwischen den Ebenen codiert, die Gewichtungen enthalten. Je höher die Gewichtung, desto stärker die Beziehung. Zusammen werden diese Reihe von Schichten und Verbindungen als künstliche neurale Netzwerke bezeichnet. Je mehr Ebenen in einem Netzwerk, desto tiefer ist es, was es zu einem tiefen neuralen Netzwerk macht.

Es gibt verschiedene Arten von neuronalen Netzwerken, die am häufigsten Multischichtes Perceptron (MLP), Konvolutionales Neuronales Netzwerk (CNN) und Wiederkehrendes Neuronales Netzwerk (RNN). Am einfachsten ist der MLP, der eine Reihe von Eingaben einem Satz von Ausgaben zuordnet. Dieses neurale Netzwerk ist gut, wenn die Daten keine räumliche oder zeitbezogene Komponente aufweisen. Der CNN verwendet konvolutionale Ebenen, um räumliche Informationen zu verarbeiten, die in den Daten enthalten sind. Ein guter Anwendungsfall für CNNs ist die Bildverarbeitung, um das Vorhandensein eines Features in einem Bereich eines Bilds zu erkennen (z. B. gibt es eine Nase in der Mitte eines Bilds?). Schließlich ermöglichen RNNs die Verwendung eines persistenten Speicherzustands als Eingabe. RNNs werden für Zeitreihenanalysen verwendet, bei denen die sequenzielle Anordnung und der Kontext von Ereignissen wichtig sind.

Grundlegendes zum Modell

Objekterkennung ist eine Bildverarbeitungsaufgabe. Daher sind die meisten Deep-Learning-Modelle, die trainiert wurden, um dieses Problem zu lösen, Convolutional Neural Networks (CNNs). Das in diesem Lernprogramm verwendete Modell ist das Tiny YOLOv2-Modell, eine kompaktere Version des YOLOv2-Modells, das im Papier beschrieben wird: "YOLO9000: Better, Faster, Stronger" von Redmon und Farhadi. Tiny YOLOv2 wird auf dem Pascal VOC-Dataset trainiert und besteht aus 15 Schichten, die 20 verschiedene Klassen von Objekten vorhersagen können. Da Tiny YOLOv2 eine komprimierte Version des ursprünglichen YOLOv2-Modells ist, wird ein Kompromiss zwischen Geschwindigkeit und Genauigkeit hergestellt. Die verschiedenen Ebenen, aus denen das Modell besteht, können mithilfe von Tools wie Netron visualisiert werden. Bei der Prüfung des Modells würde eine Zuordnung der Verbindungen zwischen allen Ebenen erzielt, aus denen das neurale Netzwerk besteht, wobei jede Ebene den Namen der Ebene zusammen mit den Dimensionen der jeweiligen Eingabe/Ausgabe enthalten würde. Die Zum Beschreiben der Eingaben und Ausgaben des Modells verwendeten Datenstrukturen werden als Tensoren bezeichnet. Tensoren können als Container betrachtet werden, die Daten in N-Dimensionen speichern. Im Fall von Tiny YOLOv2 ist image der Name der Eingabeschicht und erwartet einen Tensor von Dimensionen 3 x 416 x 416. Der Name der Ausgabeebene ist grid und generiert einen Ausgabe-Tensor von Dimensionen 125 x 13 x 13.

Eingabeebene, die in ausgeblendete Ebenen aufgeteilt wird, dann Ausgabeebene

Das YOLO-Modell verwendet ein Bild 3(RGB) x 416px x 416px. Das Modell verwendet diese Eingabe und übergibt sie über die verschiedenen Ebenen, um eine Ausgabe zu erzeugen. Die Ausgabe teilt das Eingabebild in ein 13 x 13 Raster auf, wobei jede Zelle im Raster aus 125 Werten besteht.

Was ist ein ONNX-Modell?

Der Open Neural Network Exchange (ONNX) ist ein Open Source-Format für KI-Modelle. ONNX unterstützt die Interoperabilität zwischen Frameworks. Dies bedeutet, dass Sie ein Modell in einem der vielen gängigen Machine Learning Frameworks wie PyTorch trainieren, es in das ONNX-Format konvertieren und das ONNX-Modell in einem anderen Framework wie ML.NET verwenden können. Weitere Informationen finden Sie auf der ONNX-Website.

Diagramm der verwendeten ONNX-unterstützten Formate.

Das vortrainierte Tiny YOLOv2-Modell wird im ONNX-Format gespeichert, eine serialisierte Darstellung der Schichten und gelernten Muster dieser Schichten. In ML.NET wird die Interoperabilität mit ONNX mit den ImageAnalyticsOnnxTransformer NuGet-Paketen erreicht. Das ImageAnalytics Paket enthält eine Reihe von Transformationen, die ein Bild aufnehmen und in numerische Werte codieren können, die als Eingabe in eine Vorhersage- oder Schulungspipeline verwendet werden können. Das OnnxTransformer Paket nutzt die ONNX-Runtime zum Laden eines ONNX-Modells und verwendet es, um Vorhersagen basierend auf der bereitgestellten Eingabe zu erstellen.

Datenfluss der ONNX-Datei in die ONNX-Runtime.

Einrichten des .NET-Konsolenprojekts

Nachdem Sie nun ein allgemeines Verständnis darüber haben, was ONNX ist und wie Tiny YOLOv2 funktioniert, ist es an der Zeit, die Anwendung zu erstellen.

Erstellen einer Konsolenanwendung

  1. Erstellen Sie eine C# -Konsolenanwendung namens "ObjectDetection". Klicken Sie auf die Schaltfläche Weiter .

  2. Wählen Sie .NET 8 als zu verwendende Framework aus. Klicken Sie auf die Schaltfläche " Erstellen ".

  3. Installieren Sie das Microsoft.ML NuGet-Paket:

    Hinweis

    In diesem Beispiel wird die neueste stabile Version der erwähnten NuGet-Pakete verwendet, sofern nichts anderes angegeben ist.

    • Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf Ihr Projekt, und wählen Sie "NuGet-Pakete verwalten" aus.
    • Wählen Sie "nuget.org" als Paketquelle aus, wählen Sie die Registerkarte "Durchsuchen" aus, suchen Sie nach Microsoft.ML.
    • Wählen Sie die Schaltfläche Installieren aus.
    • Wählen Sie im Dialogfeld "Vorschauänderungen" die Schaltfläche "OK" und dann im Dialogfeld "Lizenzakzeptanz" die Schaltfläche "Ich stimme zu", wenn Sie den Lizenzbedingungen für die aufgeführten Pakete zustimmen.
    • Wiederholen Sie diese Schritte für Microsoft.Windows.Compatibility, Microsoft.ML.ImageAnalytics, Microsoft.ML.OnnxTransformer und Microsoft.ML.OnnxRuntime.

Bereiten Sie Ihre Daten und das vortrainierte Modell vor.

  1. Laden Sie die Zip-Datei des Projektressourcenverzeichnisses herunter, und entpacken Sie sie.

  2. Kopieren Sie das assets Verzeichnis in Ihr ObjectDetection-Projektverzeichnis . Dieses Verzeichnis und seine Unterverzeichnisse enthalten die Bilddateien (mit Ausnahme des Tiny YOLOv2-Modells, das Sie im nächsten Schritt herunterladen und hinzufügen werden), die für dieses Lernprogramm erforderlich sind.

  3. Laden Sie das Tiny YOLOv2-Modell aus dem ONNX Model Zoo herunter.

  4. Kopieren Sie die model.onnx Datei in Ihr ObjectDetection-Projektverzeichnisassets\Model und benennen Sie sie in TinyYolo2_model.onnx um. Dieses Verzeichnis enthält das für dieses Lernprogramm erforderliche Modell.

  5. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf die dateien im Objektverzeichnis und unterverzeichnisse, und wählen Sie "Eigenschaften" aus. Ändern Sie unter Erweitert den Wert von In Ausgabeordner kopieren in Kopieren, wenn neuer.

Erstellen von Klassen und Definieren von Pfaden

Öffnen Sie die Program.cs Datei, und fügen Sie oben in der Datei die folgenden zusätzlichen using Direktiven hinzu:

using System.Drawing;
using System.Drawing.Drawing2D;
using ObjectDetection.YoloParser;
using ObjectDetection.DataStructures;
using ObjectDetection;
using Microsoft.ML;

Definieren Sie als Nächstes die Pfade der verschiedenen Ressourcen.

  1. Erstellen Sie zunächst die GetAbsolutePath Methode unten in der datei Program.cs .

    string GetAbsolutePath(string relativePath)
    {
        FileInfo _dataRoot = new FileInfo(typeof(Program).Assembly.Location);
        string assemblyFolderPath = _dataRoot.Directory.FullName;
    
        string fullPath = Path.Combine(assemblyFolderPath, relativePath);
    
        return fullPath;
    }
    
  2. Erstellen Sie dann unter den using Direktiven Felder, um den Speicherort Ihrer Objekte zu speichern.

    var assetsRelativePath = @"../../../assets";
    string assetsPath = GetAbsolutePath(assetsRelativePath);
    var modelFilePath = Path.Combine(assetsPath, "Model", "TinyYolo2_model.onnx");
    var imagesFolder = Path.Combine(assetsPath, "images");
    var outputFolder = Path.Combine(assetsPath, "images", "output");
    

Fügen Sie Ihrem Projekt ein neues Verzeichnis hinzu, um Ihre Eingabedaten und Vorhersageklassen zu speichern.

Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie dann "Neuen Ordner>" aus. Wenn der neue Ordner im Projektmappen-Explorer angezeigt wird, nennen Sie ihn "DataStructures".

Erstellen Sie Ihre Eingabedatenklasse im neu erstellten DataStructures-Verzeichnis .

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Verzeichnis "DataStructures", und wählen Sie dann "> hinzufügen" aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in ImageNetData.cs. Klicken Sie anschließend auf Hinzufügen.

    Die ImageNetData.cs Datei wird im Code-Editor geöffnet. Fügen Sie oben using die folgende Direktive hinzu:

    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Microsoft.ML.Data;
    

    Entfernen Sie die vorhandene Klassendefinition, und fügen Sie der ImageNetData den folgenden Code für die Klasse hinzu:

    public class ImageNetData
    {
        [LoadColumn(0)]
        public string ImagePath;
    
        [LoadColumn(1)]
        public string Label;
    
        public static IEnumerable<ImageNetData> ReadFromFile(string imageFolder)
        {
            return Directory
                .GetFiles(imageFolder)
                .Where(filePath => Path.GetExtension(filePath) != ".md")
                .Select(filePath => new ImageNetData { ImagePath = filePath, Label = Path.GetFileName(filePath) });
        }
    }
    

    ImageNetData ist die Eingabebilddatenklasse und weist die folgenden String Felder auf:

    • ImagePath enthält den Pfad, in dem das Bild gespeichert ist.
    • Label enthält den Namen der Datei.

    Enthält darüber hinaus eine MethodeImageNetData, ReadFromFile mit der mehrere im angegebenen Pfad gespeicherte imageFolder Bilddateien geladen und als Auflistung von ImageNetData Objekten zurückgegeben werden.

Erstellen Sie Ihre Vorhersageklasse im DataStructures-Verzeichnis .

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Verzeichnis "DataStructures", und wählen Sie dann "> hinzufügen" aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in ImageNetPrediction.cs. Klicken Sie anschließend auf Hinzufügen.

    Die ImageNetPrediction.cs Datei wird im Code-Editor geöffnet. Fügen Sie oben using die folgende Direktive hinzu:

    using Microsoft.ML.Data;
    

    Entfernen Sie die vorhandene Klassendefinition, und fügen Sie der ImageNetPrediction den folgenden Code für die Klasse hinzu:

    public class ImageNetPrediction
    {
        [ColumnName("grid")]
        public float[] PredictedLabels;
    }
    

    ImageNetPrediction ist die Vorhersagedatenklasse und weist das folgende float[] Feld auf:

    • PredictedLabels enthält die Dimensionen, die Objektheitsbewertung und die Klassenwahrscheinlichkeiten für jede der in einem Bild erkannten Begrenzungsrahmen.

Initialisieren der Variablen

Die MLContext-Klasse ist ein Ausgangspunkt für alle ML.NET-Vorgänge, und das Initialisieren von mlContext erstellt eine neue ML.NET-Umgebung, die für die Modell-Erstellungs-Workflow-Objekte freigegeben werden kann. Es ist konzeptionell ähnlich wie DBContext im Entity Framework.

Initialisieren Sie die mlContext Variable mit einer neuen Instanz, indem MLContext Sie die folgende Zeile unterhalb des outputFolder Felds hinzufügen.

MLContext mlContext = new MLContext();

Einen Parser zum Nachverarbeiten von Modellausgaben erstellen

Das Modell segmentiert ein Bild in ein 13 x 13 Raster, wobei jede Rasterzelle ist 32px x 32px. Jede Rasterzelle enthält 5 potenzielle Objekt-Begrenzungsfelder. Ein Begrenzungsrahmen hat 25 Elemente.

Rasterbeispiel auf der linken Seite und Begrenzungsfeld-Beispiel auf der rechten Seite

  • x die x-Position des Mittelpunkts der Begrenzungsbox relativ zu der Gitternetzzelle, mit der sie verbunden ist.
  • y die y-Position des Mittelpunkts des Begrenzungsrahmens relativ zur Rasterzelle, der er zugeordnet ist.
  • w die Breite des Begrenzungsrahmens.
  • h die Höhe des Begrenzungsrahmens.
  • o der Konfidenzwert, der angibt, dass ein Objekt innerhalb des Begrenzungsrahmens existiert, auch bekannt als Objektheitswert.
  • p1-p20 Klassenwahrscheinlichkeiten für jede der 20 Klassen, die vom Modell vorhergesagt werden.

Insgesamt bilden die 25 Elemente, die die 5 Begrenzungsrahmen beschreiben, die 125 Elemente, die in jeder Rasterzelle enthalten sind.

Die durch das vortrainierte ONNX-Modell generierte Ausgabe ist ein Float-Array der Länge 21125, das die Elemente eines Tensors mit Dimensionen 125 x 13 x 13darstellt. Um die vom Modell generierten Vorhersagen in einen Tensor umzuwandeln, sind einige Nachbearbeitungsarbeiten erforderlich. Erstellen Sie dazu eine Reihe von Klassen, um die Ausgabe zu analysieren.

Fügen Sie Ihrem Projekt ein neues Verzeichnis hinzu, um eine Sammlung von Parserklassen zu organisieren.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie dann "Neuen Ordner>" aus. Wenn der neue Ordner im Projektmappen-Explorer angezeigt wird, nennen Sie ihn "YoloParser".

Erstellen Sie Begrenzungsrahmen und Dimensionen

Die Datenausgabe des Modells enthält Koordinaten und Dimensionen der Begrenzungsrahmen von Objekten innerhalb des Bildes. Erstellen Sie eine Basisklasse für Dimensionen.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Verzeichnis "YoloParser", und wählen Sie dann "Neues Element> aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in DimensionsBase.cs. Klicken Sie anschließend auf Hinzufügen.

    Die DimensionsBase.cs Datei wird im Code-Editor geöffnet. Entfernen Sie alle using Direktiven und vorhandene Klassendefinitionen.

    Fügen Sie der DimensionsBase den folgenden Code für die Klasse hinzu:

    public class DimensionsBase
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float Height { get; set; }
        public float Width { get; set; }
    }
    

    DimensionsBase hat die folgenden float Eigenschaften:

    • X enthält die Position des Objekts entlang der X-Achse.
    • Y enthält die Position des Objekts entlang der Y-Achse.
    • Height enthält die Höhe des Objekts.
    • Width enthält die Breite des Objekts.

Erstellen Sie eine Klasse für Ihre Begrenzungsrahmen.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Verzeichnis "YoloParser", und wählen Sie dann "Neues Element> aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in YoloBoundingBox.cs. Klicken Sie anschließend auf Hinzufügen.

    Die YoloBoundingBox.cs Datei wird im Code-Editor geöffnet. Fügen Sie die folgende using Direktive oben in YoloBoundingBox.cs hinzu:

    using System.Drawing;
    

    Fügen Sie direkt oberhalb der vorhandenen Klassendefinition eine neue Klassendefinition BoundingBoxDimensions hinzu, die von der DimensionsBase Klasse erbt, um die Dimensionen des jeweiligen begrenzenden Rechtecks zu enthalten.

    public class BoundingBoxDimensions : DimensionsBase { }
    

    Entfernen Sie die vorhandene YoloBoundingBox Klassendefinition, und fügen Sie der YoloBoundingBox den folgenden Code für die Klasse hinzu:

    public class YoloBoundingBox
    {
        public BoundingBoxDimensions Dimensions { get; set; }
    
        public string Label { get; set; }
    
        public float Confidence { get; set; }
    
        public RectangleF Rect
        {
            get { return new RectangleF(Dimensions.X, Dimensions.Y, Dimensions.Width, Dimensions.Height); }
        }
    
        public Color BoxColor { get; set; }
    }
    

    YoloBoundingBox hat die folgenden Eigenschaften:

    • Dimensions enthält Abmessungen des Begrenzungsrahmens.
    • Label enthält die Klasse des Objekts, das innerhalb des Begrenzungsrahmens erkannt wurde.
    • Confidence enthält die Zuversicht der Klasse.
    • Rect enthält die Rechteckdarstellung der Abmessungen des Begrenzungsrahmens.
    • BoxColor enthält die Farbe, die der jeweiligen Klasse zugeordnet ist, die zum Zeichnen auf dem Bild verwendet wird.

Erstellen des Parsers

Nachdem die Klassen für Dimensionen und Begrenzungsrahmen erstellt wurden, ist es an der Zeit, den Parser zu erstellen.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Verzeichnis "YoloParser", und wählen Sie dann "Neues Element> aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in YoloOutputParser.cs. Klicken Sie anschließend auf Hinzufügen.

    Die YoloOutputParser.cs Datei wird im Code-Editor geöffnet. Fügen Sie oben using die folgenden Direktiven hinzu:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    

    Fügen Sie innerhalb der vorhandenen YoloOutputParser Klassendefinition eine geschachtelte Klasse hinzu, die die Abmessungen jeder Zelle im Bild enthält. Fügen Sie den folgenden Code für die CellDimensions Klasse hinzu, die von der DimensionsBase Klasse am Anfang der YoloOutputParser Klassendefinition erbt.

    class CellDimensions : DimensionsBase { }
    
  3. Fügen Sie innerhalb der YoloOutputParser Klassendefinition die folgenden Konstanten und Felder hinzu.

    public const int ROW_COUNT = 13;
    public const int COL_COUNT = 13;
    public const int CHANNEL_COUNT = 125;
    public const int BOXES_PER_CELL = 5;
    public const int BOX_INFO_FEATURE_COUNT = 5;
    public const int CLASS_COUNT = 20;
    public const float CELL_WIDTH = 32;
    public const float CELL_HEIGHT = 32;
    
    private int channelStride = ROW_COUNT * COL_COUNT;
    
    • ROW_COUNT ist die Anzahl der Zeilen im Raster, in die das Bild unterteilt ist.
    • COL_COUNT ist die Anzahl der Spalten im Raster, in die das Bild unterteilt ist.
    • CHANNEL_COUNT ist die Gesamtanzahl der Werte, die in einer Zelle des Rasters enthalten sind.
    • BOXES_PER_CELL ist die Anzahl der begrenzenden Boxen in einer Zelle.
    • BOX_INFO_FEATURE_COUNT ist die Anzahl der Features, die in einem Feld enthalten sind (x,y,Höhe,Breite,Konfidenz).
    • CLASS_COUNT ist die Anzahl der Klassenvorhersagen, die in jedem umgebenden Feld enthalten sind.
    • CELL_WIDTH ist die Breite einer Zelle im Bildraster.
    • CELL_HEIGHT ist die Höhe einer Zelle im Bildraster.
    • channelStride ist die Startposition der aktuellen Zelle im Raster.

    Wenn das Modell eine Vorhersage macht, auch als Scoring (Bewertung) bezeichnet, teilt es das 416px x 416px Eingabebild in ein Raster von Zellen der Größe 13 x 13. Jede Zelle enthält folgendes:32px x 32px Innerhalb jeder Zelle gibt es fünf Begrenzungsrahmen, die jeweils fünf Merkmale enthalten (x, y, Breite, Höhe, Konfidenz). Darüber hinaus enthält jedes Begrenzungsfeld die Wahrscheinlichkeit jedes der Klassen, die in diesem Fall 20 beträgt. Daher enthält jede Zelle 125 Informationen (5 Merkmale + 20 Klassenwahrscheinlichkeiten).

Erstellen Sie eine Liste der Verankerungen unterhalb channelStride für alle fünf Begrenzungsrahmen.

private float[] anchors = new float[]
{
    1.08F, 1.19F, 3.42F, 4.41F, 6.63F, 11.38F, 9.42F, 5.11F, 16.62F, 10.52F
};

Anker sind vordefinierte Höhen- und Breitenverhältnisse von Begrenzungsrahmen. Die meisten von einem Modell erkannten Objekte oder Klassen weisen ähnliche Verhältnisse auf. Dies ist nützlich, wenn es darum geht, Begrenzungsrahmen zu erstellen. Anstatt die umgebenden Felder vorherzusagen, wird der Offset aus den vordefinierten Dimensionen berechnet, wodurch die Berechnung reduziert wird, die erforderlich ist, um das umgebende Feld vorherzusagen. In der Regel werden diese Ankerverhältnisse basierend auf dem verwendeten Dataset berechnet. Da das Dataset bekannt ist und die Werte vorkompiliert wurden, können die Anker hartcodiert werden.

Definieren Sie als Nächstes die Bezeichnungen oder Klassen, die vom Modell vorhergesagt werden. Dieses Modell prognostiziert 20 Klassen, bei denen es sich um eine Teilmenge der Gesamtzahl der klassen handelt, die vom ursprünglichen YOLOv2-Modell vorhergesagt wurden.

Fügen Sie Ihre Liste der Labels unter die anchors hinzu.

private string[] labels = new string[]
{
    "aeroplane", "bicycle", "bird", "boat", "bottle",
    "bus", "car", "cat", "chair", "cow",
    "diningtable", "dog", "horse", "motorbike", "person",
    "pottedplant", "sheep", "sofa", "train", "tvmonitor"
};

Jeder Klasse sind Farben zugeordnet. Weisen Sie Ihre Klassenfarben unter Ihren labels:

private static Color[] classColors = new Color[]
{
    Color.Khaki,
    Color.Fuchsia,
    Color.Silver,
    Color.RoyalBlue,
    Color.Green,
    Color.DarkOrange,
    Color.Purple,
    Color.Gold,
    Color.Red,
    Color.Aquamarine,
    Color.Lime,
    Color.AliceBlue,
    Color.Sienna,
    Color.Orchid,
    Color.Tan,
    Color.LightPink,
    Color.Yellow,
    Color.HotPink,
    Color.OliveDrab,
    Color.SandyBrown,
    Color.DarkTurquoise
};

Erstellen von Hilfsfunktionen

Es gibt eine Reihe von Schritten, die in der Nachbearbeitungsphase involviert sind. Dazu können mehrere Hilfsmethoden eingesetzt werden.

Die Hilfsmethoden, die vom Parser verwendet werden, sind:

  • Sigmoid wendet die Sigmoidfunktion an, die eine Zahl zwischen 0 und 1 ausgibt.
  • Softmax normalisiert einen Eingabevektor in eine Wahrscheinlichkeitsverteilung.
  • GetOffset ordnet Elemente in der eindimensionalen Modellausgabe der entsprechenden Position in einem 125 x 13 x 13 Tensor zu.
  • ExtractBoundingBoxes extrahiert die Abmessungen des Begrenzungsrahmens mithilfe der Methode GetOffset aus der Modellausgabe.
  • GetConfidence extrahiert den Konfidenzwert, der angibt, wie sicher das Modell ist, dass es ein Objekt erkannt hat, und verwendet die Sigmoid Funktion, um sie in einen Prozentsatz umzuwandeln.
  • MapBoundingBoxToCell verwendet die Bounding-Box-Abmessungen und ordnet sie ihrer jeweiligen Zelle innerhalb des Bildes zu.
  • ExtractClasses extrahiert die Klassenvorhersagen für den Begrenzungsrahmen aus der Modellausgabe mithilfe der Methode GetOffset und wandelt sie mithilfe der Methode Softmax in eine Wahrscheinlichkeitsverteilung um.
  • GetTopResult wählt die Klasse aus der Liste der vorhergesagten Klassen mit der höchsten Wahrscheinlichkeit aus.
  • IntersectionOverUnion Filter, die begrenzungsgebundene Felder mit niedrigeren Wahrscheinlichkeiten überlappen.

Fügen Sie den Code für alle Hilfsmethoden unterhalb der Liste der classColors.

private float Sigmoid(float value)
{
    var k = (float)Math.Exp(value);
    return k / (1.0f + k);
}

private float[] Softmax(float[] values)
{
    var maxVal = values.Max();
    var exp = values.Select(v => Math.Exp(v - maxVal));
    var sumExp = exp.Sum();

    return exp.Select(v => (float)(v / sumExp)).ToArray();
}

private int GetOffset(int x, int y, int channel)
{
    // YOLO outputs a tensor that has a shape of 125x13x13, which 
    // WinML flattens into a 1D array.  To access a specific channel 
    // for a given (x,y) cell position, we need to calculate an offset
    // into the array
    return (channel * this.channelStride) + (y * COL_COUNT) + x;
}

private BoundingBoxDimensions ExtractBoundingBoxDimensions(float[] modelOutput, int x, int y, int channel)
{
    return new BoundingBoxDimensions
    {
        X = modelOutput[GetOffset(x, y, channel)],
        Y = modelOutput[GetOffset(x, y, channel + 1)],
        Width = modelOutput[GetOffset(x, y, channel + 2)],
        Height = modelOutput[GetOffset(x, y, channel + 3)]
    };
}

private float GetConfidence(float[] modelOutput, int x, int y, int channel)
{
    return Sigmoid(modelOutput[GetOffset(x, y, channel + 4)]);
}

private CellDimensions MapBoundingBoxToCell(int x, int y, int box, BoundingBoxDimensions boxDimensions)
{
    return new CellDimensions
    {
        X = ((float)x + Sigmoid(boxDimensions.X)) * CELL_WIDTH,
        Y = ((float)y + Sigmoid(boxDimensions.Y)) * CELL_HEIGHT,
        Width = (float)Math.Exp(boxDimensions.Width) * CELL_WIDTH * anchors[box * 2],
        Height = (float)Math.Exp(boxDimensions.Height) * CELL_HEIGHT * anchors[box * 2 + 1],
    };
}

public float[] ExtractClasses(float[] modelOutput, int x, int y, int channel)
{
    float[] predictedClasses = new float[CLASS_COUNT];
    int predictedClassOffset = channel + BOX_INFO_FEATURE_COUNT;
    for (int predictedClass = 0; predictedClass < CLASS_COUNT; predictedClass++)
    {
        predictedClasses[predictedClass] = modelOutput[GetOffset(x, y, predictedClass + predictedClassOffset)];
    }
    return Softmax(predictedClasses);
}

private ValueTuple<int, float> GetTopResult(float[] predictedClasses)
{
    return predictedClasses
        .Select((predictedClass, index) => (Index: index, Value: predictedClass))
        .OrderByDescending(result => result.Value)
        .First();
}

private float IntersectionOverUnion(RectangleF boundingBoxA, RectangleF boundingBoxB)
{
    var areaA = boundingBoxA.Width * boundingBoxA.Height;

    if (areaA <= 0)
        return 0;

    var areaB = boundingBoxB.Width * boundingBoxB.Height;

    if (areaB <= 0)
        return 0;

    var minX = Math.Max(boundingBoxA.Left, boundingBoxB.Left);
    var minY = Math.Max(boundingBoxA.Top, boundingBoxB.Top);
    var maxX = Math.Min(boundingBoxA.Right, boundingBoxB.Right);
    var maxY = Math.Min(boundingBoxA.Bottom, boundingBoxB.Bottom);

    var intersectionArea = Math.Max(maxY - minY, 0) * Math.Max(maxX - minX, 0);

    return intersectionArea / (areaA + areaB - intersectionArea);
}

Nachdem Sie alle Hilfsmethoden definiert haben, ist es an der Zeit, sie zum Verarbeiten der Modellausgabe zu verwenden.

Erstellen Sie unter der IntersectionOverUnion Methode die ParseOutputs Methode, um die vom Modell generierte Ausgabe zu verarbeiten.

public IList<YoloBoundingBox> ParseOutputs(float[] yoloModelOutputs, float threshold = .3F)
{

}

Erstellen Sie eine Liste, um die Begrenzungsrahmen zu speichern, und definieren Sie Variablen in der ParseOutputs Methode.

var boxes = new List<YoloBoundingBox>();

Jedes Bild wird in ein Zellenraster 13 x 13 unterteilt. Jede Zelle enthält fünf begrenzende Boxen. Fügen Sie unterhalb der boxes Variablen Code hinzu, um alle Felder in den einzelnen Zellen zu verarbeiten.

for (int row = 0; row < ROW_COUNT; row++)
{
    for (int column = 0; column < COL_COUNT; column++)
    {
        for (int box = 0; box < BOXES_PER_CELL; box++)
        {

        }
    }
}

Berechnen Sie innerhalb der innersten Schleife die Startposition des aktuellen Felds innerhalb der eindimensionalen Modellausgabe.

var channel = (box * (CLASS_COUNT + BOX_INFO_FEATURE_COUNT));

Verwenden Sie die ExtractBoundingBoxDimensions Methode direkt darunter, um die Abmessungen des aktuellen Begrenzungsrahmens abzurufen.

BoundingBoxDimensions boundingBoxDimensions = ExtractBoundingBoxDimensions(yoloModelOutputs, row, column, channel);

Verwenden Sie dann die GetConfidence-Methode, um die Konfidenz für den aktuellen Umgrenzungsrahmen abzurufen.

float confidence = GetConfidence(yoloModelOutputs, row, column, channel);

Verwenden Sie anschließend die MapBoundingBoxToCell Methode, um das aktuelle umgebende Feld der aktuellen Zelle zuzuordnen, die verarbeitet wird.

CellDimensions mappedBoundingBox = MapBoundingBoxToCell(row, column, box, boundingBoxDimensions);

Überprüfen Sie vor der weiteren Verarbeitung, ob Ihr Konfidenzwert größer als der angegebene Schwellenwert ist. Falls nicht zutreffend, verarbeiten Sie die nächste Begrenzungsbox.

if (confidence < threshold)
    continue;

Andernfalls fahren Sie mit der Verarbeitung der Ausgabe fort. Der nächste Schritt besteht darin, die Wahrscheinlichkeitsverteilung der vorhergesagten Klassen für den aktuellen begrenzenden Rahmen mit der Methode ExtractClasses abzurufen.

float[] predictedClasses = ExtractClasses(yoloModelOutputs, row, column, channel);

Verwenden Sie dann die GetTopResult Methode, um den Wert und den Index der Klasse mit der höchsten Wahrscheinlichkeit für das aktuelle Feld abzurufen und deren Bewertung zu berechnen.

var (topResultIndex, topResultScore) = GetTopResult(predictedClasses);
var topScore = topResultScore * confidence;

Verwenden Sie die topScore Funktion, um erneut nur die begrenzungsgebundenen Felder beizubehalten, die sich über dem angegebenen Schwellenwert befinden.

if (topScore < threshold)
    continue;

Wenn die aktuelle Bounding-Box den Schwellenwert überschreitet, erstellen Sie ein neues BoundingBox Objekt und fügen Sie es der boxes Liste hinzu.

boxes.Add(new YoloBoundingBox()
{
    Dimensions = new BoundingBoxDimensions
    {
        X = (mappedBoundingBox.X - mappedBoundingBox.Width / 2),
        Y = (mappedBoundingBox.Y - mappedBoundingBox.Height / 2),
        Width = mappedBoundingBox.Width,
        Height = mappedBoundingBox.Height,
    },
    Confidence = topScore,
    Label = labels[topResultIndex],
    BoxColor = classColors[topResultIndex]
});

Nachdem alle Zellen im Bild verarbeitet wurden, geben Sie die boxes Liste zurück. Fügen Sie die folgende Rückgabe-Anweisung unterhalb der äußersten For-Loop-Anweisung in der ParseOutputs Methode hinzu.

return boxes;

Überlappende Boxen filtern

Da nun alle hochsicheren Begrenzungsfelder aus der Modellausgabe extrahiert wurden, muss zusätzliche Filterung durchgeführt werden, um überlappende Bilder zu entfernen. Fügen Sie eine Methode hinzu, die unter der FilterBoundingBoxes Methode aufgerufen wirdParseOutputs:

public IList<YoloBoundingBox> FilterBoundingBoxes(IList<YoloBoundingBox> boxes, int limit, float threshold)
{

}

Beginnen Sie innerhalb der FilterBoundingBoxes Methode, indem Sie ein Array erstellen, das der Größe von erkannten Feldern entspricht, und markieren Sie alle Steckplätze als aktiv oder für die Verarbeitung bereit.

var activeCount = boxes.Count;
var isActiveBoxes = new bool[boxes.Count];

for (int i = 0; i < isActiveBoxes.Length; i++)
    isActiveBoxes[i] = true;

Sortieren Sie dann die Liste mit Ihren Begrenzungsrahmen in absteigender Reihenfolge basierend auf den Vertrauenswerten.

var sortedBoxes = boxes.Select((b, i) => new { Box = b, Index = i })
                    .OrderByDescending(b => b.Box.Confidence)
                    .ToList();

Erstellen Sie danach eine Liste, um die gefilterten Ergebnisse zu enthalten.

var results = new List<YoloBoundingBox>();

Beginnen Sie mit der Verarbeitung jedes Begrenzungsrahmens, indem Sie jeden der Begrenzungsrahmen durchlaufen.

for (int i = 0; i < boxes.Count; i++)
{

}

Überprüfen Sie innerhalb dieser for-Schleife, ob die aktuelle Bounding-Box verarbeitet werden kann.

if (isActiveBoxes[i])
{

}

Wenn ja, fügen Sie den Begrenzungsrahmen zur Liste der Ergebnisse hinzu. Wenn die Ergebnisse den angegebenen Grenzwert der zu extrahierenden Felder überschreiten, brechen Sie die Schleife aus. Fügen Sie den folgenden Code innerhalb der If-Anweisung hinzu.

var boxA = sortedBoxes[i].Box;
results.Add(boxA);

if (results.Count >= limit)
    break;

Sehen Sie sich andernfalls die angrenzenden Begrenzungsfelder an. Fügen Sie den folgenden Code unterhalb des Kontrollkästchenlimits hinzu.

for (var j = i + 1; j < boxes.Count; j++)
{

}

Verwenden Sie wie das erste Feld, wenn das angrenzende Feld aktiv ist oder verarbeitet werden kann, die IntersectionOverUnion Methode, um zu überprüfen, ob das erste Kontrollkästchen und das zweite Feld den angegebenen Schwellenwert überschreiten. Fügen Sie dem innersten For-Loop den folgenden Code hinzu.

if (isActiveBoxes[j])
{
    var boxB = sortedBoxes[j].Box;

    if (IntersectionOverUnion(boxA.Rect, boxB.Rect) > threshold)
    {
        isActiveBoxes[j] = false;
        activeCount--;

        if (activeCount <= 0)
            break;
    }
}

Außerhalb der innersten For-Loop-Funktion, die benachbarte Begrenzungsfelder überprüft, sehen Sie, ob es noch zu verarbeitende Begrenzungsfelder gibt. Wenn nicht, brechen Sie die äußere For-Loop-Schleife auf.

if (activeCount <= 0)
    break;

Geben Sie schließlich außerhalb der ersten for-Schleife der FilterBoundingBoxes Methode die Ergebnisse zurück.

return results;

Sehr gut! Jetzt ist es an der Zeit, diesen Code zusammen mit dem Modell für die Bewertung zu verwenden.

Verwenden des Modells für die Bewertung

Genau wie bei der Nachbearbeitung gibt es einige Schritte in den Bewertungsschritten. Fügen Sie dazu eine Klasse hinzu, die die Bewertungslogik zu Ihrem Projekt enthält.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie dann "> hinzufügen" aus.

  2. Wählen Sie im Dialogfeld " Neues Element hinzufügen " " Klasse " aus, und ändern Sie das Feld "Name " in OnnxModelScorer.cs. Klicken Sie anschließend auf Hinzufügen.

    Die OnnxModelScorer.cs Datei wird im Code-Editor geöffnet. Fügen Sie oben using die folgenden Direktiven hinzu:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.ML;
    using Microsoft.ML.Data;
    using ObjectDetection.DataStructures;
    using ObjectDetection.YoloParser;
    

    Fügen Sie innerhalb der OnnxModelScorer Klassendefinition die folgenden Variablen hinzu.

    private readonly string imagesFolder;
    private readonly string modelLocation;
    private readonly MLContext mlContext;
    
    private IList<YoloBoundingBox> _boundingBoxes = new List<YoloBoundingBox>();
    

    Erstellen Sie direkt darunter einen Konstruktor für die OnnxModelScorer Klasse, der die zuvor definierten Variablen initialisiert.

    public OnnxModelScorer(string imagesFolder, string modelLocation, MLContext mlContext)
    {
        this.imagesFolder = imagesFolder;
        this.modelLocation = modelLocation;
        this.mlContext = mlContext;
    }
    

    Nachdem Sie den Konstruktor erstellt haben, definieren Sie einige Strukturen, die Variablen enthalten, die sich auf die Bild- und Modelleinstellungen beziehen. Erstellen Sie eine Struktur namens ImageNetSettings, um die als Eingabe für das Modell erwartete Höhe und Breite zu enthalten.

    public struct ImageNetSettings
    {
        public const int imageHeight = 416;
        public const int imageWidth = 416;
    }
    

    Erstellen Sie danach eine weitere Struktur mit dem Namen TinyYoloModelSettings, die die Namen der Eingabe- und Ausgabeebenen des Modells enthält. Um den Namen der Eingabe- und Ausgabeebenen des Modells zu visualisieren, können Sie ein Tool wie Netron verwenden.

    public struct TinyYoloModelSettings
    {
        // for checking Tiny yolo2 Model input and  output  parameter names,
        //you can use tools like Netron, 
        // which is installed by Visual Studio AI Tools
    
        // input tensor name
        public const string ModelInput = "image";
    
        // output tensor name
        public const string ModelOutput = "grid";
    }
    

    Erstellen Sie als Nächstes die erste Gruppe von Methoden, die für die Bewertung verwendet werden. Erstellen Sie die LoadModel Methode innerhalb der OnnxModelScorer Klasse.

    private ITransformer LoadModel(string modelLocation)
    {
    
    }
    

    Fügen Sie innerhalb der LoadModel Methode den folgenden Code für die Protokollierung hinzu.

    Console.WriteLine("Read model");
    Console.WriteLine($"Model location: {modelLocation}");
    Console.WriteLine($"Default parameters: image size=({ImageNetSettings.imageWidth},{ImageNetSettings.imageHeight})");
    

    ML.NET Pipelines müssen das Datenschema kennen, das beim Aufrufen der Fit Methode ausgeführt werden soll. In diesem Fall wird ein Prozess verwendet, der der Schulung ähnelt. Da jedoch keine tatsächliche Schulung stattfindet, ist es akzeptabel, eine leere IDataViewzu verwenden. Erstellen Sie eine neue IDataView für die Pipeline von einer leeren Liste.

    var data = mlContext.Data.LoadFromEnumerable(new List<ImageNetData>());
    

    Definieren Sie im Folgenden die Pipeline. Die Pipeline wird aus vier Transformationen bestehen.

    • LoadImages lädt das Bild als Bitmap.
    • ResizeImages Verkleinern des Bilds auf die angegebene Größe (in diesem Fall 416 x 416).
    • ExtractPixels ändert die Pixeldarstellung des Bilds von einer Bitmap in einen numerischen Vektor.
    • ApplyOnnxModel lädt das ONNX-Modell und verwendet es, um die bereitgestellten Daten auszuwerten.

    Definieren Sie Ihre Pipeline in der Methode unterhalb der LoadModeldata Variablen.

    var pipeline = mlContext.Transforms.LoadImages(outputColumnName: "image", imageFolder: "", inputColumnName: nameof(ImageNetData.ImagePath))
                    .Append(mlContext.Transforms.ResizeImages(outputColumnName: "image", imageWidth: ImageNetSettings.imageWidth, imageHeight: ImageNetSettings.imageHeight, inputColumnName: "image"))
                    .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "image"))
                    .Append(mlContext.Transforms.ApplyOnnxModel(modelFile: modelLocation, outputColumnNames: new[] { TinyYoloModelSettings.ModelOutput }, inputColumnNames: new[] { TinyYoloModelSettings.ModelInput }));
    

    Jetzt ist es an der Zeit, das Modell für die Bewertung zu instanziieren. Rufen Sie die Fit Methode für die Pipeline auf, und geben Sie sie zur weiteren Verarbeitung zurück.

    var model = pipeline.Fit(data);
    
    return model;
    

Nachdem das Modell geladen wurde, kann es dann verwendet werden, um Vorhersagen zu erstellen. Um diesen Prozess zu erleichtern, erstellen Sie eine Methode, die unter der PredictDataUsingModel Methode aufgerufen wirdLoadModel.

private IEnumerable<float[]> PredictDataUsingModel(IDataView testData, ITransformer model)
{

}

Fügen Sie in der PredictDataUsingModelDatei den folgenden Code für die Protokollierung hinzu.

Console.WriteLine($"Images location: {imagesFolder}");
Console.WriteLine("");
Console.WriteLine("=====Identify the objects in the images=====");
Console.WriteLine("");

Verwenden Sie dann die Transform Methode, um die Daten zu scoren.

IDataView scoredData = model.Transform(testData);

Extrahieren Sie die vorhergesagten Wahrscheinlichkeiten, und geben Sie sie für die zusätzliche Verarbeitung zurück.

IEnumerable<float[]> probabilities = scoredData.GetColumn<float[]>(TinyYoloModelSettings.ModelOutput);

return probabilities;

Nachdem beide Schritte eingerichtet wurden, kombinieren Sie sie in einer einzigen Methode. Fügen Sie unterhalb der PredictDataUsingModel Methode eine neue Methode mit dem Namen Scorehinzu.

public IEnumerable<float[]> Score(IDataView data)
{
    var model = LoadModel(modelLocation);

    return PredictDataUsingModel(data, model);
}

Fast da! Jetzt ist es an der Zeit, alles zu verwenden.

Erkennen von Objekten

Nachdem das Setup abgeschlossen ist, ist es an der Zeit, einige Objekte zu erkennen.

Ausgabe des Bewertungs- und Analysemodells

Fügen Sie unterhalb der Erstellung der mlContext Variablen eine try-catch-Anweisung hinzu.

try
{

}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}

Beginnen Sie innerhalb des try Blocks mit der Implementierung der Objekterkennungslogik. Laden Sie zunächst die Daten in eine IDataView.

IEnumerable<ImageNetData> images = ImageNetData.ReadFromFile(imagesFolder);
IDataView imageDataView = mlContext.Data.LoadFromEnumerable(images);

Erstellen Sie dann eine Instanz von OnnxModelScorer und verwenden Sie sie, um die geladenen Daten zu notieren.

// Create instance of model scorer
var modelScorer = new OnnxModelScorer(imagesFolder, modelFilePath, mlContext);

// Use model to score data
IEnumerable<float[]> probabilities = modelScorer.Score(imageDataView);

Jetzt ist es an der Zeit für den Schritt nach der Verarbeitung. Erstellen Sie eine Instanz, YoloOutputParser und verwenden Sie sie, um die Modellausgabe zu verarbeiten.

YoloOutputParser parser = new YoloOutputParser();

var boundingBoxes =
    probabilities
    .Select(probability => parser.ParseOutputs(probability))
    .Select(boxes => parser.FilterBoundingBoxes(boxes, 5, .5F));

Sobald die Modellausgabe verarbeitet wurde, ist es an der Zeit, die Begrenzungsrahmen auf den Bildern zu zeichnen.

Visualisieren von Vorhersagen

Nachdem das Modell die Bilder bewertet hat und die Ergebnisse verarbeitet wurden, müssen die Begrenzungsrahmen auf dem Bild gezeichnet werden. Fügen Sie dazu eine Methode hinzu, die unterhalb der DrawBoundingBox Methode innerhalb von GetAbsolutePath aufgerufen wird.

void DrawBoundingBox(string inputImageLocation, string outputImageLocation, string imageName, IList<YoloBoundingBox> filteredBoundingBoxes)
{

}

Laden Sie zunächst das Bild, und rufen Sie die Abmessungen für Höhe und Breite in der DrawBoundingBox Methode ab.

Image image = Image.FromFile(Path.Combine(inputImageLocation, imageName));

var originalImageHeight = image.Height;
var originalImageWidth = image.Width;

Erstellen Sie dann eine For-Each-Schleife, um über jedes der Begrenzungsfelder zu iterieren, die vom Modell erkannt wurden.

foreach (var box in filteredBoundingBoxes)
{

}

Rufen Sie innerhalb der for-each-Schleife die Abmessungen des begrenzenden Rahmens ab.

var x = (uint)Math.Max(box.Dimensions.X, 0);
var y = (uint)Math.Max(box.Dimensions.Y, 0);
var width = (uint)Math.Min(originalImageWidth - x, box.Dimensions.Width);
var height = (uint)Math.Min(originalImageHeight - y, box.Dimensions.Height);

Da die Abmessungen des Begrenzungsrahmens der Modelleingabe 416 x 416 entsprechen, skalieren Sie die Abmessungen des Begrenzungsrahmens entsprechend der tatsächlichen Größe des Bildes.

x = (uint)originalImageWidth * x / OnnxModelScorer.ImageNetSettings.imageWidth;
y = (uint)originalImageHeight * y / OnnxModelScorer.ImageNetSettings.imageHeight;
width = (uint)originalImageWidth * width / OnnxModelScorer.ImageNetSettings.imageWidth;
height = (uint)originalImageHeight * height / OnnxModelScorer.ImageNetSettings.imageHeight;

Definieren Sie eine Vorlage für Text, der über jedem Begrenzungsrahmen erscheint. Der Text enthält die Objektklasse innerhalb des jeweiligen Begrenzungsrahmens sowie die Konfidenz.

string text = $"{box.Label} ({(box.Confidence * 100).ToString("0")}%)";

Um auf das Bild zu zeichnen, konvertieren Sie es in ein Graphics Objekt.

using (Graphics thumbnailGraphic = Graphics.FromImage(image))
{

}

using Optimieren Sie im Codeblock die Objekteinstellungen der GrafikGraphics.

thumbnailGraphic.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraphic.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

Legen Sie darunter die Schriftart- und Farboptionen für den Text und das umgebende Feld fest.

// Define Text Options
Font drawFont = new Font("Arial", 12, FontStyle.Bold);
SizeF size = thumbnailGraphic.MeasureString(text, drawFont);
SolidBrush fontBrush = new SolidBrush(Color.Black);
Point atPoint = new Point((int)x, (int)y - (int)size.Height - 1);

// Define BoundingBox options
Pen pen = new Pen(box.BoxColor, 3.2f);
SolidBrush colorBrush = new SolidBrush(box.BoxColor);

Erstellen und füllen Sie ein Rechteck über dem begrenzenden Rahmen, damit er den Text mithilfe der FillRectangle Methode enthält. Dadurch wird der Kontrast des Textes erhöht und die Lesbarkeit verbessert.

thumbnailGraphic.FillRectangle(colorBrush, (int)x, (int)(y - size.Height - 1), (int)size.Width, (int)size.Height);

Zeichnen Sie dann den Text und das begrenzende Rechteck auf dem Bild mithilfe der DrawString und DrawRectangle Methoden.

thumbnailGraphic.DrawString(text, drawFont, fontBrush, atPoint);

// Draw bounding box on image
thumbnailGraphic.DrawRectangle(pen, x, y, width, height);

Fügen Sie außerhalb der for-each-Schleife Code hinzu, um die Bilder in der outputFolderSchleife zu speichern.

if (!Directory.Exists(outputImageLocation))
{
    Directory.CreateDirectory(outputImageLocation);
}

image.Save(Path.Combine(outputImageLocation, imageName));

Fügen Sie für zusätzliches Feedback, dass die Anwendung zur Laufzeit Vorhersagen wie erwartet vornimmt, eine Methode hinzu, die unter der Methode in der LogDetectedObjectsDrawBoundingBox aufgerufen wird, um die erkannten Objekte in der Konsole auszugeben.

void LogDetectedObjects(string imageName, IList<YoloBoundingBox> boundingBoxes)
{
    Console.WriteLine($".....The objects in the image {imageName} are detected as below....");

    foreach (var box in boundingBoxes)
    {
        Console.WriteLine($"{box.Label} and its Confidence score: {box.Confidence}");
    }

    Console.WriteLine("");
}

Da Sie nun über Hilfsmethoden verfügen, um visuelles Feedback aus den Vorhersagen zu erstellen, fügen Sie eine Schleife hinzu, um über jedes bewertete Bild zu iterieren.

for (var i = 0; i < images.Count(); i++)
{

}

Rufen Sie innerhalb der Forschleife den Namen der Bilddatei und die damit verbundenen Begrenzungsrahmen ab.

string imageFileName = images.ElementAt(i).Label;
IList<YoloBoundingBox> detectedObjects = boundingBoxes.ElementAt(i);

Verwenden Sie unterhalb davon die DrawBoundingBox Methode, um die begrenzenden Boxen auf dem Bild zu zeichnen.

DrawBoundingBox(imagesFolder, outputFolder, imageFileName, detectedObjects);

Verwenden Sie schließlich die LogDetectedObjects Methode, um Vorhersagen an die Konsole auszugeben.

LogDetectedObjects(imageFileName, detectedObjects);

Fügen Sie nach der try-catch-Anweisung zusätzliche Logik hinzu, um anzugeben, dass der Prozess abgeschlossen ist.

Console.WriteLine("========= End of Process..Hit any Key ========");

Das ist alles!

Ergebnisse

Führen Sie nach den vorherigen Schritten die Konsolen-App (STRG+F5) aus. Ihre Ergebnisse sollten mit der folgenden Ausgabe vergleichbar sein. Möglicherweise werden Warnungen oder die Verarbeitung von Nachrichten angezeigt, aber diese Nachrichten wurden aus den folgenden Ergebnissen aus Gründen der Übersichtlichkeit entfernt.

=====Identify the objects in the images=====

.....The objects in the image image1.jpg are detected as below....
car and its Confidence score: 0.9697262
car and its Confidence score: 0.6674225
person and its Confidence score: 0.5226039
car and its Confidence score: 0.5224892
car and its Confidence score: 0.4675332

.....The objects in the image image2.jpg are detected as below....
cat and its Confidence score: 0.6461141
cat and its Confidence score: 0.6400049

.....The objects in the image image3.jpg are detected as below....
chair and its Confidence score: 0.840578
chair and its Confidence score: 0.796363
diningtable and its Confidence score: 0.6056048
diningtable and its Confidence score: 0.3737402

.....The objects in the image image4.jpg are detected as below....
dog and its Confidence score: 0.7608147
person and its Confidence score: 0.6321323
dog and its Confidence score: 0.5967442
person and its Confidence score: 0.5730394
person and its Confidence score: 0.5551759

========= End of Process..Hit any Key ========

Um die Bilder mit Begrenzungsrahmen anzuzeigen, navigieren Sie zum Verzeichnis assets/images/output/. Nachfolgend finden Sie ein Beispiel aus einem der verarbeiteten Bilder.

Beispiel für ein verarbeitetes Bild eines Esszimmers

Glückwunsch! Sie haben nun erfolgreich ein Machine Learning-Modell für die Objekterkennung erstellt, indem Sie ein vortrainiertes Modell in ML.NET wiederverwenden ONNX .

Den Quellcode für dieses Lernprogramm finden Sie im Repository "dotnet/machinelearning-samples" .

In diesem Tutorial haben Sie Folgendes gelernt:

  • Verstehen des Problems
  • Erfahren Sie, was ONNX ist und wie es mit ML.NET zusammenarbeitet.
  • Grundlegendes zum Modell
  • Wiederverwenden des vortrainierten Modells
  • Erkennen von Objekten mit einem geladenen Modell

Sehen Sie sich das GitHub-Repository für Machine Learning-Beispiele an, um ein erweitertes Beispiel zur Objekterkennung zu erkunden.