Bilderkennung mit ResNet50v2 in C#
Das Beispiel zeigt, wie ein vortrainiertes ResNet50 v2 ONNX-Modell mithilfe der Onnx Runtime C#-API ausgeführt wird.
Der Quellcode für dieses Beispiel ist hier verfügbar.
Inhalt
Voraussetzungen
Um dieses Beispiel auszuführen, benötigen Sie Folgendes:
- Installieren Sie .NET Core 3.1 oder höher für Ihr Betriebssystem (Mac, Windows oder Linux).
- Laden Sie das ONNX-Modell ResNet50 v2 auf Ihr lokales System herunter.
- Laden Sie dieses Bild eines Hundes herunter, um das Modell zu testen. Sie können auch jedes beliebige Bild verwenden.
Erste Schritte
Nachdem nun alles eingerichtet ist, können wir mit dem Hinzufügen von Code beginnen, um das Modell auf dem Bild auszuführen. Dies tun wir der Einfachheit halber in der Main-Methode des Programms.
Pfade lesen
Zunächst lesen wir den Pfad zum Modell und den Pfad zum Bild, das wir testen möchten, über Programmargumente ein.
string modelFilePath = args[0];
string imageFilePath = args[1];
Bild lesen
Als Nächstes lesen wir das Bild mit der plattformübergreifenden Bildbibliothek ImageSharp ein.
using Image<Rgb24> image = Image.Load<Rgb24>(imageFilePath, out IImageFormat format);
Beachten Sie, dass wir speziell den Typ Rgb24 lesen, damit wir das Bild in einem späteren Schritt effizient vorverarbeiten können.
Bildgröße ändern
Als Nächstes ändern wir die Größe des Bildes auf die vom Modell erwartete Größe: 224 Pixel mal 224 Pixel.
using Stream imageStream = new MemoryStream();
image.Mutate(x =>
{
x.Resize(new ResizeOptions
{
Size = new Size(224, 224),
Mode = ResizeMode.Crop
});
});
image.Save(imageStream, format);
Beachten Sie, dass wir einen zentrierten Zuschnitt durchführen, um das Seitenverhältnis beizubehalten.
Bild vorverarbeiten
Als Nächstes verarbeiten wir das Bild gemäß den Anforderungen des Modells vor.
// We use DenseTensor for multi-dimensional access to populate the image data
var mean = new[] { 0.485f, 0.456f, 0.406f };
var stddev = new[] { 0.229f, 0.224f, 0.225f };
DenseTensor<float> processedImage = new(new[] { 1, 3, 224, 224 });
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
processedImage[0, 0, y, x] = ((pixelSpan[x].R / 255f) - mean[0]) / stddev[0];
processedImage[0, 1, y, x] = ((pixelSpan[x].G / 255f) - mean[1]) / stddev[1];
processedImage[0, 2, y, x] = ((pixelSpan[x].B / 255f) - mean[2]) / stddev[2];
}
}
});
Hier erstellen wir einen Tensor der erforderlichen Größe (batch-size, channels, height, width), greifen auf die Pixelwerte zu, verarbeiten sie vor und weisen sie schließlich den entsprechenden Indizes im Tensor zu.
Eingaben einrichten
Als Nächstes erstellen wir die Eingaben für das Modell.
// Pin tensor buffer and create a OrtValue with native tensor that makes use of
// DenseTensor buffer directly. This avoids extra data copy within OnnxRuntime.
// It will be unpinned on ortValue disposal
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
processedImage.Buffer, new long[] { 1, 3, 224, 224 });
var inputs = new Dictionary<string, OrtValue>
{
{ "data", inputOrtValue }
}
Um die Eingabeknotennamen für ein ONNX-Modell zu überprüfen, können Sie Netron verwenden, um das Modell zu visualisieren und die Ein- und Ausgabenamen anzuzeigen. In diesem Fall hat dieses Modell data als Eingabeknotennamen.
Inferenz ausführen
Als Nächstes erstellen wir eine Inferenzsitzung und führen die Eingabe durch sie.
using var session = new InferenceSession(modelFilePath);
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = session.Run(runOptions, inputs, session.OutputNames);
Ausgabe nachverarbeiten
Als Nächstes müssen wir die Ausgabe nachverarbeiten, um den Softmax-Vektor zu erhalten, da dieser nicht vom Modell selbst gehandhabt wird.
// We copy results to array only to apply algorithms, otherwise data can be accessed directly
// from the native buffer via ReadOnlySpan<T> or Span<T>
var output = results[0].GetTensorDataAsSpan<float>().ToArray();
float sum = output.Sum(x => (float)Math.Exp(x));
IEnumerable<float> softmax = output.Select(x => (float)Math.Exp(x) / sum);
Andere Modelle wenden möglicherweise einen Softmax-Knoten vor der Ausgabe an, in diesem Fall benötigen Sie diesen Schritt nicht. Auch hier können Sie Netron verwenden, um die Modellausgaben anzuzeigen.
Top 10 extrahieren
Als Nächstes extrahieren wir die Top-10-Klassenvorhersagen.
IEnumerable<Prediction> top10 = softmax.Select((x, i) => new Prediction { Label = LabelMap.Labels[i], Confidence = x })
.OrderByDescending(x => x.Confidence)
.Take(10);
Ergebnisse ausgeben
Als Nächstes geben wir die Top-10-Ergebnisse auf der Konsole aus.
Console.WriteLine("Top 10 predictions for ResNet50 v2...");
Console.WriteLine("--------------------------------------------------------------");
foreach (var t in top10)
{
Console.WriteLine($"Label: {t.Label}, Confidence: {t.Confidence}");
}
Ausführen des Programms
Nun, da das Programm erstellt ist, können wir es mit dem folgenden Befehl ausführen.
dotnet run [path-to-model] [path-to-image]
z. B.
dotnet run ~/Downloads/resnet50-v2-7.onnx ~/Downloads/dog.jpeg
Ausführen auf dem folgenden Bild

Wir erhalten die folgende Ausgabe
Top 10 predictions for ResNet50 v2...
--------------------------------------------------------------
Label: Golden Retriever, Confidence: 0.9212826
Label: Kuvasz, Confidence: 0.026514154
Label: Clumber Spaniel, Confidence: 0.012455719
Label: Labrador Retriever, Confidence: 0.004103844
Label: Saluki, Confidence: 0.0033182495
Label: Flat-Coated Retriever, Confidence: 0.0032045357
Label: English Setter, Confidence: 0.002513516
Label: Brittany, Confidence: 0.0023459378
Label: Cocker Spaniels, Confidence: 0.0019343802
Label: Sussex Spaniel, Confidence: 0.0019247672