Objekterkennung mit Faster RCNN Deep Learning in C#
Das Beispiel führt durch die Ausführung eines vortrainierten Faster R-CNN Objekterkennungs-ONNX-Modells mithilfe der ONNX Runtime C#-API.
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 Faster R-CNN ONNX-Modell auf Ihr lokales System herunter.
- Laden Sie dieses Demobild 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 zur Ausführung des Modells auf dem Bild beginnen. Der Einfachheit halber werden wir dies in der Main-Methode des Programms tun.
Pfade lesen
Zuerst lesen wir den Pfad zum Modell, den Pfad zum zu testenden Bild und den Pfad zum Ausgabebild.
string modelFilePath = args[0];
string imageFilePath = args[1];
string outImageFilePath = args[2];
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 geeignete Größe, die das Modell erwartet. Es wird empfohlen, die Bildgröße so zu ändern, dass sowohl Höhe als auch Breite im Bereich [800, 1333] liegen.
float ratio = 800f / Math.Min(image.Width, image.Height);
using Stream imageStream = new MemoryStream();
image.Mutate(x => x.Resize((int)(ratio * image.Width), (int)(ratio * image.Height)));
image.Save(imageStream, format);
Bild vorverarbeiten
Als Nächstes verarbeiten wir das Bild gemäß den Anforderungen des Modells vor.
var paddedHeight = (int)(Math.Ceiling(image.Height / 32f) * 32f);
var paddedWidth = (int)(Math.Ceiling(image.Width / 32f) * 32f);
var mean = new[] { 102.9801f, 115.9465f, 122.7717f };
// Preprocessing image
// We use DenseTensor for multi-dimensional access
DenseTensor<float> input = new(new[] { 3, paddedHeight, paddedWidth });
image.ProcessPixelRows(accessor =>
{
for (int y = paddedHeight - accessor.Height; y < accessor.Height; y++)
{
Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
for (int x = paddedWidth - accessor.Width; x < accessor.Width; x++)
{
input[0, y, x] = pixelSpan[x].B - mean[0];
input[1, y, x] = pixelSpan[x].G - mean[1];
input[2, y, x] = pixelSpan[x].R - mean[2];
}
}
});
Hier erstellen wir einen Tensor der erforderlichen Größe (channels, paddedHeight, paddedWidth), greifen auf die Pixelwerte zu, verarbeiten sie vor und weisen sie schließlich den Tensor an den entsprechenden Indizes zu.
Eingaben einrichten
// Pin DenseTensor-Speicher und direkte Verwendung im OrtValue-Tensor // Wird beim Entsorgen von ortValue entpinned
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
input.Buffer, new long[] { 3, paddedHeight, paddedWidth });
Als Nächstes erstellen wir die Eingaben für das Modell.
var inputs = new Dictionary<string, OrtValue>
{
{ "image", 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-/Ausgabenamen anzuzeigen. In diesem Fall hat dieses Modell image als Eingabeknotennamen.
Inferenz ausführen
Als Nächstes erstellen wir eine Inferenzsitzung und führen die Eingabe daraus aus.
using var session = new InferenceSession(modelFilePath);
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = session.Run(runOptions, inputs, session.OutputNames);
Ausgabe nachbearbeiten
Als Nächstes müssen wir die Ausgabe nachbearbeiten, um Boxen und zugehörige Label sowie Konfidenzwerte für jede Box zu erhalten.
var boxesSpan = results[0].GetTensorDataAsSpan<float>();
var labelsSpan = results[1].GetTensorDataAsSpan<long>();
var confidencesSpan = results[2].GetTensorDataAsSpan<float>();
const float minConfidence = 0.7f;
var predictions = new List<Prediction>();
for (int i = 0; i < boxesSpan.Length - 4; i += 4)
{
var index = i / 4;
if (confidencesSpan[index] >= minConfidence)
{
predictions.Add(new Prediction
{
Box = new Box(boxesSpan[i], boxesSpan[i + 1], boxesSpan[i + 2], boxesSpan[i + 3]),
Label = LabelMap.Labels[labelsSpan[index]],
Confidence = confidencesSpan[index]
});
}
}
Beachten Sie, dass wir nur Boxen mit einer Konfidenz über 0,7 berücksichtigen, um Fehlalarme zu entfernen.
Vorhersage anzeigen
Als Nächstes zeichnen wir die Boxen und zugehörigen Labels und Konfidenzwerte auf das Bild, um zu sehen, wie das Modell abgeschnitten hat.
using var outputImage = File.OpenWrite(outImageFilePath);
Font font = SystemFonts.CreateFont("Arial", 16);
foreach (var p in predictions)
{
image.Mutate(x =>
{
x.DrawLines(Color.Red, 2f, new PointF[] {
new PointF(p.Box.Xmin, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymax),
new PointF(p.Box.Xmax, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymin)
});
x.DrawText($"{p.Label}, {p.Confidence:0.00}", font, Color.White, new PointF(p.Box.Xmin, p.Box.Ymin));
});
}
image.Save(outputImage, format);
Für jede Box-Vorhersage verwenden wir ImageSharp, um rote Linien zum Erstellen der Boxen zu zeichnen und den Label- und Konfidenztext anzuzeigen.
Ausführen des Programms
Nachdem das Programm erstellt wurde, können wir es mit dem folgenden Befehl ausführen.
dotnet run [path-to-model] [path-to-image] [path-to-output-image]
z.B. Ausführung
dotnet run ~/Downloads/FasterRCNN-10.onnx ~/Downloads/demo.jpg ~/Downloads/out.jpg
erkennt die folgenden Objekte im Bild
