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.

Load - Bind ->> Evaluate

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
    1. Wählen Sie in Visual Studio Extras > Erweiterungen und Updates aus.
    2. Wählen Sie im linken Bereich Online aus, und suchen Sie mithilfe des Suchfelds auf der rechten Seite nach „WinRT“.
    3. Wählen Sie C++/WinRT aus, klicken Sie auf Herunterladen, und schließen Sie Visual Studio.
    4. 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:

  1. Wählen Sie Datei > Neu > Projekt aus, um das Fenster Neues Projekt zu öffnen.
  2. Wählen Sie im linken Bereich Installiert > Visual C++ > Windows-Desktop und dann in der Mitte Windows-Konsolenanwendung (C++/WinRT) aus.
  3. Weisen Sie Ihrem Projekt einen Namen und einen Speicherort zu, und klicken Sie dann auf OK.
  4. 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.
  5. 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.
  6. 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:

  1. 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>
    
  2. 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;
    
  3. 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;
    
  4. 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();
    
  5. Entfernen Sie in Main.cpp den Code für „Hello World“ (gesamter Code in der main-Funktion nach init_apartment).

  6. 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.

  7. Kopieren Sie den Dateipfad, und weisen Sie ihn Ihrer modelPath-Variablen zu, in der er oben definiert wurde. Denken Sie daran, der Zeichenfolge ein L als Präfix voranzustellen, um sie zu einer Breitzeichenfolge zu machen, damit sie mit hstring 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";
    
  8. Zunächst implementieren wir die LoadModel-Methode. Fügen Sie nach der main-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);
    }
    
  9. Rufen Sie diese Methode schließlich aus der main-Methode auf:

    LoadModel();
    
  10. 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:

  1. 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;
    }
    
  2. Fügen Sie in der main-Methode einen Aufruf dieser Methode hinzu:

    imageFrame = LoadImageFile(imagePath);
    
  3. 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.

  4. 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 ein L 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";
    
  5. 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.

  1. 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);
    }
    
  2. Fügen Sie einen Aufruf von BindModel aus der main-Methode hinzu:

    BindModel();
    
  3. 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:

  1. 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);
    }
    
  2. 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]);
        }
    }
    
  3. 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;
        }
    }
    
  4. 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.

  5. 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";
    
  6. Fügen Sie einen Aufruf von EvaluateModel in der main-Methode hinzu:

    EvaluateModel();
    
  7. 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.