65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Silverlight 从网络摄像头捕获图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.19/5 (9投票s)

2012年10月10日

CPOL

5分钟阅读

viewsIcon

58306

downloadIcon

4587

从实时网络摄像头流捕获一帧并将其保存为本地图像文件

介绍 

Silverlight 带来了一整套新的控件和库,用于开发使用视频和音频等媒体内容的富互联网应用程序。在本文中,我将探讨 Silverlight 访问网络摄像头的能力,以及随后使用摄像头源冻结一帧并将其保存为 PNG 文件到磁盘。

此处提供的代码可以封装成一个不错的用户控件,并在任何 Silverlight 应用程序中使用,以捕获图像,例如,快速的个人资料图片、有趣的图片等,发挥你的想象力 微笑,尽管我在这篇文章中不讨论这部分。

背景 

本文解释了 Silverlight 访问网络摄像头的基本知识:Silverlight 网络摄像头访问

源代码 

我已上传 Silverlight 项目,可以直接添加到 Visual Studio 中。

前端 

前端的 XAML 标记如下所示

<UserControl x:Class="SilverlightApplication1.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="800" d:DesignWidth="900">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Activate Camera" Height="23" HorizontalAlignment="Left" 
            Margin="156,20,0,0" Name="btnActivate" 
            VerticalAlignment="Top" Width="100" Click="btnActivate_Click" />
        <Rectangle Height="239" HorizontalAlignment="Left" 
            Margin="12,49,0,0" Name="rectangle1" Stroke="Black" 
            StrokeThickness="1" VerticalAlignment="Top" Width="376" />
        <Rectangle Height="239" HorizontalAlignment="Left" Margin="409,49,0,0" 
            Name="rectangle2" Stroke="Black" StrokeThickness="1" 
            VerticalAlignment="Top" Width="376" />
        <Button Content="Freeze Frame" Height="23" HorizontalAlignment="Left" 
            Margin="559,20,0,0" Name="btnFreeze" VerticalAlignment="Top" 
            Width="100" Click="btnFreeze_Click" />
        <Image Height="183" HorizontalAlignment="Left" Margin="409,305,0,0" 
            Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="376" />
        <Button Name="btnSave" Content="Save Shot" Click="btnSave_Click" 
            Height="23" HorizontalAlignment="Left" Width="85" Margin="500,200,0,0"></Button>
    </Grid>
</UserControl>

GUI 的外观是这样的,相当简单。左边是网络摄像头的实时画面,右边是捕获的一帧。 (在捕获帧并截屏后,我的手稍微移动了一下,所以我的手在左边比在右边显得更低)。

点击“激活摄像头”按钮将在左侧框中开始实时视频预览,点击“冻结帧”按钮将冻结实时视频中的一帧并在右侧框中显示。点击“保存快照”将冻结的帧保存为 PNG 图像到磁盘。就是这样。

后面的 C# 代码 

