Статьи

31 Дней Манго | День № 7: необработанные данные с камеры

Эта статья является Днем № 7 в серии под названием 31 Дней Манго .

День7-RawCamera

Сегодня я собираюсь рассказать о камере, встроенной в каждый Windows Phone, и о том, как мы можем использовать ее в наших собственных приложениях. Это не обсуждение программ запуска и выбора , которые рассматривались в первоначальных 31 днях Windows Phone в дни 7 и 8. Эти действия позволяют вам предлагать пользователю сделать или выбрать фотографию на своем телефоне. Мы рассмотрим отображение необработанных данных на экране, захват изображения, использование аппаратных кнопок и сохранение фотографий на телефон вашего пользователя.

Если вы хотите загрузить образец приложения из этой статьи, он доступен на Windows Phone Marketplace.

DownloadIcon

Когда мы говорим о необработанных данных камеры, мы говорим об использовании прямой трансляции с камеры устройства непосредственно в нашем приложении. Я думаю, что лучший способ показать это — показать видео приложения, которое делает именно это:

Итак, теперь, когда вы увидели, куда мы движемся (и мы пойдем дальше), давайте начнем писать код!

Отображение канала камеры

Наш первый шаг в создании привлекательного приложения для камеры — получить изображение камеры на экране. Это удивительно легкий шаг. Мы собираемся создать элемент управления Rectangle на нашей странице XAML, а затем мы установим источник этого Rectangle в новый объект PhotoCamera в нашем файле C #. Вот наша страница 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> 

As you can see in the XAML above, we’ve added a Rectangle to the default page template, and defined its Fill property to be a VideoBrush named “CameraSource”.  You should also notice the RenderTransform I applied to the Rectangle.  By rotating it 90 degrees, we are actually accomodating the fact that the cameras are mounted in the phone with a Landscape orientation.  In our C# code-behind, we need to assign our camera data to that VideoBrush.  We do this by creating a PhotoCamera object, named “camera,” and setting the source of our VideoBrush to be that PhotoCamera object.

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();
            }
        }
    }
} 

Like I said earlier, this is the simple stuff, but there’s a couple of important details to look at here.  You might have noticed in the code above that when I created the new PhotoCamera object, I used the parameters CameraType.Primary and CameraType.FrontFacing.  This specifies that I want to use the camera that is found on the back or the front of the phone.  Newer phones that were released after the Mango launch have the option of a front-facing camera.  In this example, we’ll use the standard camera unless you have a front-facing camera available.

You’ll also noticed that I used the OnNavigatedTo and OnNavigatingFrom events.  Every time the user navigates to this page (within our app, or when navigating back from another app), we want to make sure that the camera is initialized.  We are using the OnNavigatingFrom method to then dispose of the camera when the user leaves the page.  This will save battery as well as memory on the device, and it’s the responsible way to work with the camera.

Take the time to run your application now.  If you deploy it to a real Windows Phone device, you should see what the camera sees, much like the video above.  If you are using the emulator, you probably see something that looks like this:

SNAGHTML26a7e692

The emulator does not leverage webcams or other video sources, and instead uses this white box as a way to suggest a “unique” image.  A small back rectangle rotates around the screen, and when we capture an image, a similar image is returned to us.  Let’s look at how that works:

Taking a Picture

The next step we want to take in our Camera application is to allow the user to be able to take a picture.  Let’s start by adding a button to our interface.  Below the Rectangle that we created earlier in our XAML code, add this line of Button code:

<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" /> 

I made it green because it will actually sit on top of our video feed, and if you’re using the emulator (which is 90% of you), you won’t be able to see it.  We also created a Click event handler which will trigger the phone to take a picture.

Open your code-behind file, and we have a few methods to write.  The first is the event handler method for our button.  It only needs one line of code, but because capturing an image is a expensive processing task, we want to make sure that each “capture” completes before the next one begins.  For our simple example, I have added a try/catch block to prevent these errors.

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

You can see that the primary line of code is the camera.CaptureImage() call.  Try just using that line by itself, and click the Capture button several times quickly.  Your app will crash just as fast.  Also notice that even our error handling needs to be thread-safe.  Anytime you are talking to a sensor on the device, you will want to make sure you are writing thread-conscious code.  I will show this again when we actually get the image back from the camera.

When we call the CaptureImage() method, all this does is tell the camera that we want to capture an image.  If we don’t create an event handler to receive it when it’s done, we’ll never get the result.  To do this, we will use the CaptureImageAvailable event on our PhotoCamera object.

So that you can copy and paste ALL of the code into your project, I am including my entire code-behind file below.  The important things to look for are the new event handler in our OnNavigatedTo method, the camera_CaptureImageAvailable event handler method (which passes its results to a separate thread), and the ThreadSafeImageCapture method which finally gets the result of our image capture.  Make sure to also notice that in the OnNavigatingFrom() method, we unhook our event handlers as well.  This will save on memory usage and battery life when our application is sitting in the background.

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;
        }
    }
} 

