通过 MediaFrameReader 使用开放源计算机视觉库 (OpenCV)

本文介绍了如何通过 MediaFrameReader 类(可同时读取多个源中的媒体帧)使用开放源计算机视觉库 (OpenCV),后者是一个本机代码库,可提供各类图像处理算法。 本文中的示例代码向你展示了一款简单的应用,该应用可通过颜色传感器获取帧,使用 OpenCV 库模糊每帧,然后在 XAML Image 控件中显示经过处理的图像。

注意

OpenCV.Win.Core 和 OpenCV.Win.ImgProc 没有定期更新,也没有通过 Microsoft Store 合规性检查,因此这些包仅用于实验。

本文基于其他两篇文章的内容:

  • 使用 MediaFrameReader 处理媒体帧 - 此文提供了有关使用 MediaFrameReader 获取一个或多个媒体帧源的详细信息,并详细介绍了大部分示例代码。 使用 MediaFrameReader 处理媒体帧中专门提供了帮助程序类 FrameRenderer 的代码列表,该代码可以处理 XAML Image 元素中媒体帧展示。 本文中的示例代码也使用此帮助程序类。

  • 使用 OpenCV 处理软件位图 - 此文向你展示了如何创建本机代码 Windows 运行时组件 OpenCVBridge,该组件有助于在 MediaFrameReader 使用的 SoftwareBitmap 对象与 OpenCV 库使用的 Mat 类型之间转换。 此文中的示例代码假定你已按步骤将 OpenCVBridge 组件添加到 UWP 应用解决方案中。

若要查看和下载本文中所述方案的完整、端到端工作示例,除了这些文章以外,请参阅 Windows 通用示例 GitHub 存储库中的相机帧 + OpenCV 示例

为了快速开始开发,你可以使用 NuGet 包将 OpenCV 库包含在 UWP 应用项目中,但这些包在你将应用提交到应用商店时可能无法通过应用认证过程,因此建议你下载 OpenCV 库源代码并在提交应用之前自己生成二进制文件。 使用 OpenCV 进行开发的信息可以在 https://opencv.org 中找到

实现 OpenCVHelper 本机 Windows 运行时组件

请按照使用 OpenCV 处理软件位图中的步骤创建 OpenCV 帮助程序 Windows 运行时组件并向你的 UWP 应用解决方案中添加组件项目引用。

查找可用的帧源组

首先,你需要从将要获取的媒体帧中查找媒体帧源组。 通过调用 MediaFrameSourceGroup.FindAllAsync 获取当前设备上的可用源组列表。 然后选择为你的应用方案提供所需传感器类型的源组。 对于此示例,我们只需选择一个可提供 RGB 相机帧的源组即可。

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
var selectedGroupObjects = frameSourceGroups.Select(group =>
   new
   {
       sourceGroup = group,
       colorSourceInfo = group.SourceInfos.FirstOrDefault((sourceInfo) =>
       {
           // On Xbox/Kinect, omit the MediaStreamType and EnclosureLocation tests
           return sourceInfo.SourceKind == MediaFrameSourceKind.Color;

       })

   }).Where(t => t.colorSourceInfo != null)
   .FirstOrDefault();

MediaFrameSourceGroup selectedGroup = selectedGroupObjects?.sourceGroup;
MediaFrameSourceInfo colorSourceInfo = selectedGroupObjects?.colorSourceInfo;

if (selectedGroup == null)
{
    return;
}

初始化 MediaCapture 对象

接着,你需要初始化 MediaCapture 对象以使用在上一步骤中选择的帧源组,方法是设置 MediaCaptureInitializationSettingsSourceGroup 属性。

注意

使用 OpenCV 处理软件位图中详述的 OpenCVHelper 组件采用的技术要求要处理的图像数据驻留在 CPU 内存中,而不是 GPU 内存中。 因此,应将 MediaCaptureInitializationSettingsMemoryPreference 字段指定为 MemoryPreference.CPU

初始化 MediaCapture 对象后,通过访问 MediaCapture.FrameSources 属性获取 RGB 帧源的引用。

mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

var colorFrameSource = mediaCapture.FrameSources[colorSourceInfo.Id];

初始化 MediaFrameReader

接着,为上一步中检索到的 RGB 帧源创建一个 MediaFrameReader。 为了维持良好的帧率,你可能需要处理分辨率低于传感器分辨率的帧。 本示例提供了 MediaCapture.CreateFrameReaderAsync 方法的可选 BitmapSize 参数,以请求将帧阅读器提供的帧调整为 640 x 480 像素。

创建帧阅读器后,为 FrameArrived 事件注册一个处理程序。 然后,创建一个新的 SoftwareBitmapSource 对象,FrameRenderer 帮助程序类可以使用该对象来展示处理的图像。 然后调用 FrameRenderer 的构造函数。 初始化 OpenCVBridge Windows 运行时组件中定义的 OpenCVHelper 类实例。 FrameArrived 处理程序将使用此帮助程序类来处理每个帧。 最后,通过调用 StartAsync 启动帧阅读器。

BitmapSize size = new BitmapSize() // Choose a lower resolution to make the image processing more performant
{
    Height = 480,
    Width = 640
};

mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32, size);
mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived_OpenCV;

imageElement.Source = new SoftwareBitmapSource();
_frameRenderer = new FrameRenderer(imageElement);

await mediaFrameReader.StartAsync();

处理 FrameArrived 事件

帧阅读器中提供新帧时将会引发 FrameArrived 事件。 调用 TryAcquireLatestFrame 以获取帧(如存在)。 从 MediaFrameReference 中获取 SoftwareBitmap。 请注意,本示例中使用的 CVHelper 类需要图像使用带预乘 alpha 的 BRGA8 像素格式。 如果传递至事件的帧具有不同的格式,请将 SoftwareBitmap 转换为正确的格式。 接着,创建一个 SoftwareBitmap,以用作模糊操作的目标。 源图像属性用作构造函数的参数,以创建具有匹配格式的位图。 调用帮助程序类模糊方法来处理帧。 最后,将模糊操作的输出图像传递至 PresentSoftwareBitmap,它是 FrameRenderer 帮助程序类的方法,用于在初始化使用的 XAML Image 控件中显示图像。

private void ColorFrameReader_FrameArrived_OpenCV(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{

    var mediaFrameReference = sender.TryAcquireLatestFrame();
    if (mediaFrameReference != null)
    {

        SoftwareBitmap openCVInputBitmap = null;
        var inputBitmap = mediaFrameReference.VideoMediaFrame?.SoftwareBitmap;
        if (inputBitmap != null)
        {
            //The XAML Image control can only display images in BRGA8 format with premultiplied or no alpha
            if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Bgra8
                && inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
            {
                openCVInputBitmap = SoftwareBitmap.Copy(inputBitmap);
            }
            else
            {
                openCVInputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
            }

            SoftwareBitmap openCVOutputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, openCVInputBitmap.PixelWidth, openCVInputBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);

            // operate on the image and render it
            openCVHelper.Blur(openCVInputBitmap, openCVOutputBitmap);
            _frameRenderer.PresentSoftwareBitmap(openCVOutputBitmap);

        }
    }
}