进行捕获和保存的 C# 代码将在以下几个阶段进行讨论

  1. 获取网络摄像头访问权限
  2. 正如您在下面看到的,第一步是获取视频捕获设备,并将其源流式传输到预先预览的 Rectangle,即 rectangle1。这是通过用 VideoBrush 绘制或“填充” rectangle1 来实现的,其内容源已设置为视频捕获设备。 

    同时请注意,rectangle2 已被填充了图像笔刷,其原因稍后将讨论。

    完成之后,RequestDeviceAccess() 方法会请求用户权限来访问网络摄像头,如果获得批准,则会启动网络摄像头,并且实时流会出现在 rectangle1 中。

    private void btnActivate_Click(object sender, RoutedEventArgs e)
    {
        source = new CaptureSource();
        VideoCaptureDevice vcd = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
        source.VideoCaptureDevice = vcd;
        VideoBrush vb = new VideoBrush();
        ib = new ImageBrush();
        vb.SetSource(source);
        rectangle1.Fill = vb;
        rectangle2.Fill = ib;
    
        if (CaptureDeviceConfiguration.RequestDeviceAccess())
        {
            source.Start();
        }
    }
  3. 冻结帧
  4. 像这样将 CaptureImageCompleted 事件处理程序附加到“冻结帧”按钮的点击事件: 

    private void btnFreeze_Click(object sender, RoutedEventArgs e)
    {
        source.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(source_CaptureImageCompleted);
        source.CaptureImageAsync();
    }

    并调用 CaptureImageAsync() 方法,这是一个异步方法,它会触发并继续执行其余代码,而无需阻塞和等待响应。当方法返回时,事件处理程序会被触发,并且结果会像这样收集: 

    void source_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
    {
        ib.ImageSource = e.Result;
        CapturedImage = e.Result;
    }

    如果您还记得,刚才我们用图像笔刷“ib”填充了 rectangle2,当时它是空的,即没有源来绘制图像。现在它有了。如上所示,我将图像笔刷“ib”的 ImageSource 设置为 CaptureImageCompleted 事件的结果。这导致从实时视频流中冻结一帧,并出现在 rectangle2 中,如先前的 GUI 屏幕截图所示。 

    另外请注意,我还设置了另一个属性 CapturedImage,其结果相同。它有什么作用?嗯,CapturedImage 定义如下:  

    private WriteableBitmap _capimage;
    public WriteableBitmap CapturedImage
    {
        get 
        {              
            return _capimage; 
        }
        set { _capimage = value; }
    }

    它是一个 WriteableBitmap 对象,有助于将帧保存为实际图像,这也是我在倒数第二个阶段要讲到的。 

  5. 将帧保存为 PNG
  6. 现在,您需要一些第三方开源库的支持。 ImageTools 就是为此目的而设计的库。它为几种图像格式提供了编码器/解码器,PNG 是其中之一。

    您需要在 Silverlight 项目中引用以下 DLL

    1. ImageTools.dll
    2. ImageTools.IO.PNG.dll
    3. ImageTools.Utils.dll

    如果尚未包含,也请包含以下命名空间

    using System.Windows.Media;
    using System.Windows.Media.Effects;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;

    ImageTools 还提供了一个扩展方法:ToImage(WriteableBitmap wmp),它将 WriteableBitmap 对象转换为 ExtendedImage 对象(ImageTools 库的一部分)。 

    像这样在点击“保存快照”按钮时将此帧保存为 PNG

    private void btnSave_Click(object sender, RoutedEventArgs e)
    {
        SaveFileDialog sfd = new SaveFileDialog();
        sfd.Filter = "PNG files (*.PNG)|*.png|All Files (*.*)|*.*";
        var enc = new PngEncoder();
        if ((bool)sfd.ShowDialog())
        {
            Stream stream = sfd.OpenFile();
            var image = Imager.ToImg(CapturedImage);
            enc.Encode(image, stream);
            stream.Close();
        }
    }

    [注]:由于未知原因,我无法直接访问 ToImage() 扩展方法,因此我使用 IL-Spy 反编译了 ImageTools DLL,并将 ToImage() 方法复制到我 Silverlight 项目中的一个单独类中,并将其命名为 ToImg()。我只是“原样”使用了原始方法,并没有以任何方式修改它,其原始作者是构建 ImageTools 库的人,而不是我。仅供您参考 微笑

    总之,回到正题,如上所示,我打开了一个 SaveFileDialog 来创建一个空的 PNG 文件,将其打开到一个 Stream 中,将 CapturedImage(即 WriteableBitmap)转换为 ExtendedImage 类型,然后调用 ImageTools.IO.PNG.dllPngEncoder 类定义的 Encode() 方法将图像编码为 PNG 格式并写回流。由于流直接连接到实际文件,因此在流关闭后,编码数据将立即写入文件,瞧!您已成功在 Silverlight Web 应用程序中从网络摄像头捕获图像并将其保存为 PNG 文件。

关注点   

说实话,我尝试了一些奇怪的技巧来编写自己的 PngEncoder(这在我甚至听说 ImageTools 之前很久,并且“自己动手”的愿望达到了有毒的程度 微笑),但我真的没有取得多大进展。最后,我决定咬紧牙关,选择第三方库的方式。但通过研究这个库,可以开始了解各种图像格式的工作原理以及如何着手编写它们的编码器。 

此代码的下一步可能是将其包装成一个不错的 Silverlight 用户控件,可以即插即用地集成到任何应用程序中(尽管这还有待观察)。 

如果对于控件来说过于复杂,它甚至可以作为一个独立应用程序保留,然后嵌入到另一个 Silverlight 应用程序中,并从宿主应用程序动态加载。我在这里的博客中讨论了如何做到这一点:这里

至于此类控件的用例,我相信人们可以发挥他们的想象力找到使用它的方法。我在本文开头已经提到了几个我能想到的例子,但它们绝不是全部。

感谢您认为本文值得阅读。

© . All rights reserved.