Tutorial: Erstellen einer Desktopanwendung für Windows Machine Learning (C++)
Windows ML-APIs können genutzt werden, um problemlos mit Machine Learning-Modellen in C++-Desktopanwendungen (Win32) zu interagieren. Wenn Sie die drei Schritte zum Laden, Binden und Auswerten verwenden, kann Ihre Anwendung von der Leistungsfähigkeit von Machine Learning profitieren.
Wir erstellen eine etwas vereinfachte Version des SqueezeNet-Beispiels zum Erkennen von Objekten, das auf GitHub verfügbar ist. Sie können das vollständige Beispiel herunterladen, wenn Sie sehen möchten, wie es nach der Fertigstellung aussehen wird.
Wir verwenden C++/WinRT für den Zugriff auf die WinML-APIs. Weitere Informationen finden Sie unter C++/WinRT.
In diesem Tutorial lernen Sie Folgendes:
- Laden eines Machine Learning-Modells
- Laden eines Bilds als VideoFrame
- Binden der Ein- und Ausgaben des Modells
- Auswerten des Modells und Ausgeben aussagekräftiger Ergebnisse
Voraussetzungen
- Visual Studio 2019 (oder Visual Studio 2017, Version 15.7.4 oder höher)
- Windows 10, Version 1809 oder höher
- Windows SDK, Build 17763 oder höher
- Visual Studio-Erweiterung für C++/WinRT
- Wählen Sie in Visual Studio Extras > Erweiterungen und Updates aus.
- Wählen Sie im linken Bereich Online aus, und suchen Sie mithilfe des Suchfelds auf der rechten Seite nach „WinRT“.
- Wählen Sie C++/WinRT aus, klicken Sie auf Herunterladen, und schließen Sie Visual Studio.
- Befolgen Sie die Installationsanweisungen, und öffnen Sie Visual Studio dann erneut.
- Windows-Machine Learning-GitHub-Repository (Sie können es entweder als ZIP-Datei herunterladen oder auf Ihren Computer klonen)
Erstellen des Projekts
Zunächst erstellen wir das Projekt in Visual Studio:
- Wählen Sie Datei > Neu > Projekt aus, um das Fenster Neues Projekt zu öffnen.
- Wählen Sie im linken Bereich Installiert > Visual C++ > Windows-Desktop und dann in der Mitte Windows-Konsolenanwendung (C++/WinRT) aus.
- Weisen Sie Ihrem Projekt einen Namen und einen Speicherort zu, und klicken Sie dann auf OK.
- Legen Sie im Fenster Neues UWP-Projekt das Ziel und die Mindestversionen auf Build 17763 oder höher fest, und klicken Sie dann auf OK.
- Stellen Sie sicher, dass die Dropdownmenüs in der oberen Symbolleiste auf Debuggen und x64 oder x86 abhängig von der Architektur des Computers festgelegt sind.
- Drücken Sie STRG+F5, um das Programm ohne Debuggen auszuführen. Ein Terminal mit dem Text „Hello World“ sollte geöffnet werden. Drücken Sie eine beliebige Taste, um das Terminal zu schließen.
Laden des Modells
Als Nächstes laden wir das ONNX-Modell mithilfe von LearningModel.LoadFromFilePath in unser Programm:
Fügen Sie in pch.h (im Ordner Header Files) die folgenden
include
-Anweisungen hinzu (diese gewähren uns Zugriff auf alle APIs, die wir benötigen):#include <winrt/Windows.AI.MachineLearning.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Graphics.Imaging.h> #include <winrt/Windows.Media.h> #include <winrt/Windows.Storage.h> #include <string> #include <fstream> #include <Windows.h>
Fügen Sie in Main.cpp (im Ordner Source Files) die folgenden
using
-Anweisungen hinzu:using namespace Windows::AI::MachineLearning; using namespace Windows::Foundation::Collections; using namespace Windows::Graphics::Imaging; using namespace Windows::Media; using namespace Windows::Storage; using namespace std;
Fügen Sie nach den
using
-Anweisungen die folgenden Variablendeklarationen hinzu:// Global variables hstring modelPath; string deviceName = "default"; hstring imagePath; LearningModel model = nullptr; LearningModelDeviceKind deviceKind = LearningModelDeviceKind::Default; LearningModelSession session = nullptr; LearningModelBinding binding = nullptr; VideoFrame imageFrame = nullptr; string labelsFilePath; vector<string> labels;
Fügen Sie nach den globalen Variablen die folgenden Vorwärtsdeklarationen hinzu:
// Forward declarations void LoadModel(); VideoFrame LoadImageFile(hstring filePath); void BindModel(); void EvaluateModel(); void PrintResults(IVectorView<float> results); void LoadLabels();
Entfernen Sie in Main.cpp den Code für „Hello World“ (gesamter Code in der
main
-Funktion nachinit_apartment
).Suchen Sie in Ihrem lokalen Klon des Windows-Machine-Learning-Repositorys nach der Datei SqueezeNet.onnx. Sie sollte sich in \Windows-Machine-Learning\SharedContent\models befinden.
Kopieren Sie den Dateipfad, und weisen Sie ihn Ihrer
modelPath
-Variablen zu, in der er oben definiert wurde. Denken Sie daran, der Zeichenfolge einL
als Präfix voranzustellen, um sie zu einer Breitzeichenfolge zu machen, damit sie mithstring
ordnungsgemäß funktioniert, und versehen Sie etwaige umgekehrte Schrägstriche (\
) mit einem zusätzlichen umgekehrten Schrägstrich als Escapezeichen. Beispiel:hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
Zunächst implementieren wir die
LoadModel
-Methode. Fügen Sie nach dermain
-Methode die folgende Methode hinzu. Diese Methode lädt das Modell und gibt aus, wie lange der Vorgang gedauert hat:void LoadModel() { // load the model printf("Loading modelfile '%ws' on the '%s' device\n", modelPath.c_str(), deviceName.c_str()); DWORD ticks = GetTickCount(); model = LearningModel::LoadFromFilePath(modelPath); ticks = GetTickCount() - ticks; printf("model file loaded in %d ticks\n", ticks); }
Rufen Sie diese Methode schließlich aus der
main
-Methode auf:LoadModel();
Führen Sie Ihr Programm ohne Debuggen aus. Sie sollten sehen, dass das Modell erfolgreich geladen wird.
Laden des Bilds
Im nächsten Schritt laden wir die Bilddatei in unser Programm:
Fügen Sie die folgende -Methode hinzu. Diese Methode lädt das Bild aus dem angegebenen Pfad und erstellt daraus einen Videoframe:
VideoFrame LoadImageFile(hstring filePath) { printf("Loading the image...\n"); DWORD ticks = GetTickCount(); VideoFrame inputImage = nullptr; try { // open the file StorageFile file = StorageFile::GetFileFromPathAsync(filePath).get(); // get a stream on it auto stream = file.OpenAsync(FileAccessMode::Read).get(); // Create the decoder from the stream BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get(); // get the bitmap SoftwareBitmap softwareBitmap = decoder.GetSoftwareBitmapAsync().get(); // load a videoframe from it inputImage = VideoFrame::CreateWithSoftwareBitmap(softwareBitmap); } catch (...) { printf("failed to load the image file, make sure you are using fully qualified paths\r\n"); exit(EXIT_FAILURE); } ticks = GetTickCount() - ticks; printf("image file loaded in %d ticks\n", ticks); // all done return inputImage; }
Fügen Sie in der
main
-Methode einen Aufruf dieser Methode hinzu:imageFrame = LoadImageFile(imagePath);
Suchen Sie in Ihrem lokalen Klon des Windows-Machine-Learning-Repositorys nach dem Ordner media. Er sollte sich unter \Windows-Machine-Learning\SharedContent\media befinden.
Wählen Sie eines der Bilder in diesem Ordner aus, und weisen Sie seinen Dateipfad der
imagePath
-Variablen zu, in der wir ihn oben definiert haben. Denken Sie daran, ihm einL
als Präfix voranzustellen, um die Zeichenfolge zu einer Breitzeichenfolge zu machen, und versehen Sie etwaige umgekehrte Schrägstriche mit einem zusätzlichen umgekehrten Schrägstrich als Escapezeichen. Beispiel:hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
Führen Sie Ihr Programm ohne Debuggen aus. Das Bild sollte erfolgreich geladen worden sein.
Binden der Eingabe und der Ausgabe
Als Nächstes erstellen wir eine Sitzung auf der Grundlage des Modells und binden die Eingabe und Ausgabe der Sitzung mit LearningModelBinding.Bind. Weitere Informationen zu dieser Bindung finden Sie unter Binden eines Modells.
Implementieren Sie die
BindModel
-Methode. Dadurch wird eine auf dem Modell und dem Gerät basierende Sitzung sowie eine auf dieser Sitzung basierende Bindung erstellt. Anschließend binden wir die Eingaben und Ausgaben an Variablen, die mithilfe ihrer Namen erstellt wurden. Wir wissen bereits im Voraus, dass die Eingabefunktion den Namen „data_0“ und die Ausgabefunktion den Namen „softmaxout_1“ trägt. Sie können diese Eigenschaften für jedes Modell anzeigen, indem Sie sie in Netron öffnen, einem Online-Modellvisualisierungstool.void BindModel() { printf("Binding the model...\n"); DWORD ticks = GetTickCount(); // now create a session and binding session = LearningModelSession{ model, LearningModelDevice(deviceKind) }; binding = LearningModelBinding{ session }; // bind the intput image binding.Bind(L"data_0", ImageFeatureValue::CreateFromVideoFrame(imageFrame)); // bind the output vector<int64_t> shape({ 1, 1000, 1, 1 }); binding.Bind(L"softmaxout_1", TensorFloat::Create(shape)); ticks = GetTickCount() - ticks; printf("Model bound in %d ticks\n", ticks); }
Fügen Sie einen Aufruf von
BindModel
aus dermain
-Methode hinzu:BindModel();
Führen Sie Ihr Programm ohne Debuggen aus. Die Eingaben und Ausgaben des Modells sollten erfolgreich gebunden werden. Wir haben es fast geschafft.
Evaluieren des Modells
Wir befinden uns jetzt im letzten Schritt der Abbildung am Anfang dieses Tutorials: Auswerten. Das Modell wird mit LearningModelSession.Evaluate ausgewertet:
Implementieren Sie die
EvaluateModel
-Methode. Diese Methode nimmt unsere Sitzung an und wertet Sie mithilfe unserer Bindung und einer Korrelations-ID aus. Die Korrelations-ID kann später verwendet werden, um einen bestimmten Auswertungsaufruf mit den Ausgabeergebnissen abzugleichen. Auch hier wissen wir bereits im Voraus, dass der Name der Ausgabe „softmaxout_1“ lautet.void EvaluateModel() { // now run the model printf("Running the model...\n"); DWORD ticks = GetTickCount(); auto results = session.Evaluate(binding, L"RunId"); ticks = GetTickCount() - ticks; printf("model run took %d ticks\n", ticks); // get the output auto resultTensor = results.Outputs().Lookup(L"softmaxout_1").as<TensorFloat>(); auto resultVector = resultTensor.GetAsVectorView(); PrintResults(resultVector); }
Implementieren wir nun
PrintResults
. Diese Methode ruft die ersten drei Wahrscheinlichkeiten für das Objekt ab, das auf dem Bild abgebildet sein könnte, und gibt sie aus:void PrintResults(IVectorView<float> results) { // load the labels LoadLabels(); // Find the top 3 probabilities vector<float> topProbabilities(3); vector<int> topProbabilityLabelIndexes(3); // SqueezeNet returns a list of 1000 options, with probabilities for each, loop through all for (uint32_t i = 0; i < results.Size(); i++) { // is it one of the top 3? for (int j = 0; j < 3; j++) { if (results.GetAt(i) > topProbabilities[j]) { topProbabilityLabelIndexes[j] = i; topProbabilities[j] = results.GetAt(i); break; } } } // Display the result for (int i = 0; i < 3; i++) { printf("%s with confidence of %f\n", labels[topProbabilityLabelIndexes[i]].c_str(), topProbabilities[i]); } }
Wir müssen auch
LoadLabels
implementieren. Diese Methode öffnet die Datei mit den Bezeichnungen, die die unterschiedlichen Objekte enthält, die vom Modell erkannt werden können, und analysiert sie:void LoadLabels() { // Parse labels from labels file. We know the file's entries are already sorted in order. ifstream labelFile{ labelsFilePath, ifstream::in }; if (labelFile.fail()) { printf("failed to load the %s file. Make sure it exists in the same folder as the app\r\n", labelsFilePath.c_str()); exit(EXIT_FAILURE); } std::string s; while (std::getline(labelFile, s, ',')) { int labelValue = atoi(s.c_str()); if (labelValue >= labels.size()) { labels.resize(labelValue + 1); } std::getline(labelFile, s); labels[labelValue] = s; } }
Suchen Sie in Ihrem lokalen Klon des Windows-Machine-Learning-Repositorys nach der Datei Labels.txt. Sie sollte sich in \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp befinden.
Weisen Sie diesen Dateipfad der
labelsFilePath
-Variablen zu, in der wir ihn oben definiert haben. Stellen Sie sicher, dass alle umgekehrten Schrägstriche mit einem weiteren umgekehrten Schrägstrich als Escapezeichen versehen werden. Beispiel:string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
Fügen Sie einen Aufruf von
EvaluateModel
in dermain
-Methode hinzu:EvaluateModel();
Führen Sie Ihr Programm ohne Debuggen aus. Es sollte nun richtig erkannt werden, welches Objekt auf dem Bild abgebildet ist. Im Folgenden finden Sie ein Beispiel für eine mögliche Ausgabe:
Loading modelfile 'C:\Repos\Windows-Machine-Learning\SharedContent\models\SqueezeNet.onnx' on the 'default' device model file loaded in 250 ticks Loading the image... image file loaded in 78 ticks Binding the model...Model bound in 15 ticks Running the model... model run took 16 ticks tabby, tabby cat with confidence of 0.931461 Egyptian cat with confidence of 0.065307 Persian cat with confidence of 0.000193
Nächste Schritte
Herzlichen Glückwunsch, die Objekterkennung funktioniert in einer C++-Desktopanwendung! Als Nächstes können Sie versuchen, das Modell und die Bilddateien mithilfe von Befehlszeilenargumenten einzugeben, anstatt sie hart zu codieren, ähnlich wie es im Beispiel auf GitHub der Fall ist. Sie können auch versuchen, die Auswertung auf einem anderen Gerät (z. B. einer GPU) auszuführen, um zu sehen, wie sich die Leistung unterscheidet.
Experimentieren Sie mit den anderen Beispielen auf GitHub, und erweitern Sie sie ganz nach Wunsch!
Siehe auch
Hinweis
Verwenden Sie die folgenden Ressourcen, wenn Sie Hilfe mit Windows ML benötigen:
- Wenn Sie technische Fragen zu Windows ML stellen oder beantworten möchten, verwenden Sie das Tag windows-machine-learning auf Stack Overflow.
- Wenn Sie einen Fehler melden möchten, erstellen Sie eine Anfrage auf GitHub.