Mango开发31日谈#7:原始摄像头数据

这篇文章是Mango开发31日谈系列文章的第七天。

今天我们将要谈谈Windows Phone手机当中的内置摄像头,以及我们如何在自己的程序中使用它。这并不是一篇对启动器(Launchers)和选择器(Choosers)讨论的文章,因为关于它们已经在之前Windows Phone开发31日谈#7&#8文章中讲过了。他们可以允许你提示用户在手机中去拍摄或选择一张照片。而这篇文章将会包含在屏幕中显示摄像头原始数据,捕获图片,使用硬件按钮,和保存图片到用户手机中。

本程序可以在微软Marketplace中下载到:

当我们谈论摄像头原始数据时,其实我们已经在谈论如何在程序中直接使用摄像头来获取实时画面。我想向你展示该过程的最好方法就是使用一个简短视频来帮你理解:

现在你应该明白我们到底要做什么了吧(不过我们所要完成的还不止这些),现在让我们开始代码的编写!

显示摄像头原始数据(Camera Feed)

要完成一个吸引人的摄像头程序时,我们首先要获取屏幕上的摄像头原始数据。这一步非常简单。我们将在XAML页面创建一个Rectangle 控件,然后在C#文件中将它的source设置为一个PhotoCamera对象。下面是我们完成XAML文件:

<phone:PhoneApplicationPage
   x:Class="Day7_RawCamera.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
   FontFamily="{StaticResource PhoneFontFamilyNormal}"
   FontSize="{StaticResource PhoneFontSizeNormal}"
   Foreground="{StaticResource PhoneForegroundBrush}"
   SupportedOrientations="Portrait" Orientation="Portrait"
   shell:SystemTray.IsVisible="True">
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="31 DAYS OF MANGO" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="raw camera" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Rectangle x:Name="ViewBox" Height="460" Margin="-22,-1,-131,148">
                <Rectangle.Fill>
                    <VideoBrush x:Name="CameraSource" />
                </Rectangle.Fill>
                <Rectangle.RenderTransform>
                    <RotateTransform Angle="90" CenterX="240" CenterY="240" />
                </Rectangle.RenderTransform>
            </Rectangle>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage> 

正如你所看到的,我们添加了一个Rectangle到默认页面模板中,然后定义了它的Fill属性为一个VideoBrush,名称为“CameraSource”。你应该会注意到我对Rectangle使用了RenderTransform属性。通过旋转90度,我们手机可以自适应横竖屏切换。在我们的C#后台代码中,我们需要将照相机数据赋值给VideoBrush。我是通过创建一个PhotoCamera对象来完成这些的,其名称为“camera”。然后设置VideoBrush 的源为PhotoCamera 对象。

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace Day7_RawCamera
{
    public partial class MainPage : PhoneApplicationPage
    {
        PhotoCamera camera;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
                camera = new PhotoCamera(CameraType.FrontFacing);
            else
                camera = new PhotoCamera(CameraType.Primary);
            CameraSource.SetSource(camera);
        }

        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (camera != null)
            {
                camera.Dispose();
            }
        }
    }
}
 

正如我之前所说的,这个示例非常简单,但是有两点比较重要的细节需要注意。你可以在上面的代码中注意到,当我创建一个新的PhotoCamera对象时,我使用了参数CameraType.Primary 和 CameraType.FrontFacing。这表明我想使用前置还是后置摄像头。最新的Mango版本的手机可能会有前置摄像头这个选项。在这个示例中,我将使用标准的摄像头,除非你有可用的前置摄像头。

你还会发现我使用了OnNavigatedTo 和OnNavigatingFrom事件,每一此用户导航到该界面(在程序内部,或从其他程序导航回来),我想确保摄像头被初始化。我使用OnNavigatingFrom方法来在离开该页面时对对其销毁。这将可以延长设备上的电池和内存寿命,而且对摄像头的正常工作工作也是必要的。

来看看这个应用程序。如果你将其部署在一个Windows Phone设备上,你会看到照相机所拍摄的一切,就像视频。如果你使用的是模拟器,你会看到如下图所示:

