Condividi tramite


Elaborare mappe di bit con OpenCV

Questo articolo illustra come usare la classe SoftwareBitmap, usata da molte API di Windows Runtime diverse per rappresentare le immagini, con Open SOURCE Computer Vision Library (OpenCV), una libreria di codice nativa open source che fornisce un'ampia gamma di algoritmi di elaborazione delle immagini.

Gli esempi in questo articolo illustrano come creare un componente Windows Runtime di codice nativo che può essere usato da un'app UWP, incluse le app create con C#. Questo componente helper esporrà un singolo metodo, Blur, che userà la funzione di sfocatura dell'immagine di OpenCV. Il componente implementa metodi privati che permettono di ottenere un puntatore al buffer di dati immagine sottostante, utilizzabile direttamente dalla libreria OpenCV, rendendo più semplice estendere il componente di supporto per implementare ulteriori funzionalità di elaborazione con OpenCV.

Annotazioni

La tecnica usata dal componente OpenCVHelper, descritta in dettaglio in questo articolo, richiede che i dati dell'immagine vengano elaborati risiedono nella memoria DELLA CPU e non nella memoria GPU. Pertanto, per le API che consentono di richiedere la posizione di memoria delle immagini, ad esempio la classe MediaCapture , è necessario specificare la memoria della CPU.

Creare un componente Windows Runtime helper per l'interoperabilità OpenCV

1. Aggiungere un nuovo progetto di componente Windows Runtime di codice nativo alla soluzione

  1. Aggiungere un nuovo progetto alla soluzione in Visual Studio facendo clic con il pulsante destro del mouse sulla soluzione in Esplora soluzioni e selezionando Aggiungi>Nuovo progetto.
  2. Nella categoria Visual C++, selezionare Componente Windows Runtime (Universal Windows). Per questo esempio, denominare il progetto "OpenCVBridge" e fare clic su OK.
  3. Nella finestra di dialogo Nuovo progetto universale di Windows, selezionare la versione di sistema operativo di destinazione e minima per l'app e fare clic su OK.
  4. Fare clic con il pulsante destro del mouse sul file generato automaticamente Class1.cpp in Esplora soluzioni e scegliere Rimuovi, quando viene visualizzata la finestra di dialogo di conferma, scegliere Elimina. Eliminare quindi il file di intestazione Class1.h.
  5. Fare clic con il pulsante destro del mouse sull'icona del progetto OpenCVBridge e selezionare Aggiungi-Classe.... Nella finestra di dialogo Aggiungi classe immettere "OpenCVHelper" nel campo Nome classe e quindi fare clic su OK. Il codice verrà aggiunto ai file di classe creati in un passaggio successivo.

2. Aggiungere i pacchetti NuGet OpenCV al progetto del componente

  1. Fare clic con il pulsante destro del mouse sull'icona del progetto OpenCVBridge in Esplora soluzioni e scegliere Gestisci pacchetti NuGet...
  2. Quando si apre la finestra di dialogo "Gestione pacchetti NuGet", selezionare la scheda Sfoglia e digitare "OpenCV.Win" nella casella di ricerca.
  3. Seleziona "OpenCV.Win.Core" e fai clic su Installa. Nella finestra di dialogo anteprima, fare clic su OK.
  4. Usare la stessa procedura per installare il pacchetto "OpenCV.Win.ImgProc".

Annotazioni

OpenCV.Win.Core e OpenCV.Win.ImgProc non vengono aggiornati regolarmente e non superano i controlli di conformità dello Store, pertanto questi pacchetti sono destinati solo alla sperimentazione.

3. Implementare la classe OpenCVHelper

Incollare il codice seguente nel file di intestazione OpenCVHelper.h. Questo codice include i file di intestazione OpenCV dei pacchetti Core e ImgProc che abbiamo installato e dichiara tre metodi che verranno mostrati nei passaggi seguenti.

#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);
    };
}

Eliminare il contenuto esistente del file OpenCVHelper.cpp e quindi aggiungere le direttive di inclusione seguenti.

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

Dopo le direttive di inclusione, aggiungere le seguenti usando le direttive.

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

Aggiungere quindi il metodo GetPointerToPixelData a OpenCVHelper.cpp. Questo metodo accetta un SoftwareBitmap e, tramite una serie di conversioni, ottiene una rappresentazione dell'interfaccia COM dei dati dei pixel attraverso la quale è possibile ottenere un puntatore al buffer di dati sottostante sotto forma di un array di char .

