如何在 Windows Phone 的相机应用程序中使用灰度

2012/2/9

使用 Windows Phone OS 7.1,您可以采用编程方式访问设备相机。本主题介绍如何从相机预览缓冲区改变实时视频帧。本主题中所述的应用程序介绍如何从相机处理 Alpha、红色、绿色和蓝色 (ARGB) 帧以及如何将它们转换为灰度。本主题与相机灰度示例相对应。若要下载完整的项目,请参阅 Windows Phone 的代码示例

提示提示:

如果您的应用程序只需要灰度帧并且不需要处理颜色,则考虑使用 GetPreviewBufferY(array<Byte>[]()[][]) 方法。该方法使用有效的 YCbCr 格式从相机预览缓冲区中仅捕获亮度 (Y) 信息。有关更多信息,请参阅 Windows Phone 的相机颜色转换(YCbCr 到 ARGB)

本主题分为两个部分:

重要说明重要说明:

当升级 Windows Phone OS 7.0 应用程序以使用 Windows Phone OS 7.1 中的功能时,相机功能 ID_CAP_ISV_CAMERA 不会自动添加到应用程序清单文件 WMAppManifest.xml 中。如果没有 ID_CAP_ISV_CAMERA,则使用相机 API 的应用程序将无法工作。在新的 Windows Phone OS 7.1 项目中,该功能包含在应用程序的清单文件中。

下图演示本主题中创建的相机应用程序。

AP_Con_CameraGrayscale
注意注意:

本主题基于 C# 开发;但也提供 Visual Basic 代码。

在本节中,您创建相机 UI,它包含一个取景器区域、一个用于在彩色和灰度模式之间切换按钮 StackPanel 控件,以及一个覆盖用于灰度视图的取景器区域的 Image 控件。