模拟器并不能利用网络摄像头或是其他视频源,所以只能显示一个白框来表示其是一张图片。一个黑色的小矩形在屏幕周围旋转,当我们捕获一张图片时,一张相似的图片会返回给我们。下面让我们看看他是如何工作的:

照相

下一步我想利用摄像头程序使得用户可以照一张相片。先让我们添加一个按钮到用户界面当中。在之前创建Rectangle下面添加一个按钮,其代码如下:

<Button Foreground="Green" BorderBrush="Green" Content="Capture" Height="72" HorizontalAlignment="Left" Margin="6,535,0,0" Name="CaptureButton" VerticalAlignment="Top" Width="160" Click="CaptureButton_Click" />

我将其设置为绿色,他将显示在视频源的上方,如果你使用的是模拟器(90%大小),你不会看到它。我们可以创建一个Click事件处理程序来触发手机拍照。

打开你的后台代码文件,我们需要写一些方法。首先是用于按钮的事件处理方法。他只需要一行代码就可以完成,因为捕获图片是一个过程,我们要确保每次捕获发生在下一次开始之前。对于这个示例,我添加了try/catch来防止出现异常。

private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
    try { camera.CaptureImage(); }
    catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
} 

你可以看到最主要的一行代码是camera.CaptureImage()的调用。只需要使用这一行代码,然后快速点击Capture按钮多次即可。你会发现出现异常提示,注意你的操作需要确保线程安全。每一次你调用设备的传感器时,你都需要确保你在编写一个线程清晰的代码。接着我将向你展示何时我们会获得来自照相机返回的图片。

CaptureImage()方法的调用是为了通知摄像头我们需要捕获一张图片。如果我们不创建一个用于接收它完成事件句柄,我们不会获得任何结果。要完成这些,我们需要通过PhotoCamera 对象调用CaptureImageAvailable 。

你可以拷贝粘贴这里所有的代码到你的项目当中。在OnNavigatedTo方法中我们创建了 camera_CaptureImageAvailable事件句柄 (它将其结果传送至一个独立线程),其指定的ThreadSafeImageCapture 方法用于获取到捕获到的图片结果。注意,在OnNavigatingFrom()方法中我们也对该事件处理进行了注销。这能延长你手机内存使用和电池的寿命。

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace Day7_RawCamera
{
    public partial class MainPage : PhoneApplicationPage
    {
        PhotoCamera camera;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
                camera = new PhotoCamera(CameraType.FrontFacing);
            else
                camera = new PhotoCamera(CameraType.Primary);
            camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
            CameraSource.SetSource(camera);
        }

        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (camera != null)
            {
                camera.Dispose();
                camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
            }
        }

        private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            try { camera.CaptureImage(); }
            catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
        }

        void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
        {
            Dispatcher.BeginInvoke(() => ThreadSafeImageCapture(e));
        }

        void ThreadSafeImageCapture(ContentReadyEventArgs e)
        {
            BitmapImage image = new BitmapImage();
            image.SetSource(e.ImageStream);
            ImageBrush still = new ImageBrush();
            still.ImageSource = image;
            ViewBox.Fill = still;
        }
    }
}
 

在ThreadSafeImageCapture方法中,我们需要创建一个新的BitmapImage 对象(它需要使用System.Windows.Media.Imaging声明),我将BitmapImage 的source设置为所捕获的图片。

接下来一步是用来将我们捕获的图片显示在屏幕中。

这是一个有关摄像头程序的很好开始,然而我们仍有很多问题需要解决,我们还要讨论如何将所捕获的图片存入手机相册中。

使用摄像头硬件按钮

为了使用摄像头硬件按钮,有三个事件需要触发:ShutterKeyHalfPress, ShutterKeyPressed, 和 ShutterKeyReleased。当我们建立自己的摄像头程序时,该按钮不会有其默认的行为,因此我们需要自己去实现它。

  • ShutterKeyHalfPress – 将摄像头作为焦点
  • ShutterKeyPress –拍照
  • ShutterKeyReleased – 取消摄像头焦点。

