Partager via


Traiter des bitmaps avec OpenCV

Cet article explique comment utiliser la classe SoftwareBitmap, qui est utilisée par de nombreuses API Windows Runtime différentes pour représenter des images, avec la bibliothèque Open Source Vision par ordinateur (OpenCV), une bibliothèque de code native open source qui fournit un large éventail d’algorithmes de traitement d’images.

Les exemples de cet article vous guident tout au long de la création d’un composant Windows Runtime de code natif qui peut être utilisé à partir d’une application UWP, y compris les applications créées à l’aide de C#. Ce composant d’assistance expose une méthode unique, Blur, qui utilisera la fonction de flou d’image d’OpenCV. Le composant implémente des méthodes privées qui obtiennent un pointeur vers la mémoire tampon de données d’image sous-jacente qui peut être utilisée directement par la bibliothèque OpenCV, ce qui simplifie l’extension du composant d’assistance pour implémenter d’autres fonctionnalités de traitement OpenCV.

Remarque

La technique utilisée par le composant OpenCVHelper, décrite en détail dans cet article, nécessite que les données d’image soient traitées réside dans la mémoire processeur, et non dans la mémoire GPU. Par conséquent, pour les API qui vous permettent de demander l’emplacement de mémoire des images, telles que la classe MediaCapture, vous devez spécifier la mémoire processeur.

Créer un composant Windows Runtime d’assistance pour l’interopérabilité OpenCV

1. Ajouter un nouveau projet de composant Windows Runtime de code natif à votre solution

  1. Ajoutez un nouveau projet à votre solution dans Visual Studio en cliquant avec le bouton droit sur votre solution dans l’Explorateur de solutions et en sélectionnant Ajouter>nouveau projet.
  2. Sous la catégorie Visual C++, sélectionnez Composant Windows Runtime (Universal Windows). Pour cet exemple, nommez le projet « OpenCVBridge », puis cliquez sur OK.
  3. Dans la boîte de dialogue nouveau projet universel Windows , sélectionnez la version cible et minimale du système d’exploitation de votre application, puis cliquez sur OK.
  4. Cliquez avec le bouton droit sur le fichier généré automatiquement Class1.cpp dans l’Explorateur de solutions et sélectionnez Supprimer, lorsque la boîte de dialogue de confirmation s’affiche, choisissez Supprimer. Supprimez ensuite le fichier d’en-tête Class1.h.
  5. Cliquez avec le bouton droit sur l’icône du projet OpenCVBridge, puis sélectionnez Ajouter-Classe.... Dans la boîte de dialogue Ajouter une classe, entrez « OpenCVHelper » dans le champ nom de classe , puis cliquez sur OK. Le code sera ajouté aux fichiers de classe créés dans une étape ultérieure.

2. Ajouter les packages NuGet OpenCV à votre projet de composant

  1. Cliquez avec le bouton droit sur l’icône du projet OpenCVBridge dans l’Explorateur de solutions, puis sélectionnez Gérer les packages NuGet...
  2. Lorsque la boîte de dialogue Gestionnaire de package NuGet s’ouvre, sélectionnez l’onglet Parcourir et tapez « OpenCV.Win » dans la zone de recherche.
  3. Sélectionnez « OpenCV.Win.Core », puis cliquez sur Installer. Dans la boîte de dialogue aperçu , cliquez sur OK.
  4. Utilisez la même procédure pour installer le package « OpenCV.Win.ImgProc ».

Remarque

OpenCV.Win.Core et OpenCV.Win.ImgProc ne sont pas régulièrement mis à jour et ne passent pas les vérifications de conformité du Windows Store. Par conséquent, ces packages sont destinés à l’expérimentation uniquement.

3. Implémenter la classe OpenCVHelper

Collez le code suivant dans le fichier d’en-tête OpenCVHelper.h. Ce code inclut les fichiers d’en-tête OpenCV pour les core et packages ImgProc que nous avons installés et déclare trois méthodes qui seront affichées dans les étapes suivantes.

#pragma once

// OpenCVHelper.h
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>