As I demonstrate in the ThreadSafeImageCapture method, we need to create a new BitmapImage object (which also requires the System.Windows.Media.Imaging statement at the top), and I set the source of that BitmapImage to the results of our image capture.

The remaining steps in that method actually replace the VideoBrush we were using to show our camera feed with an ImageBrush, showing the still image we just captured on the screen.

This is a good beginning to a camera application, but there’s much more to talk about, like using the native camera hardware button that’s on every Windows Phone, and then finally we’ll talk about saving these captured images to the same place the normal camera app saves them, the phone’s Camera Roll.

Using the Hardware Camera Button

In order to use the hardware camera button, there are three events that the button fires: ShutterKeyHalfPress, ShutterKeyPressed, and ShutterKeyReleased.  When building our own camera application, the button doesn’t have any default behaviors, so we need to implement them ourselves.  The user will likely already be familiar with these events, and we should implement them with the expected behavior:

  • ShutterKeyHalfPress – this action should focus the camera.
  • ShutterKeyPress – this action should take a picture with the camera.
  • ShutterKeyReleased – the event lets us know there’s nothing more to do, and also allows us to cancel any more focusing.

The first thing we need to do is implement each of those events.  The code sample below only shows my OnNavigatedTo event handler method, we will build each of the event handler methods for them afterwards.

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);
} 

The first event handler method we’ll create is for the ShutterKeyHalfPressed event, primarily because it’s the most involved of the three (though it’s still pretty simple.)  As you can see below, we have to check to make sure we have a PhotoCamera object, and then we try to Focus() the camera.  I’ve wrapped this in a try/catch block because we can’t call the Focus() method when the camera is capturing an image, but you’ll notice that we don’t do anything with the exception.  The Exception will fire every time you capture an image, because the button will pass through the “half-pressed” state twice on the way to a “pressed” state.  Once on the way down, and again on the way up to a “released” state.  If you’d like to write this error to a log file, go for it, but to limit the distractions in this example, I’ve left it empty.

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

For the ShutterKeyPressed event, I will use the same code I used earlier, when I created the button to capture an image for our app.

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

Finally, we have the ShutterKeyReleased event.  In this case, the only thing we really want to do with this is cancel any focusing that we tried to perform in the ShutterHalfKeyPressed event.  CancelFocus() doesn’t unfocus our image, it just stops the camera from focusing anymore.

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

The final step for using these events is to make sure that we remove our event handlers when we leave our page.  We do this the same was that we removed the CaptureImageAvailable event handler, using our OnNavigatingFrom event.  I’ve included the code for the entire method, but the only addition is the three CameraButtons event handler removal statements.

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;
    }
} 

At this point, you should have a working camera application that takes a picture using the hardware camera button, and shows it on-screen.  Our next step should be to save it, and that’s exactly what we’re going to do.

Saving Images to the Camera Roll

To make this super simple, Microsoft created a MediaLibrary class.  We’re actually going to build an entire app that takes advantage of the MediaLibrary class later in this series, but for now, we’re going to use it to save our captured images to the user’s “Camera Roll” on their device.  This will allow them to browse their images the same way they look at the other photos on their phone, and even sync them with their computers when they plug the phone in.

This entire process is actually only two lines of code, and the first line is just to get a reference to the MediaLibrary.  At the top of your page, in the same place that you created your PhotoCamera object, we will add this line of code below it:

MediaLibrary library =
new
MediaLibrary();

 

So that’s the first step.  The second step is to take a look in that ThreadSafeImageCapture method we created earlier.  The previous code was there to capture the image, change the Fill of our Rectangle, and replace it with the captured image.  We are going to clear out the contents of that method for this example, and instead replace it with this:

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

You should now be able to run this app on an actual phone, take a picture, and then find it in the Camera Roll of the Pictures Hub.  Here’s another quick video just to illustrate what I’m talking about.

So, in the spirit of brevity, that’s about it!  There are many more things you can do with this data, including capturing video, manipulating the captured image, and even changing the flash settings.  There is a great series of tutorials on the Code Samples for Windows Phone page on MSDN, and you can find those camera examples here.

Summary

In what is likely the longest article in this series, you learned how to show the user the raw camera data, how to create user interface elements to capture an image, how to make use of the dedicated hardware camera button on the phone, and finally how to save the images into the user’s Camera Roll.  This could probably have been several day’s worth of articles, but I didn’t want to short you.

If you’d like to download a complete working Windows Phone solution that uses all of the code in this article, click the Download Code button below.

скачать

Tomorrow, we are going to completely shift gears and look at some of the user data available on the phone.  More specifically, I’m going to walk you through how we can access the list of contacts a user has.  See you then!

toolsbutton

Source: http://www.jeffblankenburg.com/2011/11/07/31-days-of-mango-day-7-raw-camera-data/