创建相机 UI 和基本功能的步骤:

  1. 在 Visual Studio 2010 Express for Windows Phone 中,通过选择“文件 | 新建项目”菜单命令创建一个新项目。

  2. 将显示“新建项目”窗口。展开“Visual C#”模板,然后选择“Silverlight for Windows Phone”模板。

  3. 选择“Windows Phone 应用程序”模板。用您选择的名称填写“名称”框。

  4. 单击“确定”。将显示“新建 Windows Phone 应用程序”窗口。

  5. “Windows Phone 目标版本”菜单中,确保已选择 Windows Phone 7.1。

  6. 单击“确定”。将创建一个新的项目,并且“MainPage.xaml”将在 Visual Studio 设计器窗口中打开。

  7. “项目”菜单中,选择“添加引用”。在 .NET 标签中,选择 Microsoft.XNA.Framework 并单击“确定”

  8. MainPage.xaml 上,更新 phone:PhoneApplicationPage 元素,如以下代码中所示。

        SupportedOrientations="Landscape" Orientation="LandscapeLeft"
        shell:SystemTray.IsVisible="False"
    
    

    这将配置横向的页面以及隐藏系统托盘。

  9. MainPage.xaml 中,将名为 LayoutRootGrid 替换为以下代码。

        <!--LayoutRoot is the root grid where all page content is placed-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="640" />
                <ColumnDefinition Width="160*" />
            </Grid.ColumnDefinitions>
    
            <!--Camera viewfinder >-->
            <Rectangle Width="640" Height="480" HorizontalAlignment="Left" >
                <Rectangle.Fill>
                    <VideoBrush x:Name="viewfinderBrush" />
                </Rectangle.Fill>
    
            </Rectangle>
    
            <!--Overlay for the viewfinder region to display grayscale WriteableBitmap objects-->
            <Image x:Name="MainImage" 
                   Width="320" Height="240" 
                   HorizontalAlignment="Left" VerticalAlignment="Bottom"  
                   Margin="16,0,0,16"
                   Stretch="Uniform"/>
    
            <!--Button StackPanel to the right of viewfinder>-->
            <StackPanel Grid.Column="1" >
                <Button             
                    Content="Gray: ON"
                    Name="GrayscaleOnButton"  
                    Click="GrayOn_Clicked" />
                <Button             
                    Content="Gray: OFF"
                    Name="GrayscaleOffButton"  
                    Click="GrayOff_Clicked" />
            </StackPanel>
    
            <!--Used for debugging >-->
            <TextBlock Height="40" HorizontalAlignment="Left" Margin="8,428,0,0" Name="txtDebug" VerticalAlignment="Top" Width="626" FontSize="24" FontWeight="ExtraBold" />
    
        </Grid>
    
    

    该代码创建一个 640 x 480 取景器区域,该区域具有一个包含灰度打开和关闭按钮的 StackPanel 控件。而且,Image 控件的作用是在选择灰度模式时覆盖取景器区域。这样便创建了由帧泵提供的灰度 WriteableBitmap 对象的另一个视图区域。

  10. 打开主页的代码隐藏文件 MainPage.xaml.cs,在该页面的顶部添加以下指令。

    // Directives
    using Microsoft.Devices;
    using System.Windows.Media.Imaging;
    using System.Threading;
    
    
  11. MainPage.xaml.csMainPage 类中,在 MainPage 类的构造函数上面添加以下变量声明。

        // Variables
        PhotoCamera cam = new PhotoCamera();
        private static ManualResetEvent pauseFramesEvent = new ManualResetEvent(true);
        private WriteableBitmap wb;
        private Thread ARGBFramesThread;
        private bool pumpARGBFrames;
    
    
  12. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

    注意注意:

    直到您完成此过程中的以下步骤,Visual Studio 才会列出有关当前上下文中不存在的方法的错误。将在以下步骤中添加这些方法。

            //Code for camera initialization event, and setting the source for the viewfinder
            protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
            {
    
                // Check to see if the camera is available on the device.
                if ((PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true) ||
                     (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing) == true))
                {
                    // Initialize the default camera.
                    cam = new Microsoft.Devices.PhotoCamera();
    
                    //Event is fired when the PhotoCamera object has been initialized
                    cam.Initialized += new EventHandler<Microsoft.Devices.CameraOperationCompletedEventArgs>(cam_Initialized);
    
                    //Set the VideoBrush source to the camera
                    viewfinderBrush.SetSource(cam);
                }
                else
                {
                    // The camera is not supported on the device.
                    this.Dispatcher.BeginInvoke(delegate()
                    {
                        // Write message.
                        txtDebug.Text = "A Camera is not available on this device.";
                    });
    
                    // Disable UI.
                    GrayscaleOnButton.IsEnabled = false;
                    GrayscaleOffButton.IsEnabled = false;
                }
            }
    
            protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
            {
                if (cam != null)
                {
                    // Dispose of the camera to minimize power consumption and to expedite shutdown.
                    cam.Dispose();
    
                    // Release memory, ensure garbage collection.
                    cam.Initialized -= cam_Initialized;
                }
            }
    
    

    该代码使用 OnNavigatedTo(NavigationEventArgs) 方法创建名为 camPhotoCamera 对象并添加一个事件处理程序。该代码还将 VideoBrush 源设置为手机相机对象 cam。如果设备上的某个相机不可用,则按钮将被禁用,而且会在 UI 中显示一条消息。

    注意注意:

    若要从相机中拉出视频帧,如稍后 GetPreviewBufferArgb32(array<Int32>[]()[][]) 方法所示,则 PhotoCamera 对象需要设置为向 VideoBrush 控件显示视频预览。

  13. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            // Dispose camera to minimize power consumption and to expedite shutdown.
            cam.Dispose();
    
            // Release memory, ensure garbage collection.
            cam.Initialized -= cam_Initialized;
        }
    
    

    该代码帮助释放与相机有关的内存。

  14. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

    //Update UI if initialization succeeds
            void cam_Initialized(object sender, Microsoft.Devices.CameraOperationCompletedEventArgs e)
            {        
                if (e.Succeeded)
                {
                    this.Dispatcher.BeginInvoke(delegate()
                    {
                        txtDebug.Text = "Camera initialized";
                    });
                   
                }
            }
    
    

    该代码使用相机 Initialized 事件更新名为 txtDebugTextBlock。需要使用 BeginInvoke 方法更新状态,因为应用程序 UI 在另一个线程上运行。

  15. 若要创建相机应用程序,必须在应用程序清单文件中声明相机功能。如果没有相机功能,该应用程序将无法工作。打开 WMAppManifest.xml 并确认存在以下功能元素。

    <Capability Name="ID_CAP_ISV_CAMERA"/>
    

在本节中,您需要创建两个方法,这两个方法共同负责从相机获取 ARGB(Alpha、红色、绿色、蓝色) 帧并将这些帧转换为灰度。