Prima di tutto, un BitmapBuffer contenente i dati pixel viene ottenuto chiamando LockBuffer, richiedendo un buffer di lettura/scrittura in modo che la libreria OpenCV possa modificare tali dati pixel. viene chiamato CreateReference per ottenere un oggetto IMemoryBufferReference. Successivamente, viene eseguito il cast dell'interfaccia IMemoryBufferByteAccess come IInspectable, l'interfaccia di base di tutte le classi di Windows Runtime e queryInterface viene chiamata per ottenere un interfaccia IMemoryBufferByteAccess COM che consente di ottenere il buffer dei dati pixel come matrice char . Infine, popolare la matrice char chiamando IMemoryBufferByteAccess::GetBuffer. Se uno dei passaggi di conversione in questo metodo ha esito negativo, il metodo restituisce false, a indicare che un'ulteriore elaborazione non può continuare.

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;
}

Aggiungere quindi il metodo TryConvert illustrato di seguito. Questo metodo accetta un SoftwareBitmap e tenta di convertirlo in un oggetto Mat, che è l'oggetto matrice usato da OpenCV per rappresentare buffer di dati immagine. Questo metodo chiama il metodo GetPointerToPixelData definito in precedenza per ottenere una rappresentazione di array char del buffer di dati pixel. Se l'operazione ha esito positivo, viene chiamato il costruttore per la classe Mat, passando la larghezza e l'altezza dei pixel ottenuta dall'oggetto SoftwareBitmap di origine.

Annotazioni

In questo esempio viene specificata la costante CV_8UC4 come formato pixel per l'oggetto Mat creato. Ciò significa che il SoftwareBitmap passato a questo metodo deve avere un valore BitmapPixelFormat proprietà di BGRA8 con alfa premoltiplicato, equivalente a CV_8UC4, per lavorare con questo esempio.

Una copia superficiale dell'oggetto Mat creato viene restituita dal metodo in modo che un'ulteriore elaborazione funzioni sullo stesso buffer di dati pixel a cui fa riferimento il SoftwareBitmap e non una copia del buffer.

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;
}

Infine, questa classe helper di esempio implementa un singolo metodo di elaborazione delle immagini, Blur, che usa semplicemente il metodo TryConvert definito in precedenza per recuperare un oggetto Mat che rappresenta la bitmap di origine e la bitmap di destinazione per l'operazione di sfocatura e quindi chiama il metodo sfocatura dalla libreria ImgProc OpenCV. L'altro parametro di sfocatura specifica la dimensione dell'effetto sfocatura nelle direzioni X e 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));
}

Un semplice esempio di SoftwareBitmap con OpenCV usando il componente di supporto

Ora che il componente OpenCVBridge è stato creato, è possibile creare una semplice app C# che usa il metodo OpenCV sfocatura per modificare un SoftwareBitmap. Per accedere al componente Windows Runtime dall'app UWP, devi prima aggiungere un riferimento al componente. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo Riferimenti nel progetto dell'app UWP e selezionare Aggiungi riferimento. Nella finestra di dialogo Gestione riferimenti selezionare Projects->Solution. Seleziona la casella accanto al progetto OpenCVBridge e fai clic su OK.

Il codice di esempio seguente consente all'utente di selezionare un file di immagine e quindi usa BitmapDecoder per creare una rappresentazione dell'immagine con SoftwareBitmap. Per altre informazioni sull'uso di SoftwareBitmap, vedere Creare, modificare e salvare immagini bitmap.

Come illustrato in precedenza in questo articolo, la classe OpenCVHelper richiede che tutte le immagini SoftwareBitmap fornite vengano codificate usando il formato di pixel BGRA8 con valori alfa premoltiplicati, quindi se l'immagine non è già in questo formato, il codice di esempio chiama Convert per convertire l'immagine nel formato previsto.

Successivamente, viene creato un SoftwareBitmap da usare come obiettivo dell'operazione di sfocatura. Le proprietà dell'immagine di input vengono usate come argomenti per il costruttore per creare una bitmap con formato corrispondente.

Viene creata una nuova istanza di OpenCVHelper e viene chiamato il metodo Blur, passando le bitmap di origine e di destinazione. Infine, viene creato un SoftwareBitmapSource per assegnare l'immagine di output a un controllo XAML Immagine.

Questo codice di esempio usa le API degli spazi dei nomi seguenti, oltre agli spazi dei nomi inclusi nel modello di progetto predefinito.

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;