Partilhar via


Tutorial: Criar um aplicativo de área de trabalho do Windows Machine Learning (C++)

As APIs de ML do Windows podem ser aproveitadas para interagir facilmente com modelos de aprendizado de máquina em aplicativos de área de trabalho C++ (Win32). Usando as três etapas de carregamento, vinculação e avaliação, seu aplicativo pode se beneficiar do poder do aprendizado de máquina.

Carregar -> Vincular -> Avaliar

Criaremos uma versão um pouco simplificada do exemplo SqueezeNet Object Detection, que está disponível no GitHub. Você pode baixar o exemplo completo se quiser ver como será quando terminar.

Usaremos C++/WinRT para acessar as APIs do WinML. Consulte C++/WinRT para obter mais informações.

Neste tutorial, você aprenderá a:

  • Carregar um modelo de aprendizado de máquina
  • Carregar a imagem como um VideoFrame
  • Vincular as entradas e saídas do modelo
  • Avalie o modelo e imprima resultados significativos

Pré-requisitos

Criar o projeto

Primeiro, criaremos o projeto no Visual Studio:

  1. Selecione Ficheiro > Novo > Projeto para abrir a janela Novo Projeto.
  2. No painel esquerdo, selecione Installed > Visual C++ > Windows Desktop e, no meio, selecione Windows Console Application (C++/WinRT).
  3. Dê ao seu projeto um Nome e um Local e clique em OK.
  4. Na janela Novo Projeto da Plataforma Universal do Windows, defina as Versões de Destino e Mínima para criar 17763 ou posterior e clique em OK.
  5. Verifique se os menus suspensos na barra de ferramentas superior estão definidos para Depurar e x64 ou x86, dependendo da arquitetura do seu computador.
  6. Pressione Ctrl+F5 para executar o programa sem depuração. Um terminal deve abrir com algum texto "Olá mundo". Pressione qualquer tecla para fechá-lo.

Carregue o modelo

Em seguida, carregaremos o modelo ONNX em nosso programa usando LearningModel.LoadFromFilePath:

  1. Em pch.h (na pasta Arquivos de cabeçalho ), adicione as seguintes include instruções (elas nos dão acesso a todas as APIs de que precisamos):

    #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. No main.cpp (na pasta Arquivos de código-fonte), adicione as seguintes instruções:using

    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. Adicione as seguintes declarações de variáveis após as using instruções:

    // 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. Adicione as seguintes declarações de encaminhamento após suas variáveis globais:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. Em main.cpp, remova o código "Hello world" (tudo na main função depois init_apartment).

  6. Encontre o arquivo SqueezeNet.onnx em seu clone local do repositório Windows-Machine-Learning . Ele deve estar localizado em \Windows-Machine-Learning\SharedContent\models.

  7. Copie o caminho do arquivo e atribua-o à sua modelPath variável onde o definimos na parte superior. Lembre-se de prefixar a cadeia de caracteres com um L para torná-la uma cadeia de caracteres larga para que funcione corretamente com hstring, e escape qualquer barra invertida (\) com uma barra invertida extra. Por exemplo:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Primeiro, implementaremos o LoadModel método. Adicione o seguinte método após o main método. Este método carrega o modelo e produz quanto tempo ele levou:

    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. Finalmente, chame este método a partir de main.

    LoadModel();
    
  10. Execute o programa sem depuração. Você deve ver que seu modelo carrega com sucesso!

Carregue a imagem

Em seguida, carregaremos o arquivo de imagem em nosso programa:

  1. Adicione o seguinte método. Este método carregará a imagem do caminho fornecido e criará um VideoFrame a partir dele:

    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. Adicione uma chamada a este método no main método:

    imageFrame = LoadImageFile(imagePath);
    
  3. Encontre a pasta de mídia em seu clone local do repositório Windows-Machine-Learning . Ele deve estar localizado em \Windows-Machine-Learning\SharedContent\media.

  4. Escolha uma das imagens nessa pasta e atribua seu caminho de arquivo à imagePath variável onde a definimos na parte superior. Lembre-se de prefixá-lo com um L para torná-lo uma cadeia de caracteres larga e para escapar de qualquer barra invertida com outra barra invertida. Por exemplo:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Execute o programa sem depuração. Você deve ver a imagem carregada com sucesso!

Vincular a entrada e a saída

Em seguida, criaremos uma sessão com base no modelo e vincularemos a entrada e a saída da sessão usando LearningModelBinding.Bind. Para obter mais informações sobre vinculação, consulte Vincular um modelo.

  1. Implemente o método BindModel. Isso cria uma sessão com base no modelo e no dispositivo, e uma associação com base nessa sessão. Em seguida, vinculamos as entradas e saídas às variáveis que criamos usando seus nomes. Acontece que sabemos de antemão que o recurso de entrada é chamado de "data_0" e o recurso de saída é chamado de "softmaxout_1". Você pode ver essas propriedades para qualquer modelo abrindo-as no Netron, uma ferramenta de visualização de modelo online.

    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. Adicione uma chamada a BindModel partir do main método:

    BindModel();
    
  3. Execute o programa sem depuração. As entradas e saídas do modelo devem ser vinculadas com êxito. Estamos quase lá!

Avaliar o modelo

Estamos agora na última etapa do diagrama no início deste tutorial, Avaliar. Vamos avaliar o modelo usando LearningModelSession.Avaliar:

  1. Implemente o método EvaluateModel. Este método pega nossa sessão e a avalia usando nossa ligação e uma ID de correlação. O ID de correlação é algo que poderíamos usar mais tarde para fazer corresponder uma chamada de avaliação específica aos resultados de saída. Mais uma vez, sabemos de antemão que o nome da saída é "softmaxout_1".

    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. Agora vamos implementar PrintResults. Este método obtém as três principais probabilidades para o objeto que poderia estar na imagem e as imprime:

    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. Precisamos também de implementar LoadLabels. Esse método abre o arquivo de rótulos que contém todos os diferentes objetos que o modelo pode reconhecer e o analisa:

    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. Localize o arquivo Labels.txt em seu clone local do repositório Windows-Machine-Learning . Ele deve estar em \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Atribua esse caminho de arquivo à labelsFilePath variável onde o definimos na parte superior. Certifique-se de escapar de qualquer barra invertida com outra barra invertida. Por exemplo:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Adicione uma chamada ao EvaluateModel no método main.

    EvaluateModel();
    
  7. Execute o programa sem depuração. Agora deve reconhecer corretamente o que está na imagem! Aqui está um exemplo do que ele pode gerar:

    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
    

Próximos passos

Viva, você conseguiu pôr a deteção de objetos a funcionar numa aplicação de desktop C++! Em seguida, você pode tentar usar argumentos de linha de comando para inserir os arquivos de modelo e imagem em vez de codificá-los, semelhante ao que o exemplo no GitHub faz. Você também pode tentar executar a avaliação em um dispositivo diferente, como a GPU, para ver como o desempenho difere.

Brinque com as outras amostras no GitHub e estenda-as como quiser!

Ver também

Observação

Use os seguintes recursos para obter ajuda com o Windows ML:

  • Para fazer ou responder a perguntas técnicas sobre o Windows ML, use a tag windows-machine-learning em Stack Overflow.
  • Para relatar um bug, registre um problema em nosso GitHub.