本文說明如何建立原生 C++/WinRT Windows 執行階段 元件,將 SoftwareBitmap 物件轉換為 OpenCV Mat 型態。 這讓你能在 Windows 相機 API 捕捉的幀上使用 OpenCV 的廣泛影像處理演算法,並在 WinUI 3 應用程式中顯示結果。
概觀
這個SoftwareBitmap類別是 Windows 媒體 API 常用的影像格式,而 OpenCV 則使用這個Mat類別。 為了橋接這些格式,你會建立一個 C++/WinRT 執行時元件,該元件:
- 接受
SoftwareBitmap輸入。 - 將其轉換為 OpenCV
Mat。 - 套用所需的影像處理。
- 回傳結果為
SoftwareBitmap。
因為 OpenCV 是原生的 C++ 函式庫,你會使用 C++/WinRT 的 Windows 執行階段 元件專案來建立橋接器。 你的 C# WinUI 3 應用程式會參考這個元件。
建立 C++/WinRT 元件專案
在 Visual Studio 中,新增一個新的 Windows 執行階段 元件(C++/WinRT)專案到你的解決方案中。 把它命名為類似
OpenCVBridge的名稱。請在 封裝管理員 主控台執行以下指令,針對橋接專案下載 OpenCV NuGet 套件:
Install-Package OpenCV.Windows -ProjectName OpenCVBridge或者,從 opencv.org 下載 OpenCV 版本,並手動設定專案的包含路徑和函式庫路徑。
在橋接專案的
pch.h中,加入 OpenCV 標頭檔:#include <opencv2/core.hpp> #include <opencv2/imgproc.hpp> #include <robuffer.h> #include <windows.foundation.h>
建立 OpenCVHelper 執行時類別
定義一個執行時類別,提供 與 OpenCV SoftwareBitmap之間轉換Mat的方法。 建立 IDL 檔案 OpenCVHelper.idl:
// OpenCVHelper.idl
namespace OpenCVBridge
{
runtimeclass OpenCVHelper
{
OpenCVHelper();
void ProcessBitmap(
Windows.Graphics.Imaging.SoftwareBitmap input,
Windows.Graphics.Imaging.SoftwareBitmap output);
}
}
實作轉換
在 OpenCVHelper.cpp中,實作從 SoftwareBitmap 到 和 返回 Mat 的轉換:
#include "pch.h"
#include "OpenCVHelper.h"
#include "OpenCVHelper.g.cpp"
#include <opencv2/imgproc.hpp>
using namespace winrt;
using namespace Windows::Graphics::Imaging;
namespace winrt::OpenCVBridge::implementation
{
void OpenCVHelper::ProcessBitmap(
SoftwareBitmap const& input,
SoftwareBitmap const& output)
{
// Lock the input buffer for reading
auto inputBuffer = input.LockBuffer(
BitmapBufferAccessMode::Read);
auto inputRef = inputBuffer.CreateReference();
uint8_t* inputData = nullptr;
uint32_t inputSize = 0;
winrt::check_hresult(
inputRef.as<::Windows::Foundation::
IMemoryBufferByteAccess>()->GetBuffer(
&inputData, &inputSize));
auto inputDesc =
inputBuffer.GetPlaneDescription(0);
// Create a Mat from the input data (use Stride for correct row size)
cv::Mat inputMat(
inputDesc.Height,
inputDesc.Width,
CV_8UC4,
inputData,
inputDesc.Stride);
// Lock the output buffer for writing
auto outputBuffer = output.LockBuffer(
BitmapBufferAccessMode::Write);
auto outputRef = outputBuffer.CreateReference();
uint8_t* outputData = nullptr;
uint32_t outputSize = 0;
winrt::check_hresult(
outputRef.as<::Windows::Foundation::
IMemoryBufferByteAccess>()->GetBuffer(
&outputData, &outputSize));
auto outputDesc =
outputBuffer.GetPlaneDescription(0);
cv::Mat outputMat(
outputDesc.Height,
outputDesc.Width,
CV_8UC4,
outputData,
outputDesc.Stride);
// Apply image processing - example: blur
cv::GaussianBlur(inputMat, outputMat, cv::Size(15, 15), 5);
}
}
Note
上述程式碼是用 IMemoryBufferByteAccess 來存取原始像素資料的。 輸入與輸出 SoftwareBitmap 物件必須使用像素 Bgra8 格式。 如果來自 MediaFrameReader 的影格使用不同的格式,請先使用 SoftwareBitmap.Convert 進行轉換。
使用 C 語言的元件#
在你的 C# WinUI 3 應用程式中,為元件新增專案參考 OpenCVBridge 。 然後從你的幀處理程式碼中呼叫 ProcessBitmap :
using OpenCVBridge;
using Windows.Graphics.Imaging;
private readonly OpenCVHelper _openCVHelper = new();
private void ProcessFrameWithOpenCV(
SoftwareBitmap inputBitmap)
{
// Ensure the bitmap is in Bgra8 format
if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
{
inputBitmap = SoftwareBitmap.Convert(
inputBitmap, BitmapPixelFormat.Bgra8);
}
// Create an output bitmap with the same dimensions
var outputBitmap = new SoftwareBitmap(
BitmapPixelFormat.Bgra8,
inputBitmap.PixelWidth,
inputBitmap.PixelHeight,
BitmapAlphaMode.Premultiplied);
// Process with OpenCV
_openCVHelper.ProcessBitmap(inputBitmap, outputBitmap);
// Display the result
DispatcherQueue.TryEnqueue(async () =>
{
var source =
new Microsoft.UI.Xaml.Media.Imaging
.SoftwareBitmapSource();
await source.SetBitmapAsync(outputBitmap);
OutputImage.Source = source;
});
}
新增更多處理操作
你可以擴充 OpenCVHelper 類別,加入針對特定操作的額外方法。 更新 IDL 與實作:
// Add to OpenCVHelper.idl
void ApplyCannyEdges(
Windows.Graphics.Imaging.SoftwareBitmap input,
Windows.Graphics.Imaging.SoftwareBitmap output,
Double threshold1,
Double threshold2);
// Implementation
void OpenCVHelper::ApplyCannyEdges(
SoftwareBitmap const& input,
SoftwareBitmap const& output,
double threshold1,
double threshold2)
{
// ... lock buffers as above ...
cv::Mat grayMat;
cv::cvtColor(inputMat, grayMat, cv::COLOR_BGRA2GRAY);
cv::Mat edges;
cv::Canny(grayMat, edges, threshold1, threshold2);
cv::cvtColor(edges, outputMat, cv::COLOR_GRAY2BGRA);
}