namespace OpenCVBridge
{
    public ref class OpenCVHelper sealed
    {
    public:
        OpenCVHelper() {}

        void Blur(
            Windows::Graphics::Imaging::SoftwareBitmap^ input,
            Windows::Graphics::Imaging::SoftwareBitmap^ output);


    private:
        // helper functions for getting a cv::Mat from SoftwareBitmap
        bool TryConvert(Windows::Graphics::Imaging::SoftwareBitmap^ from, cv::Mat& convertedMat);
        bool GetPointerToPixelData(Windows::Graphics::Imaging::SoftwareBitmap^ bitmap,
            unsigned char** pPixelData, unsigned int* capacity);
    };
}

Supprimez le contenu existant du fichier OpenCVHelper.cpp, puis ajoutez les directives include suivantes.

#include "pch.h"
#include "OpenCVHelper.h"
#include "MemoryBuffer.h"

Après les directives Include, ajoutez le suivant à l’aide de directives.

using namespace OpenCVBridge;
using namespace Platform;
using namespace Windows::Graphics::Imaging;
using namespace Windows::Foundation;
using namespace Microsoft::WRL;
using namespace cv;

Ensuite, ajoutez la méthode GetPointerToPixelData à OpenCVHelper.cpp. Cette méthode accepte un SoftwareBitmap et, par le biais d’une série de conversions, obtient une représentation d’interface COM des données de pixels via laquelle nous pouvons obtenir un pointeur vers de la mémoire tampon de données sous-jacente sous forme de tableau de caractères char.

Tout d’abord, une BitmapBuffer contenant les données de pixels est obtenue en appelant LockBuffer, en demandant une mémoire tampon en lecture/écriture afin que la bibliothèque OpenCV puisse modifier ces données de pixels. createReference est appelée pour obtenir un objet IMemoryBufferReference. Ensuite, l’interface IMemoryBufferByteAccess est convertie en tant qu’IInspectable, l’interface de base de toutes les classes Windows Runtime, et QueryInterface est appelée pour obtenir une interface IMemoryBufferByteAccess COM qui nous permettra d’obtenir le tampon de données de pixels en tant que tableau de caractères . Enfin, remplissez le tableau char en appelant IMemoryBufferByteAccess ::GetBuffer. Si l’une des étapes de conversion de cette méthode échoue, la méthode retourne faux, ce qui indique que le traitement supplémentaire ne peut pas continuer.

bool OpenCVHelper::GetPointerToPixelData(SoftwareBitmap^ bitmap, unsigned char** pPixelData, unsigned int* capacity)
{
    BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
    IMemoryBufferReference^ reference = bmpBuffer->CreateReference();

    ComPtr<IMemoryBufferByteAccess> pBufferByteAccess;
    if ((reinterpret_cast<IInspectable*>(reference)->QueryInterface(IID_PPV_ARGS(&pBufferByteAccess))) != S_OK)
    {
        return false;
    }

    if (pBufferByteAccess->GetBuffer(pPixelData, capacity) != S_OK)
    {
        return false;
    }
    return true;
}

Ensuite, ajoutez la méthode TryConvert illustrée ci-dessous. Cette méthode prend une SoftwareBitmap et tente de la convertir en objet Mat, qui est l’objet matrice qu’OpenCV utilise pour représenter des mémoires tampons de données d’image. Cette méthode appelle la méthode GetPointerToPixelData définie ci-dessus pour obtenir une représentation sous forme de tableau de caractères char de la mémoire tampon de données de pixels. Si cela fonctionne, le constructeur de la classe Mat est appelé, et il reçoit la largeur et la hauteur des pixels obtenues à partir de l’objet source SoftwareBitmap.

Remarque

Cet exemple spécifie la constante CV_8UC4 comme format de pixel pour l’objet Mat créé. Cela signifie que le SoftwareBitmap passé dans cette méthode doit avoir une valeur de propriété BitmapPixelFormat de BGRA8 avec alpha prémultiplié, l’équivalent de CV_8UC4, pour utiliser cet exemple.

Une copie superficielle de l’objet Mat créé est retournée par la méthode afin que le traitement ultérieur fonctionne sur le même tampon de données de pixels référencé par le SoftwareBitmap et non une copie de ce tampon.