首先我们需要实现每一个事件,他们的事件句柄在OnNavigatedTo 方法中进行了定义,我们要在之后建立对每一个事件处理方法。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
        camera = new PhotoCamera(CameraType.FrontFacing);
    else
        camera = new PhotoCamera(CameraType.Primary);
    camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
    CameraSource.SetSource(camera);

    CameraButtons.ShutterKeyHalfPressed += new EventHandler(CameraButtons_ShutterKeyHalfPressed);
    CameraButtons.ShutterKeyPressed += new EventHandler(CameraButtons_ShutterKeyPressed);
    CameraButtons.ShutterKeyReleased += new EventHandler(CameraButtons_ShutterKeyReleased);
} 

我们先为ShutterKeyHalfPressed事件创建一个事件处理方法,这主要是因为它在三个事件中最为重要(尽管很简单)。接下来,我们需要检测是否存在PhotoCamera 对象,然后通过它来调用focus方法,此处我采用了try/catch方法进行处理,但并没有对捕获的异常进行处理,在每次捕获图片时,都会引发该异常,因为按钮在按下的过程中会有两次处于“一半按下”的状态。你可以将此记录在日志中,我在这里并没有对其进行处理。

void CameraButtons_ShutterKeyHalfPressed(object sender, EventArgs e) {     if (camera != null)     {         try { camera.Focus(); }         catch (Exception ex) { }     } }  

对于ShutterKeyPressed事件,当我创建一个按钮用来捕获图片时,我将使用和之前相同的代码。

void CameraButtons_ShutterKeyPressed(object sender, EventArgs e)
{
    if (camera != null)
    {
        try { camera.CaptureImage(); }
        catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
    }
} 

最后,我们还有ShutterKeyReleased 事件。在这里我们只想注销之前ShutterHalfKeyPressed 事件。CancelFocus并不会取消图片焦点,而是停止摄像头工作。

void CameraButtons_ShutterKeyReleased(object sender, EventArgs e)
{
    if (camera != null)
        camera.CancelFocus();
}
 

最后一步是通过事件来确保当我们离开页面时,我们之前创建的事件全部被注销。

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
    if (camera != null)
    {
        camera.Dispose();
        camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
        CameraButtons.ShutterKeyHalfPressed -= CameraButtons_ShutterKeyHalfPressed;
        CameraButtons.ShutterKeyPressed -= CameraButtons_ShutterKeyPressed;
        CameraButtons.ShutterKeyReleased -= CameraButtons_ShutterKeyReleased;
    }
} 

此时,你应该可以在你程序中使用摄像头硬件按钮了,图像会显示在屏幕中。下一步,我们将对其进行保存。

保存图片到相册

微软为我们提供了MediaLibrary 类可以很好的帮助我们完成此操作。通过该类你可以在相册中找到之前所保存的图片,并且可以同步到电脑上。

这整个过程的实现其实只有两行代码,首先在页面顶部创建PhotoCamera 对象的下方实例化MediaLibrary。

MediaLibrary library = new MediaLibrary(); 

然后将之前的ThreadSafeImageCapture 方法替换成如下代码:

void ThreadSafeImageCapture(ContentReadyEventArgs e)
{
    library.SavePictureToCameraRoll(DateTime.Now.ToString() + ".jpg", e.ImageStream);}
 

现在你的程序应该可以在手机上运行了,你可以拍一张照片然后看它是否存在相册中。下面是我的一个简短视频。

这个程序只是一个简短的示例,其实你可以利用所得的数据进行其他许多操作,比如拍摄视频,对图片进行处理,甚至改变闪光设置,在MSDN上有个一个代码示例系列教程,你可以找到有关摄像头使用的示例。

总结

这篇文章貌似有点长,不过在这里你学习到了如果向用户展示原始照片数据,如何创建一个用户界面来捕获图片,如何使用手机中的照相机硬件按钮,最后是如何保存图片到相册。希望你能理解它。

下载完整代码示例可以点击下面图标。

明天我们将有一次飞跃,我会带领你去看看手机中的可用的用户数据,教你如何访问联系人列表。明天见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值