Tutoriel : créer votre propre Windows Vision Skill (C#)

Remarque

L’espace de noms Microsoft.AI.Skills.SkillInterfacePreview est déconseillé, car il est hérité et n’est plus maintenu. Il a été remplacé par un package non surveillé offrant les mêmes fonctionnalités ainsi que des performances et une fiabilité accrues : Microsoft.AI.Skills.SkillInterface. Il est prévu que cette documentation soit mise à jour pour refléter les nouvelles fonctionnalités.

Si vous disposez déjà d’une solution de vision personnalisée, ce tutoriel montre comment wrapper la solution dans une compétence de vision Windows en étendant l’API de base Microsoft.AI.Skills.SkillInterfacePreview.

Nous allons créer une compétence d’analyse des sentiments du visage qui s’appuie sur :

Cette compétence prend :

  • Une image d’entrée

Et elle génère :

  • Un tenseur de valeurs de score de précision uniques dans la plage [0, 1] pour chaque sentiment qu’elle évalue
  • Un tenseur de valeurs flottantes dans la plage [0, 1] définissant le cadre englobant d’un visage en coordonnées relatives : left (x,y), top (x,y), right (x,y) et bottom (x,y)

Diagram of the example FaceSentimentAnalysis skill's inputs and outputs

Le code source complet pour les versions C# et C++/WinRT de cet exemple est disponible dans le dépôt GitHub des exemples :

Ce tutoriel vous guide tout au long des étapes suivantes :

  1. Implémentation des interfaces principales requises pour une compétence de vision Windows
  2. Création d’un fichier .nuspec pour produire un package NuGet
  3. Obfuscation et désobfuscation des fichiers pour masquer leur contenu

Prérequis

1. Créer et implémenter les principales classes de compétences

Tout d’abord, nous devons implémenter les principales classes de compétence (pour plus d’informations, consultez Concepts d’API importants) :

Ouvrez votre solution de vision personnalisée dans Visual Studio.

a. ISkillDescriptor

Créez et implémentez une classe de descripteur de compétence héritée de ISkillDescriptor qui fournit des informations sur la compétence, fournit une liste des appareils d’exécution pris en charge (processeur, GPU, etc.) et fait office d’objet de fabrique pour la compétence.

  1. Importez l’espace de noms Microsoft.AI.Skills.SkillInterfacePreview et dérivez votre classe de l’interface [ISkillDescriptor][ISkillDescriptor].

    ...
    using Microsoft.AI.Skills.SkillInterfacePreview;
    ...
    
    public sealed class FaceSentimentAnalyzerDescriptor : ISkillDescriptor
    {
        ...
    }
    
  2. Créez deux variables membres destinées à contenir les descriptions des entrées et des sorties requises par la compétence. Ensuite, dans le constructeur du descripteur, remplissez-les de façon appropriée avec les descripteurs de fonctionnalités d’entrée et de sortie. En outre, créez l’objet SkillInformation qui fournit toutes les propriétés de description nécessaires de votre compétence.

    ...
    // Member variables to hold input and output descriptions
    private List<ISkillFeatureDescriptor> m_inputSkillDesc;
    private List<ISkillFeatureDescriptor> m_outputSkillDesc;
    
    // Properties required by the interface
    public IReadOnlyList<ISkillFeatureDescriptor> InputFeatureDescriptors => m_inputSkillDesc;
    public IReadOnlyDictionary<string, string> Metadata => null;
    public IReadOnlyList<ISkillFeatureDescriptor> OutputFeatureDescriptors => m_outputSkillDesc;
    
    // Constructor
    public FaceSentimentAnalyzerDescriptor()
    {
        Information = SkillInformation.Create(
                "FaceSentimentAnalyzer", // Name
                "Finds a face in the image and infers its predominant sentiment from a set of 8 possible labels", // Description
                new Guid(0xf8d275ce, 0xc244, 0x4e71, 0x8a, 0x39, 0x57, 0x33, 0x5d, 0x29, 0x13, 0x88), // Id
                new Windows.ApplicationModel.PackageVersion() { Major = 0, Minor = 0, Build = 0, Revision = 8 }, // Version
                "Contoso Developer", // Author
                "Contoso Publishing" // Publisher
            );
    
        // Describe input feature
        m_inputSkillDesc = new List<ISkillFeatureDescriptor>();
        m_inputSkillDesc.Add(
            new SkillFeatureImageDescriptor(
                "InputImage", // skill input feature name
                "the input image onto which the sentiment analysis runs",
                true, // isRequired (since this is an input, it is required to be bound before the evaluation occurs)
                -1, // width
                -1, // height
                -1, // maxDimension
                BitmapPixelFormat.Nv12,
                BitmapAlphaMode.Ignore)
        );
    
        // Describe first output feature
        m_outputSkillDesc = new List<ISkillFeatureDescriptor>();
        m_outputSkillDesc.Add(
            new SkillFeatureTensorDescriptor(
                "FaceRectangle", // skill output feature name
                "a face bounding box in relative coordinates (left, top, right, bottom)",
                false, // isRequired (since this is an output, it automatically get populated after the evaluation occurs)
                new List<long>() { 4 }, // tensor shape
                SkillElementKind.Float)
            );
    
        // Describe second output feature
        m_outputSkillDesc.Add(
            new SkillFeatureTensorDescriptor(
                FaceSentimentScores, // skill output feature name
                "the prediction score for each class",
                false, // isRequired (since this is an output, it automatically get populated after the evaluation occurs)
                new List<long>() { 1, 8 }, // tensor shape
                SkillElementKind.Float)
            );
    }
    
    
  3. Implémentez la méthode requise qui recherche les appareils d’exécution pris en charge sur lesquels il est possible d’exécuter la compétence et les retourne au consommateur. Dans notre cas, nous retournons le processeur et tous les périphériques DirectX prenant en charge D3D version 12 ou ultérieure.

    public IAsyncOperation<IReadOnlyList<ISkillExecutionDevice>> GetSupportedExecutionDevicesAsync()
    {
        return AsyncInfo.Run(async (token) =>
        {
            return await Task.Run(() =>
            {
                var result = new List<ISkillExecutionDevice>();
    
                // Add CPU as supported device
                result.Add(SkillExecutionDeviceCPU.Create());
    
                // Retrieve a list of DirectX devices available on the system and filter them by keeping only the ones that support DX12+ feature level
                var devices = SkillExecutionDeviceDirectX.GetAvailableDirectXExecutionDevices();
                var compatibleDevices = devices.Where((device) => (device as SkillExecutionDeviceDirectX).MaxSupportedFeatureLevel >= D3DFeatureLevelKind.D3D_FEATURE_LEVEL_12_0);
                result.AddRange(compatibleDevices);
    
                return result as IReadOnlyList<ISkillExecutionDevice>;
            });
        });
    
    
  4. Implémentez les méthodes requises pour l’instanciation de votre compétence.

    • L’une d’entre elles sélectionne le meilleur appareil disponible :

      public IAsyncOperation<ISkill> CreateSkillAsync()
      {
          return AsyncInfo.Run(async (token) =>
          {
              // Retrieve the available execution devices
              var supportedDevices = await GetSupportedExecutionDevicesAsync();
              ISkillExecutionDevice deviceToUse = supportedDevices.First();
      
              // Either use the first device returned (CPU) or the highest performing GPU
              int powerIndex = int.MaxValue;
              foreach (var device in supportedDevices)
              {
                  if (device.ExecutionDeviceKind == SkillExecutionDeviceKind.Gpu)
                  {
                      var directXDevice = device as SkillExecutionDeviceDirectX;
                      if (directXDevice.HighPerformanceIndex < powerIndex)
                      {
                          deviceToUse = device;
                          powerIndex = directXDevice.HighPerformanceIndex;
                      }
                  }
              }
              return await CreateSkillAsync(deviceToUse);
          });
      }
      
    • L’autre utilise l’appareil d’exécution spécifié :

      public IAsyncOperation<ISkill> CreateSkillAsync(ISkillExecutionDevice executionDevice)
      {
          return AsyncInfo.Run(async (token) =>
          {
              // Create a skill instance with the executionDevice supplied
              var skillInstance = await FaceSentimentAnalyzerSkill.CreateAsync(this, executionDevice);
      
              return skillInstance as ISkill;
          });
      }
      

b. ISkillBinding

Créez et implémentez une classe de liaison de compétence héritée de l’interface [ISkillBinding][ISkillBinding] qui contient les variables d’entrée et de sortie consommées et produites par la compétence.

  1. Importez l’espace de noms Microsoft.AI.Skills.SkillInterfacePreview et dérivez votre classe de l’interface [ISkillBinding][ISkillBinding] et son type de collection requis.

    ...
    using Microsoft.AI.Skills.SkillInterfacePreview;
    ...
    
    public sealed class FaceSentimentAnalyzerBinding : IReadOnlyDictionary<string, ISkillFeature>, ISkillBinding
    {
        ...
    
    
  2. Commencez par créer deux variables membres :

    • L’une est une classe d’assistance [VisionSkillBindingHelper][VisionSkillBindingHelper] fournie dans l’interface de base et destinée à contenir une fonctionnalité d’image d’entrée nommée « InputImage ».
    private VisionSkillBindingHelper m_bindingHelper = null;
    
    • L’autre contient le LearningModelBinding à passer à notre LearningModelSession spécifié en tant qu’argument pour notre constructeur par la suite dans la classe de compétence.
    // WinML related member variables
    internal LearningModelBinding m_winmlBinding = null;
    

    Déclarez les propriétés requises :

    // ISkillBinding
    public ISkillExecutionDevice Device => m_bindingHelper.Device;
    
    // IReadOnlyDictionary
    public bool ContainsKey(string key) => m_bindingHelper.ContainsKey(key);
    public bool TryGetValue(string key, out ISkillFeature value) => m_bindingHelper.TryGetValue(key, out value);
    public ISkillFeature this[string key] => m_bindingHelper[key];
    public IEnumerable<string> Keys => m_bindingHelper.Keys;
    public IEnumerable<ISkillFeature> Values => m_bindingHelper.Values;
    public int Count => m_bindingHelper.Count;
    public IEnumerator<KeyValuePair<string, ISkillFeature>> GetEnumerator() => m_bindingHelper.AsEnumerable().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => m_bindingHelper.AsEnumerable().GetEnumerator();
    

    Et Implémentez le constructeur. Notez que le constructeur est interne ; dans notre paradigme, les instances ISkillBinding sont créées par la compétence et ne doivent donc pas exposer de constructeur autonome :

     // Constructor
    internal FaceSentimentAnalyzerBinding(
        ISkillDescriptor descriptor,
        ISkillExecutionDevice device,
        LearningModelSession session)
    {
        m_bindingHelper = new VisionSkillBindingHelper(descriptor, device);
    
        // Create WinML binding
        m_winmlBinding = new LearningModelBinding(session);
    }
    
  3. Créez une énumération qui facilite la lecture des types de sentiments générés par la compétence.

    /// Defines the set of possible emotion label scored by this skill
    public enum SentimentType
    {
        neutral = 0,
        happiness,
        surprise,
        sadness,
        anger,
        disgust,
        fear,
        contempt
    };
    
  4. Implémentez des méthodes supplémentaires facultatives qui facilitent l’obtention et la définition des opérations sur la liaison.

    /// Returns whether or not a face is found given the bound outputs
    public bool IsFaceFound
    {
        get
        {
            ISkillFeature feature = null;
            if (m_bindingHelper.TryGetValue(FaceSentimentAnalyzerConst.SKILL_OUTPUTNAME_FACERECTANGLE, out feature))
            {
                var faceRect = (feature.FeatureValue as SkillFeatureTensorFloatValue).GetAsVectorView();
                return !(faceRect[0] == 0.0f &&
                    faceRect[1] == 0.0f &&
                    faceRect[2] == 0.0f &&
                    faceRect[3] == 0.0f);
            }
            else
            {
                return false;
            }
        }
    }
    
    /// Returns the sentiment with the highest score
    public SentimentType PredominantSentiment
    {
        get
        {
            SentimentType predominantSentiment = SentimentType.neutral;
            ISkillFeature feature = null;
            if (m_bindingHelper.TryGetValue(FaceSentimentAnalyzerConst.SKILL_OUTPUTNAME_FACESENTIMENTSCORES, out feature))
            {
                var faceSentimentScores = (feature.FeatureValue as SkillFeatureTensorFloatValue).GetAsVectorView();
    
                float maxScore = float.MinValue;
                for (int i = 0; i < faceSentimentScores.Count; i++)
                {
                    if (faceSentimentScores[i] > maxScore)
                    {
                        predominantSentiment = (SentimentType)i;
                        maxScore = faceSentimentScores[i];
                    }
                }
            }
    
            return predominantSentiment;
        }
    }
    
    /// Returns the face rectangle
    public IReadOnlyList<float> FaceRectangle
    {
        get
        {
            ISkillFeature feature = null;
            if (m_bindingHelper.TryGetValue(FaceSentimentAnalyzerConst.SKILL_OUTPUTNAME_FACERECTANGLE, out feature))
            {
                return (feature.FeatureValue as SkillFeatureTensorFloatValue).GetAsVectorView();
            }
            else
            {
                return null;
            }
        }
    }
    

c. ISkill

Créez et implémentez une classe de compétence héritée de l’interface [ISkill][ISkill] qui exécute la logique de compétence et produit une sortie en fonction d’un jeu d’entrées. Elle fait également office d’objet de fabrique pour la dérivée ISkillBinding.

  1. Importez l’espace de noms Microsoft.AI.Skills.SkillInterfacePreview et dérivez votre classe de l’interface [ISkill][ISkill].

    ...
    using Microsoft.AI.Skills.SkillInterfacePreview;
    ...
    
    public sealed class FaceSentimentAnalyzerSkill : ISkill
    {
        ...
    
  2. Commencez par créer deux variables membres :

    • L’une destinée à contenir un FaceDetector pour trouver un visage sur l’image d’entrée.
    private FaceDetector m_faceDetector = null;
    
    • Une autre destinée à contenir le LearningModelSession utilisé pour évaluer le modèle d’analyse des sentiments :
    private LearningModelSession m_winmlSession = null;
    

    Déclarez les propriétés requises :

    public ISkillDescriptor SkillDescriptor { get; private set; }
    public ISkillExecutionDevice Device { get; private set; }
    

    Et implémentez le constructeur et la méthode de fabrique statique. Notez que le constructeur est privé et que la méthode de fabrique est interne ; dans notre paradigme, les instances ISkill sont créées par le descripteur de la compétence et ne doivent donc pas exposer de constructeur autonome :

     // Constructor
    private FaceSentimentAnalyzerSkill(
            ISkillDescriptor description,
            ISkillExecutionDevice device)
    {
        SkillDescriptor = description;
        Device = device;
    }
    
    // ISkill Factory method
    internal static IAsyncOperation<FaceSentimentAnalyzerSkill> CreateAsync(
        ISkillDescriptor descriptor,
        ISkillExecutionDevice device)
    {
        return AsyncInfo.Run(async (token) =>
        {
            // Create instance
            var skillInstance = new FaceSentimentAnalyzerSkill(descriptor, device);
    
            // Instantiate the FaceDetector
            skillInstance.m_faceDetector = await FaceDetector.CreateAsync();
    
            // Load ONNX model and instantiate LearningModel
            var modelFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Contoso.FaceSentimentAnalyzer/emotion_ferplus.onnx"));
            var winmlModel = LearningModel.LoadFromFilePath(modelFile.Path);
    
            // Create LearningModelSession
            skillInstance.m_winmlSession = new LearningModelSession(winmlModel, GetWinMLDevice(device));
    
            return skillInstance;
        });
    }
    
  3. Implémentez ensuite la méthode de fabrique ISkillBinding :

    // ISkillBinding Factory method
    public IAsyncOperation<ISkillBinding> CreateSkillBindingAsync()
    {
        return AsyncInfo.Run((token) =>
        {
            var completedTask = new TaskCompletionSource<ISkillBinding>();
            completedTask.SetResult(new FaceSentimentAnalyzerBinding(SkillDescriptor, Device, m_winmlSession));
            return completedTask.Task;
        });
    }
    
  4. Tout ce qui reste à implémenter maintenant est la logique principale de la compétence par le biais de la méthode EvaluateAsync() déclarée dans l’interface de base. Nous commençons par vérifier la validité et récupérons les fonctionnalités de sortie à remplir.

    // Skill core logic
    public IAsyncAction EvaluateAsync(ISkillBinding binding)
    {
        FaceSentimentAnalyzerBinding bindingObj = binding as FaceSentimentAnalyzerBinding;
        if (bindingObj == null)
        {
            throw new ArgumentException("Invalid ISkillBinding parameter: This skill handles evaluation of FaceSentimentAnalyzerBinding instances only");
        }
    
        return AsyncInfo.Run(async (token) =>
        {
            // Retrieve input frame from the binding object
            VideoFrame inputFrame = (binding[FaceSentimentAnalyzerConst.SKILL_INPUTNAME_IMAGE].FeatureValue as SkillFeatureImageValue).VideoFrame;
            SoftwareBitmap softwareBitmapInput = inputFrame.SoftwareBitmap;
    
            // Retrieve a SoftwareBitmap to run face detection
            if (softwareBitmapInput == null)
            {
                if (inputFrame.Direct3DSurface == null)
                {
                    throw (new ArgumentNullException("An invalid input frame has been bound"));
                }
                softwareBitmapInput = await SoftwareBitmap.CreateCopyFromSurfaceAsync(inputFrame.Direct3DSurface);
            }
    
            // Retrieve face rectangle output feature from the binding object
            var faceRectangleFeature = binding[FaceSentimentAnalyzerConst.SKILL_OUTPUTNAME_FACERECTANGLE];
    
            // Retrieve face sentiment scores output feature from the binding object
            var faceSentimentScores = binding[FaceSentimentAnalyzerConst.SKILL_OUTPUTNAME_FACESENTIMENTSCORES];
            ...
    

    Ensuite, cette compétence particulière se poursuit en 2 étapes :

    • Étape 1 : exécutez FaceDetector sur l’image et récupérez le rectangle englobant englobant du visage.
            ...
            // Run face detection and retrieve face detection result
            var faceDetectionResult = await m_faceDetector.DetectFacesAsync(softwareBitmapInput);
            ...
    
    • Étape 2 : si un visage a été détecté, ajustez le rectangle englobant, normalisez ses coordonnées pour faciliter l’utilisation et poursuivez l’analyse des sentiments de cette partie de l’image à l’aide de Windows.AI.MachineLearning. Une fois l’inférence effectuée, mettez à jour le score de chaque sentiment possible retourné comme résultat.
            ...
            // If a face is found, update face rectangle feature
            if (faceDetectionResult.Count > 0)
            {
                // Retrieve the face bound and enlarge it by a factor of 1.5x while also ensuring clamping to frame dimensions
                BitmapBounds faceBound = faceDetectionResult[0].FaceBox;
                var additionalOffset = faceBound.Width / 2;
                faceBound.X = Math.Max(0, faceBound.X - additionalOffset);
                faceBound.Y = Math.Max(0, faceBound.Y - additionalOffset);
                faceBound.Width = (uint)Math.Min(faceBound.Width + 2 * additionalOffset, softwareBitmapInput.PixelWidth - faceBound.X);
                faceBound.Height = (uint)Math.Min(faceBound.Height + 2 * additionalOffset, softwareBitmapInput.PixelHeight - faceBound.Y);
    
                // Set the face rectangle SkillFeatureValue in the skill binding object
                // note that values are in normalized coordinates between [0, 1] for ease of use
                await faceRectangleFeature.SetFeatureValueAsync(
                    new List<float>()
                    {
                            (float)faceBound.X / softwareBitmapInput.PixelWidth, // left
                            (float)faceBound.Y / softwareBitmapInput.PixelHeight, // top
                            (float)(faceBound.X + faceBound.Width) / softwareBitmapInput.PixelWidth, // right
                            (float)(faceBound.Y + faceBound.Height) / softwareBitmapInput.PixelHeight // bottom
                    });
    
                // Bind the WinML input frame with the adequate face bounds specified as metadata
                bindingObj.m_winmlBinding.Bind(
                    "Input3", // WinML input feature name defined in ONNX protobuf
                    inputFrame, // VideoFrame
                    new PropertySet() // VideoFrame bounds
                    {
                        { "BitmapBounds",
                            PropertyValue.CreateUInt32Array(new uint[]{ faceBound.X, faceBound.Y, faceBound.Width, faceBound.Height })
                        }
                    });
    
                // Run WinML evaluation
                var winMLEvaluationResult = await m_winmlSession.EvaluateAsync(bindingObj.m_winmlBinding, "");
    
                // Retrieve result using the WinML output feature name defined in ONNX protobuf
                var winMLModelResult = (winMLEvaluationResult.Outputs["Plus692_Output_0"] as TensorFloat).GetAsVectorView();
    
                // Set the SkillFeatureValue in the skill binding object related to the face sentiment scores for each possible SentimentType
                // note that we SoftMax the output of WinML to give a score normalized between [0, 1] for ease of use
                var predictionScores = SoftMax(winMLModelResult);
                await faceSentimentScores.SetFeatureValueAsync(predictionScores);
            }
            else // if no face found, reset output SkillFeatureValues with 0s
            {
                await faceRectangleFeature.SetFeatureValueAsync(FaceSentimentAnalyzerConst.ZeroFaceRectangleCoordinates);
                await faceSentimentScores.SetFeatureValueAsync(FaceSentimentAnalyzerConst.ZeroFaceSentimentScores);
            }
        });
    }
    

2. Empaqueter votre compétence dans NuGet

Il ne reste plus qu’à compiler votre compétence et à créer un package NuGet à partir de celle-ci afin qu’une application puisse l’ingérer.

(Découvrez-en plus sur les packages NuGet ici)

Pour créer un package NuGet, vous devez écrire un fichier .nuspec comme celui ci-dessous consultez le fichier d’origine dans le dépôt Git. Ce fichier est composé de deux sections principales :

  • métadonnées : cette partie contient le nom, la description, l’auteur, le propriétaire, la licence et les dépendances. Notez que dans notre cas, nous dépendons du package NuGet Microsoft.AI.Skills.SkillInterfacePreview. Ce package NuGet est également lié à une licence et déclenche une demande d’approbation avant ingestion.

  • fichiers : cette partie pointe vers vos bits et ressources compilés. Notez que l’emplacement cible pointe vers la version de framework uap10.0.17763. Cela permet de s’assurer que les applications ingérant votre package qui ciblent une version antérieure à 10.0.17763 (version minimale du système d’exploitation requise par cette compétence) recevront un message d’erreur.

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <metadata>
        <!-- Required elements-->
        <id>Contoso.FaceSentimentAnalyzer_CS</id>
        <title>Contoso.FaceSentimentAnalyzer_CS</title>
        <version>0.0.0.5</version>
        <description>Face Sentiment Analyzer skill sample that extends the Microsoft.AI.Skills.SkillInterfacePreview APIs</description>
        <authors>Contoso</authors>
        <owners>Contoso</owners>
        <copyright>Copyright (c) Microsoft Corporation.  All rights reserved.</copyright>
        <requireLicenseAcceptance>true</requireLicenseAcceptance>
        <licenseUrl>https://github.com/Microsoft/WindowsVisionSkillsPreview/blob/master/license/doc/LICENSE_package.md</licenseUrl>
        <projectUrl>https://github.com/Microsoft/WindowsVisionSkillsPreview</projectUrl>
        <iconUrl>https://github.com/Microsoft/WindowsVisionSkillsPreview/blob/master/doc/Logo.png?raw=true</iconUrl>
        <releaseNotes>v0.0.0.5 release https://github.com/Microsoft/WindowsVisionSkillsPreview/releases</releaseNotes>
        <dependencies>
            <dependency id="Microsoft.AI.Skills.SkillInterfacePreview" version="0.5.2.12" />
        </dependencies>
        <tags>ComputerVision AI VisionSkill</tags>
    </metadata>

    <files>
        <!-- WinMD, Intellisense and resource files-->
        <file src="..\common\emotion_ferplus.onnx" target="lib\uap10.0.17763\Contoso.FaceSentimentAnalyzer" />
        <file src="..\cs\FaceSentimentAnalyzer\bin\Release\Contoso.FaceSentimentAnalyzer.winmd" target="lib\uap10.0.17763" />
        <file src="..\cs\FaceSentimentAnalyzer\bin\Release\Contoso.FaceSentimentAnalyzer.pri" target="lib\uap10.0.17763" />
        <file src="..\common\Contoso.FaceSentimentAnalyzer.xml" target="lib\uap10.0.17763" />
    </files>
</package>

Vous devez ensuite empaqueter votre fichier .nuspec à l’aide de nuget.exe (télécharger à partir du site officiel) pour créer un fichier de package NuGet .nupkg. Ouvrez une ligne de commande et accédez à l’emplacement de nuget.exe, puis appelez :

> .\nuget.exe pack <path to your .nuspec>

Pour tester votre package localement, vous pouvez ensuite placer ce fichier .nupkg dans un dossier que vous définissez en tant que flux NuGet dans Visual Studio (consultez la procédure ici).

Bravo, vous avez créé votre première compétence de vision Windows ! Vous pouvez charger la compétence empaquetée sur NuGet.org.

3. Une dernière chose… Obfuscation et désobfuscation des fichiers de ressources pour masquer votre propriété intellectuelle

Pour dissuader le consommateur de falsifier vos ressources de compétence ou d’y accéder (fichiers de modèle, images, etc.), vous pouvez obfusquer les fichiers en guise d’étape de prégénération et les désobfusquer au moment de l’exécution. L’exemple dans notre dépôt GitHub d’exemples contient l’implémentation de classes d’assistance qui tirent parti de Windows.Security.Cryptography pour obfusquer les fichiers au moment de la compilation et les désobfusquer au moment de l’exécution. Notez que cette partie n’est montrée que dans la version C++/WinRT de l’exemple de compétence afin de simplifier la version C#.

  • L’obfuscation est un événement prébuild que vous pouvez définir dans votre projet afin qu’il l’exécute tout le temps ou qu’il l’exécute une seule fois et utilise la sortie comme ressource directement. Dans cet exemple, nous utilisons un outil compilé dédié (Obfuscator.exe). Vous devez veiller à compiler cet outil avant de l’appeler en tant qu’événement prébuild de la compilation de votre compétence. Notez qu’étant donné qu’il s’exécute sur votre ordinateur de développement au moment de la compilation, vous pouvez le compiler une seule fois à l’aide de n’importe quelle cible et plateforme prises en charge (en l’occurrence, Debug/Win32).

    Vous pouvez définir cet événement prébuild dans Visual Studio en procédant comme suit :

  • Projet C++ : cliquez avec le bouton droit sur votre projet de compétence -> agrandissez Créer un événement -> sélectionnez Événement prébuild -> entrez la Ligne de commande

  • Projet C# : cliquez avec le bouton droit sur votre projet de compétence -> sélectionnez Créer un événement-> entrez la Ligne de commande de l’événement prébuild

    Cette commande : 1 : Copie le fichier de ressource localement 2 : Chiffre le fichier au format .crypt (il peut s’agir de n’importe quel nom d’extension) à l’aide de la logique définie qui nécessite une clé GUID 3 : Supprime le fichier local

Remarque

Nous vous suggérons de modifier la logique de chiffrement proposée dans l’exemple afin de la rendre unique pour votre compétence.

copy $(ProjectDir)..\..\Common\emotion_ferplus.onnx $(ProjectDir) &amp;&amp; ^$(ProjectDir)..\Obfuscator\Win32\Debug\Obfuscator.exe $(ProjectDir)emotion_ferplus.onnx $(ProjectDir) emotion_ferplus.crypt 678BD455-4190-45D3-B5DA-41543283C092 &amp;&amp; ^del $(ProjectDir)emotion_ferplus.onnx
  • La désobfuscation est exposé par le biais d’un composant Windows Runtime d’application auxiliaire simple ingéré par la compétence. Sa logique de déchiffrement suit la logique de chiffrement définie à l’étape précédente.

    // FaceSentimentAnalyzerSkill.cpp
    ...
    #include "winrt/DeobfuscationHelper.h"
    ...
    
    // ISkill Factory method
    Windows::Foundation::IAsyncOperation<winrt::Contoso::FaceSentimentAnalyzer::FaceSentimentAnalyzerSkill> FaceSentimentAnalyzerSkill::CreateAsync(
        ISkillDescriptor descriptor,
        ISkillExecutionDevice device)
    {
        ...
    
        // Load WinML model
        auto modelFile = Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(Windows::Foundation::Uri(L"ms-appx:///Contoso.FaceSentimentAnalyzer/" + WINML_MODEL_FILENAME)).get();
    
        // Deobfuscate model file and retrieve LearningModel instance
        LearningModel learningModel = winrt::DeobfuscationHelper::Deobfuscator::DeobfuscateModelAsync(modelFile, descriptor.Id()).get();
    
        ...
    
    

Remarque

Utilisez les ressources suivantes pour obtenir de l’aide sur Windows Vision Skills :

  • Pour signaler un bogue, veuillez signaler un problème dans notre plateforme GitHub.
  • Pour demander une fonctionnalité, veuillez accéder à la page Windows Developer Feedback.