bool OpenCVHelper::TryConvert(SoftwareBitmap^ from, Mat& convertedMat)
{
    unsigned char* pPixels = nullptr;
    unsigned int capacity = 0;
    if (!GetPointerToPixelData(from, &pPixels, &capacity))
    {
        return false;
    }

    Mat mat(from->PixelHeight,
        from->PixelWidth,
        CV_8UC4, // assume input SoftwareBitmap is BGRA8
        (void*)pPixels);
    
    // shallow copy because we want convertedMat.data = pPixels
    // don't use .copyTo or .clone
    convertedMat = mat;
    return true;
}

Enfin, cet exemple de classe d’assistance implémente une méthode de traitement d’image unique, Blur, qui utilise simplement la méthode TryConvert définie ci-dessus pour récupérer un objet Mat représentant la bitmap source et la bitmap cible pour l’opération de flou, puis appelle la méthode flou à partir de la bibliothèque OpenCV ImgProc. L’autre paramètre pour flou spécifie l'ampleur de l’effet flou dans les directions X et Y.

void OpenCVHelper::Blur(SoftwareBitmap^ input, SoftwareBitmap^ output)
{
    Mat inputMat, outputMat;
    if (!(TryConvert(input, inputMat) && TryConvert(output, outputMat)))
    {
        return;
    }
    blur(inputMat, outputMat, cv::Size(15, 15));
}

Exemple simple d'utilisation de SoftwareBitmap avec OpenCV, à l’aide du composant auxiliaire.

Maintenant que le composant OpenCVBridge a été créé, nous pouvons créer une application C# simple qui utilise la méthode OpenCV floue pour modifier un SoftwareBitmap. Pour accéder au composant Windows Runtime à partir de votre application UWP, vous devez d’abord ajouter une référence au composant. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le nœud Références sous votre projet d’application UWP, puis sélectionnez Ajouter une référence.... Dans la boîte de dialogue Gestionnaire de références, sélectionnez Projets->Solution. Cochez la case en regard du projet OpenCVBridge et cliquez sur OK.

L’exemple de code ci-dessous permet à l’utilisateur de sélectionner un fichier image, puis utilise BitmapDecoder pour créer une représentation SoftwareBitmap de l’image. Pour plus d’informations sur l’utilisation de SoftwareBitmap, consultez Créer, modifier et enregistrer des images bitmap.

Comme indiqué précédemment dans cet article, la classe OpenCVHelper nécessite que toutes les images SoftwareBitmap fournies soient encodées à l’aide du format BGRA8 pixel avec des valeurs alpha prémultipliées. Par conséquent, si l’image n’est pas déjà dans ce format, l’exemple de code appelle Convertir pour convertir l’image dans le format attendu.

Ensuite, un SoftwareBitmap est créé pour être utilisé comme cible de l’opération de flou. Les propriétés d’image d’entrée sont utilisées comme arguments pour le constructeur pour créer une bitmap au format correspondant.

Une nouvelle instance de OpenCVHelper est créée, et la méthode Blur est appelée, en passant les bitmaps source et cible. Enfin, un SoftwareBitmapSource est créé pour affecter l’image de sortie à un contrôle Image XAML.

Cet exemple de code utilise des API à partir des espaces de noms suivants, en plus des espaces de noms inclus par le modèle de projet par défaut.

using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media.Imaging;
FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;

var inputFile = await fileOpenPicker.PickSingleFileAsync();

if (inputFile == null)
{
    // The user cancelled the picking operation
    return;
}

SoftwareBitmap inputBitmap;
using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
{
    // Create the decoder from the stream
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

    // Get the SoftwareBitmap representation of the file
    inputBitmap = await decoder.GetSoftwareBitmapAsync();
}

if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8
            || inputBitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
{
    inputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
    
SoftwareBitmap outputBitmap = new SoftwareBitmap(inputBitmap.BitmapPixelFormat, inputBitmap.PixelWidth, inputBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);


var helper = new OpenCVBridge.OpenCVHelper();
helper.Blur(inputBitmap, outputBitmap);

var bitmapSource = new SoftwareBitmapSource();
await bitmapSource.SetBitmapAsync(outputBitmap);
imageControl.Source = bitmapSource;