本文說明如何使用 SoftwareBitmap 類別,此類別由許多不同的 Windows 運行時間 API 用來代表影像,而開放原始碼電腦視覺連結庫(OpenCV),這是一種開放原始碼的原生程式代碼連結庫,可提供各種不同的影像處理演算法。
本文中的範例會逐步引導您建立可從 UWP 應用程式使用的原生程式代碼 Windows 執行時間元件,包括使用 C# 建立的應用程式。 此協助程式元件會公開單一方法,Blur,這會使用 OpenCV 的模糊影像處理函式。 元件會實作私有方法,以取得可供 OpenCV 庫直接使用的底層影像數據緩衝區的指標,讓您輕鬆地擴充輔助元件以實作其他 OpenCV 處理功能。
- 如需使用 SoftwareBitmap的簡介,請參閱 建立、編輯和儲存位圖影像。
- 若要瞭解如何使用 OpenCV 庫,請移至 https://opencv.org。
- 若要瞭解如何使用本文所示的 OpenCV 協助程式元件搭配 MediaFrameReader 來實現攝影機的即時影像處理,請參閱 使用 OpenCV 與 MediaFrameReader。
- 如需實作一些不同效果的完整程式碼範例,請參閱 Windows 通用範例 GitHub 存放庫中的 Camera Frames + OpenCV 範例。
備註
本文詳述的 OpenCVHelper 元件所使用的技術,需要處理映射數據位於 CPU 記憶體中,而不是 GPU 記憶體中。 因此,對於可讓您要求映像的記憶體位置的 API,例如 MediaCapture 類別,您應該指定 CPU 記憶體。
建立用於 OpenCV 互操作的輔助 Windows 執行階段元件
1. 將新的本機程式碼 Windows 運行時間元件專案新增至您的解決方案
- 在 Visual Studio 中,以滑鼠右鍵按一下方案總管中的方案,然後選取 [新增-]>[專案],以在 Visual Studio 中新增專案。
- 在 [Visual C++] 類別下,選取 [Windows 執行階段元件 (通用 Windows)]。 在這個範例中,將專案命名為 "OpenCVBridge",然後按下 確定。
- 在 [新增 Windows 通用專案] 對話框中,選取您 app 的目標和最低 OS 版本,然後按兩下 [確定] [確定]。
- 以滑鼠右鍵按兩下 [方案總管] 中自動產生的檔案Class1.cpp,然後選取 [ 移除],當確認對話框快顯時,選擇 [ 刪除]。 然後刪除 Class1.h 頭檔。
- 右鍵點擊 OpenCVBridge 專案圖示,然後選取 [Add->Class...]。在 [Add Class] 對話框中,在 [Class Name] 欄位中輸入 "OpenCVHelper",然後點擊 [OK] 。 程序代碼將會在稍後的步驟中新增至已建立的類別檔案。
2.將 OpenCV NuGet 套件新增至元件專案
- 以滑鼠右鍵按兩下 [方案總管] 中的 OpenCVBridge 專案圖示,然後選取 [管理 NuGet 套件] ...
- 當 [NuGet 套件管理員] 對話框開啟時,選取 [流覽] 索引卷標,然後在搜尋方塊中輸入 “OpenCV.Win”。
- 選取 [OpenCV.Win.Core],然後按一下 [安裝]。 在 [Preview] 對話框中,按 [確定] 。
- 使用相同的程式來安裝 「OpenCV.Win.ImgProc」 套件。
備註
OpenCV.Win.Core 和 OpenCV.Win.ImgProc 不會定期更新,也不會通過市集合規性檢查,因此這些套件僅供測試使用。
3.實作 OpenCVHelper 類別
將下列程式代碼貼到 OpenCVHelper.h 頭檔中。 此程式碼包含我們已安裝的 Core 和 ImgProc 套件的 OpenCV 標頭檔,並宣告將在以下步驟中展示的三個函式。
#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);
};
}
刪除OpenCVHelper.cpp檔案的現有內容,然後新增下列 include 指示詞。
#include "pch.h"
#include "OpenCVHelper.h"
#include "MemoryBuffer.h"
在 include 指示詞之後,使用 指示詞新增下列
using namespace OpenCVBridge;
using namespace Platform;
using namespace Windows::Graphics::Imaging;
using namespace Windows::Foundation;
using namespace Microsoft::WRL;
using namespace cv;
接下來,將 getPointerToPixelData 方法新增至 OpenCVHelper.cpp。 這個方法會採用 SoftwareBitmap,並透過一系列轉換取得像素數據的 COM 介面表示法,透過此介面我們可以取得基礎數據緩衝區的指標,作為 字符 陣列。
首先,呼叫 lockBuffer來
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;
}
接下來,新增 TryConvert 方法,如下所示。 此方法會採用 SoftwareBitmap,並嘗試將它轉換成 Mat 物件,這是 OpenCV 用來表示影像數據緩衝區的矩陣物件。 此方法會呼叫上述定義的 GetPointerToPixelData 方法來取得像素數據緩衝區的 字元 陣列表示。 如果成功,則會呼叫 Mat 類別的建構函式,並傳入從來源 SoftwareBitmap 物件取得的圖元寬度和高度。
備註
本範例會將CV_8UC4常數指定為所建立 Mat 物件的圖元格式。 這表示傳遞至此方法的 SoftwareBitmap 必須具有 BitmapPixelFormat 屬性值為 BGRA8 並使用預乘的 Alpha,相當於 CV_8UC4,才能使用此範例。
會從 方法傳回所建立 Mat 對象的淺層複本,以便進一步處理 SoftwareBitmap 所參考的相同數據圖元數據緩衝區,而不是此緩衝區的複本。
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;
}
最後,這個範例協助程序類別會實作單一影像處理方法,Blur,它只會使用上述定義的 TryConvert 方法來擷取代表來源位圖和模糊作業目標位圖的 Mat 物件,然後從 OpenCV ImgProc 連結庫呼叫 模糊 方法。 要 模糊 的另一個參數會指定 X 和 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));
}
使用輔助組件的簡單 SoftwareBitmap OpenCV 範例
現在已建立 OpenCVBridge 元件,我們可以建立一個簡單的 C# 應用程式,使用 OpenCV 的 模糊 方法來修改 SoftwareBitmap。 若要從 UWP 應用程式存取 Windows 執行時間元件,您必須先新增元件的參考。 在 [方案總管] 中,以滑鼠右鍵按兩下 UWP 應用程式專案下 [參考] 節點,然後選取 [[新增參考...]。在 [參考管理員] 對話框中,選取 [專案->方案]。 勾選 OpenCVBridge 專案旁的方塊,然後按一下 [確定]。
下列範例程式代碼可讓用戶選取圖像檔案,然後使用 BitmapDecoder 來建立影像 SoftwareBitmap 表示法。 如需使用 SoftwareBitmap的詳細資訊,請參閱 建立、編輯和儲存位圖影像。
如本文先前所述,OpenCVHelper 類別會要求所有提供的 SoftwareBitmap 影像都使用具有預乘 Alpha 值的 BGRA8 像素格式進行編碼,因此如果影像尚未採用此格式,範例程式代碼會呼叫 Convert,將影像轉換成預期的格式。
接下來,我們將建立 SoftwareBitmap,以用作模糊作業的目標。 輸入影像屬性會作為建構函式的自變數,以建立具有相符格式的點陣圖。
會建立 OpenCVHelper 的新實例,並呼叫 Blur 方法,並傳入來源和目標位圖。 最後,會建立 SoftwareBitmapSource,將輸出影像指派給 XAML Image 控件。
此範例程式代碼除了預設專案範本所包含的命名空間之外,也會使用來自下列命名空間的 API。
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;
相關主題