创建 ARGB 帧泵的步骤

  1. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

        // ARGB frame pump
        void PumpARGBFrames()
        {
            // Create capture buffer.
            int[] ARGBPx = new int[(int) cam.PreviewResolution.Width * (int) cam.PreviewResolution.Height];
    
            try
            {
                PhotoCamera phCam = (PhotoCamera)cam;
    
                while (pumpARGBFrames)
                {
                    pauseFramesEvent.WaitOne();
    
                    // Copies the current viewfinder frame into a buffer for further manipulation.
                    phCam.GetPreviewBufferArgb32(ARGBPx);
    
                    // Conversion to grayscale.
                    for (int i = 0; i < ARGBPx.Length; i++)
                    {
                        ARGBPx[i] = ColorToGray(ARGBPx[i]);
                    }
    
                    pauseFramesEvent.Reset();
                    Deployment.Current.Dispatcher.BeginInvoke(delegate()
                    {
                        // Copy to WriteableBitmap.
                        ARGBPx.CopyTo(wb.Pixels, 0);
                        wb.Invalidate();
    
                        pauseFramesEvent.Set();
                    });
                }
    
            }
            catch (Exception e)
            {
                this.Dispatcher.BeginInvoke(delegate()
                {
                    // Display error message.
                    txtDebug.Text = e.Message;
                });
            }
        }
    
        internal int ColorToGray(int color)
        {
            int gray = 0;
    
            int a = color >> 24;
            int r = (color & 0x00ff0000) >> 16;
            int g = (color & 0x0000ff00) >> 8;
            int b = (color & 0x000000ff);
    
            if ((r == g) && (g == b))
            {
                gray = color;
            }
            else
            {
                // Calculate for the illumination.
                // I =(int)(0.109375*R + 0.59375*G + 0.296875*B + 0.5)
                int i = (7 * r + 38 * g + 19 * b + 32) >> 6;
    
                gray = ((a & 0xFF) << 24) | ((i & 0xFF) << 16) | ((i & 0xFF) << 8) | (i & 0xFF);
            }
            return gray;
        }
    
    

    在该代码中,名为 PumpARGBFrames(ARGB 帧泵)的方法将 ARGB 帧复制到缓冲区中以便进行操作。然后,当每个帧都在缓冲区中时,使用 ColorToGray 方法将帧转换为灰度。最后,PumpARGBFrames 将转换的帧复制到名为 wbWriteableBitmap 对象。帧泵的连续处理用于产生在 UI 中显示的实时黑白视频。

    注意注意:

    若要从相机中拉出视频帧,如 GetPreviewBufferArgb32(array<Int32>[]()[][]) 方法所示,则 PhotoCamera 对象需要设置为向 VideoBrush 控件显示视频预览。

  2. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

        // Start ARGB to grayscale pump.
        private void GrayOn_Clicked(object sender, RoutedEventArgs e)
        {
            MainImage.Visibility = Visibility.Visible;
            pumpARGBFrames = true;
            ARGBFramesThread = new System.Threading.Thread(PumpARGBFrames);
    
            wb = new WriteableBitmap((int) cam.PreviewResolution.Width, (int) cam.PreviewResolution.Height);
            this.MainImage.Source = wb;
    
            // Start pump.
            ARGBFramesThread.Start();
            this.Dispatcher.BeginInvoke(delegate()
            {
                txtDebug.Text = "ARGB to Grayscale";
            });
        }
    
        // Stop ARGB to grayscale pump.
        private void GrayOff_Clicked(object sender, RoutedEventArgs e)
        {
            MainImage.Visibility = Visibility.Collapsed;
            pumpARGBFrames = false;
    
            this.Dispatcher.BeginInvoke(delegate()
            {
                txtDebug.Text = "";
            });
        }
    
    

    在该代码中,GrayOn_Clicked 方法调整名为 MainImageImage 控件的大小以覆盖屏幕并将它设置为显示名为 wb 的可写位图(由 ARGB 帧泵更新)。GrayOff_Clicked 事件折叠 MainImage 控件并停止帧泵。

    注意注意:

    请注意,在某些情况下,Image 控件呈折叠状态,不可见。而且,该控件覆盖所实现的标准彩色取景器。它是活动的并且可用于灰度视图。

  3. 在设备上,通过选择“调试 | 启动调试”菜单命令来运行应用程序。

显示: