Windows Community Toolkit 3.0 - CameraPreview

来源:https://www.cnblogs.com/shaomeng/archive/2018/07/15/9281089.html
-Advertisement-
Play Games

概述 Windows Community Toolkit 3.0 於 2018 年 6 月 2 日 Release,同時正式更名為 Windows Community Toolkit,原名為 UWP Community Toolkit。顧名思義,3.0 版本會更註重整個 Windows 平臺的工具實 ...


概述

Windows Community Toolkit 3.0 於 2018 年 6 月 2 日 Release,同時正式更名為 Windows Community Toolkit,原名為 UWP Community Toolkit。顧名思義,3.0 版本會更註重整個 Windows 平臺的工具實現,而不再只局限於 UWP 應用,這從 Release Note 也可以看出來:https://github.com/Microsoft/WindowsCommunityToolkit/releases

我們從今年 3 月份開始陸續針對 Windows Community Toolkit 2.2 版本的特性和代碼實現做了分析,從本篇開始,我們會對 3.0 版本做持續的分享,首先本篇帶來的關於 CameraPreview 相關的分享。

CameraPreview 控制項允許在 MediaPlayerElement 中簡單預覽攝像機幀源組的視頻,開發者可以在所選攝像機實時獲取 Video Frame 和 Bitmap,僅顯示支持彩色視頻預覽或視頻記錄流。

這是一個非常有用的控制項,之前在 Face++ 工作時,我們做的很多事情都是對攝像頭傳出的視頻幀做人臉檢測或關鍵點標註等操作。所以該控制項對攝像頭的控制,以及對視頻幀的傳出,就成了我們工作的資源源頭,我們對視頻幀做規範化,再進行演算法處理,再把處理後的視頻幀反饋到視頻播放控制項中,就可以完成檢測,人臉美顏處理等很多操作。

Windows Community Toolkit Doc - CameraPreview 

Windows Community Toolkit Source Code - CameraPreview

Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;

 

開發過程

代碼分析

首先來看 CameraPreview 的類結構:

  • CameraPreview.Cpmstants.cs - 定義了 CameraPreview 的兩個常量字元串;
  • CameraPreview.Events.cs - 定義了 CameraPreview 的事件處理 PreviewFailed;
  • CameraPreview.Properties.cs - 定義了 CameraPreview 的依賴屬性 IsFrameSourceGroupButtonVisible;
  • CameraPreview.cs - CameraPreview 的主要處理邏輯;
  • CameraPreview.xaml - CameraPreview 的樣式文件;
  • PreviewFailedEventArgs.cs - 定義了 CameraPreview 的事件處理 PreviewFailed 的參數;

接下來我們主要關註 CameraPreview.xaml 和 CameraPreview.cs 的代碼實現:

1. CameraPreview.xaml

CameraPreview 控制項的樣式文件組成很簡單,就是用戶播放預覽視頻幀的 MediaPlayerElement 和 FrameSourceGroup 按鈕。

<Style TargetType="local:CameraPreview" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:CameraPreview">
                <Grid Background="{TemplateBinding Background}">
                    <MediaPlayerElement x:Name="MediaPlayerElementControl" HorizontalAlignment="Left">
                    </MediaPlayerElement>
                    <Button x:Name="FrameSourceGroupButton" Background="{ThemeResource SystemBaseLowColor}" 
                            VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5">
                        <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE89E;" Foreground="{ThemeResource SystemAltHighColor}" />
                    </Button>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2. CameraPreview.cs

我們先來看一下 CameraPreview 的類組成:

整體的處理邏輯很清晰:

  1. 通過 OnApplyTemplate(), InitializeAsync(), SetUIControls(), SetMediaPlayerSource() 等方法初始化控制項,初始化攝像頭視頻源組,選擇視頻源賦值 MediaPleyerElement 做展示;
  2. 通過 StartAsync() 方法開始使用攝像頭視頻源,開發者用於展示和獲取每一幀圖像 Bitmap;
  3. 使用完成後,調用 Stop() 來結束並釋放攝像頭資源;

而 CameraPreview 類中出現了一個很重要的幫助類 CameraHelper,它的作用是對攝像頭資源的獲取和視頻幀的獲取/處理,它是 CameraPreview 中的核心部分,下麵我們來看 CameraHelper 的實現:

我們看到 CameraHelper 類中包括了獲取攝像頭視頻源組,初始化和開始獲取視頻幀,接收視頻幀進行處理,釋放資源等方法,我們來看幾個主要方法實現:

1. GetFrameSourceGroupsAsync()

獲取視頻源組的方法,使用 DeviceInformation 類獲取所有類別為 VideoCapture 的設備,再使用 MediaFrameSourceGroup 類獲取所有 mediaFrameSourceGroup,在 groups 中獲取彩色視頻預覽和視頻錄製的所有 group。

public static async Task<IReadOnlyList<MediaFrameSourceGroup>> GetFrameSourceGroupsAsync()
{
    if (_frameSourceGroups == null)
    {
        var videoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
        var groups = await MediaFrameSourceGroup.FindAllAsync();

        // Filter out color video preview and video record type sources and remove duplicates video devices.
        _frameSourceGroups = groups.Where(g => g.SourceInfos.Any(s => s.SourceKind == MediaFrameSourceKind.Color &&
                                                                    (s.MediaStreamType == MediaStreamType.VideoPreview || s.MediaStreamType == MediaStreamType.VideoRecord))
                                                                    && g.SourceInfos.All(sourceInfo => videoDevices.Any(vd => vd.Id == sourceInfo.DeviceInformation.Id))).ToList();
    }

    return _frameSourceGroups;
}

2. InitializeAndStartCaptureAsync()

使用 GetFrameSourceGroupsAsync() 和 InitializeMediaCaptureAsync() 對視頻源組和 MediaCapture 進行初始化;利用 MediaCapture 讀取選擇的視頻源組對應的預覽幀源,註冊 Reader_FrameArrived 事件,開始讀取操作,返回操作結果;

public async Task<CameraHelperResult> InitializeAndStartCaptureAsync()
{
    CameraHelperResult result;
    try
    {
        await semaphoreSlim.WaitAsync();
        ...
        result = await InitializeMediaCaptureAsync();

        if (_previewFrameSource != null)
        {
            _frameReader = await _mediaCapture.CreateFrameReaderAsync(_previewFrameSource);
            if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.Media.Capture.Frames.MediaFrameReader", "AcquisitionMode"))
            {
                _frameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Realtime;
            }

            _frameReader.FrameArrived += Reader_FrameArrived;

            if (_frameReader == null)
            {
                result = CameraHelperResult.CreateFrameReaderFailed;
            }
            else
            {
                MediaFrameReaderStartStatus statusResult = await _frameReader.StartAsync();
                if (statusResult != MediaFrameReaderStartStatus.Success)
                {
                    result = CameraHelperResult.StartFrameReaderFailed;
                }
            }
        }

        _initialized = result == CameraHelperResult.Success;
        return result;
    }
    ...
}

3. InitializeMediaCaptureAsync()

上面方法中使用的初始化 MediaCapture 的方法,首先獲取預覽幀源,獲取順序是彩色預覽 -> 視頻錄製;接著判斷它支持的格式,包括視頻幀率(>= 15 幀),媒體編碼格式的支持(Nv12,Bgra8,Yuy2,Rgb32),按照視頻寬高進行排序;對支持狀態進行判斷,如果狀態可用,則返回預設最高解析度;同時該方法會對許可權等進行判斷,對錯誤狀態返回對應狀態;只有狀態為 CameraHelperResult.Success 時才是正確狀態。

CameraHelperResult 中對應的錯誤狀態有:CreateFrameReaderFailed,StartFrameReaderFailed,NoFrameSourceGroupAvailable,NoFrameSourceAvailable,CameraAccessDenied,InitializationFailed_UnknownError,NoCompatibleFrameFormatAvailable。

private async Task<CameraHelperResult> InitializeMediaCaptureAsync()
{
    ...
    try
    {
        await _mediaCapture.InitializeAsync(settings);

        // Find the first video preview or record stream available
        _previewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoPreview
                                                                                && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
        if (_previewFrameSource == null)
        {
            _previewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord
                                                                                    && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
        }

        if (_previewFrameSource == null)
        {
            return CameraHelperResult.NoFrameSourceAvailable;
        }

        // get only formats of a certain framerate and compatible subtype for previewing, order them by resolution
        _frameFormatsAvailable = _previewFrameSource.SupportedFormats.Where(format =>
            format.FrameRate.Numerator / format.FrameRate.Denominator >= 15 // fps
            && (string.Compare(format.Subtype, MediaEncodingSubtypes.Nv12, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Bgra8, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Yuy2, true) == 0
                || string.Compare(format.Subtype, MediaEncodingSubtypes.Rgb32, true) == 0))?.OrderBy(format => format.VideoFormat.Width * format.VideoFormat.Height).ToList();

        if (_frameFormatsAvailable == null || !_frameFormatsAvailable.Any())
        {
            return CameraHelperResult.NoCompatibleFrameFormatAvailable;
        }

        // set the format with the higest resolution available by default
        var defaultFormat = _frameFormatsAvailable.Last();
        await _previewFrameSource.SetFormatAsync(defaultFormat);
    }
    catch (UnauthorizedAccessException)
    { ... }
    catch (Exception)
    { ... }

    return CameraHelperResult.Success;
}

4. Reader_FrameArrived(sender, args)

獲取到視頻幀的處理,觸發 FrameArrived 事件,傳入 VideoFrame,開發者可以對 frame 做自己的處理。

private void Reader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    // TryAcquireLatestFrame will return the latest frame that has not yet been acquired.
    // This can return null if there is no such frame, or if the reader is not in the
    // "Started" state. The latter can occur if a FrameArrived event was in flight
    // when the reader was stopped.
    var frame = sender.TryAcquireLatestFrame();
    if (frame != null)
    {
        var vmf = frame.VideoMediaFrame;
        EventHandler<FrameEventArgs> handler = FrameArrived;
        var frameArgs = new FrameEventArgs() { VideoFrame = vmf.GetVideoFrame() };
        handler?.Invoke(sender, frameArgs);
    }
}

 

調用示例

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"      
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
    mc:Ignorable="d">    

    <StackPanel Orientation="Vertical" Margin="20">
    <controls:CameraPreview x:Name="CameraPreviewControl"> 
        </controls:CameraPreview>
        <Image x:Name="CurrentFrameImage" MinWidth="300" Width="400" HorizontalAlignment="Left"></Image>
    </StackPanel>
</Page>
// Initialize the CameraPreview control and subscribe to the events
CameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed;
await CameraPreviewControl.StartAsync();
CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived;

// Create a software bitmap source and set it to the Xaml Image control source.
var softwareBitmapSource = new SoftwareBitmapSource();
CurrentFrameImage.Source = softwareBitmapSource;

private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e)
{
    var videoFrame = e.VideoFrame;
    var softwareBitmap = e.VideoFrame.SoftwareBitmap;
    var targetSoftwareBitmap = softwareBitmap;

    if (softwareBitmap != null)
    {
        if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
        {
            targetSoftwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
        }

        await softwareBitmapSource.SetBitmapAsync(targetSoftwareBitmap);
    }
}

 

總結

到這裡我們就把 Windows Community Toolkit 3.0 中的 CameraPreview 的源代碼實現過程講解完成了,希望能對大家更好的理解和使用這個擴展有所幫助。

相信大家在做到很多跟攝像頭有關的功能,比如人臉檢測,視頻直播的美顏處理,貼紙操作等操作時都會用到這個控制項。如果大家有好玩的應用場景,歡迎多多交流,謝謝!

最後,再跟大家安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490大家可以通過微博關註最新動態。

衷心感謝 WindowsCommunityToolkit 的作者們傑出的工作,感謝每一位貢獻者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上一篇文章,學習了併發編程中的volatile,最後取了網上流傳很廣的一張圖來結尾,從圖中可以看出除了volatile變數的讀寫,還有一個叫做CAS的東西,所以這篇文章再來學習CAS。 1、 併發編程三要素-原子性、可見性、有序性 在討論CAS前,我想先討論一下併發編程的三要素,這個應該可以幫助理解 ...
  • 什麼是RESTful? RESTful是一種開發理念,REST是Roy Thomas Fileding在他博文提出的.REST特點;url簡潔,將參數通過url傳遞到伺服器,簡單就是說URL定位資源,用HTTP動詞描述操作. RESTful架構: 每一個URL代表一種資源; 客戶端和伺服器之間,傳遞 ...
  • JRE(Java Runtime Environment Java運行環境) 包括Java虛擬機(JVM Java Virtual Machine)和Java程式所需的核心類庫等,如果想要運行一個開發好的Java程式,電腦中只需要安裝JRE即可。 JDK(Java Development Kit ...
  • 上兩篇文章我向大家介紹了一些線程間的基本通信方式,那麼這篇文章就和大家聊聊volatile關鍵字的相關知識。這個關鍵字在我們的日常開發中很少會使用到,而在JDK的Lock包和Concurrent包下的類則大量的使用了這個關鍵字,因為它有如下兩個特性: 1.確保記憶體可見性 2.禁止指令重排序 接下來就 ...
  • 2.矩陣專欄¶ 吐槽一下:矩陣本身不難,但是矩陣的寫作太蛋疼了 (⊙﹏⊙)汗 還好有Numpy,不然真的崩潰了... LaTex有沒有一個集成了很多常用公式以及推導或者含題庫的線上編輯器? 代碼褲子:https://github.com/lotapp/BaseCode 線上編程系:https://m ...
  • 寫在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html 作者:yilezhu 上一篇關於Asp.Net Core Web Api圖片上傳的文章使用的是mongoDB進行圖片的存儲,文章發佈後,張隊就來了一句,說沒有使用GridFS。的確博主只是 ...
  • 推薦加【QQ49300063】專業盜取微信密碼,破解微信密碼,查詢微信聊天記錄,不成功不收費!!!! 隨著信息時代的來臨,很多人使用上了微信,微信的出現使得人們的生活變的十便利。人們不僅在工作中使用它,在社交中也讓其發揮了重要的作用。微信現在已經漸漸成為了人們生活中不能缺少的一部分。使用微信除了其方 ...
  • 1、前言 分散式已經成為了當前最熱門的話題,分散式框架也百花齊放,群雄逐鹿。從中心化服務治理框架,到去中心化分散式服務框架,再到分散式微服務引擎,這都是通過技術不斷積累改進而形成的結果。esb,網關,nginx網關 這些中心化服務治理框架現在都是各個公司比較主流的架構,而最近幾年大家炒的比較火的去中 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...