前言:
摄像头的访问是芒果7.5的一大新功能。
通过摄像头的访问出现了一些新的应用,如应用程序QR,条形码扫描,增强摄像头功能和自定义摄像头UI等。
有很好资源来告诉我们如何使用新的API来访问芒果相机,但是我没有找到任何一个完整的QR码扫描程序,因此我写了这个博客。
正文:
芒果新增了2个新的关于Camera的API。你可以使用Silverlight 4中引入摄像头的API或者使用芒果提供的新的PhotoCamera类。我不是要覆盖这两个API的所有细节,而我将集中讨论如何实施QR扫描仪解决方案。如果您想了解更多有关Silverlight 4中的API访问摄像机,我推荐你看Rene Schulte’s detailed blog post的博客。关于PhotoCamera的API信息,我推荐你看(这个MSDN文章展示了如何建立一个为Windows Phone的摄像头应用程序)this MSDN article showing how to build a camera application for Windows Phone.
建设QR扫描仪的任务开始时我做的第一件事是研究不同的开源库,做图像识别。幸运的是有很多这样的开源库,但是从谷歌ZXing库似乎是最流行的一个。ZXing库有很多个版本,并且支持1D/2D码(QR,Code128,Code39,EAN等等)。其中有一个C#的借口库,被移植到Silverlight和Windows Phone 7上了。这样这个QR码扫描程序的任务就是合并ZXing库和芒果的Camera API了。
扫描器的XAML代码由4个主要部分组成。 一个矩形用来作为显示在取景器的视频流。 相机和矩形的链接方式是通过一个VideoBrush。你可以使用图像资源为VidoBrush设值,在任何的XAML元素上画画。这个转换可以让我们通过videobrush的旋转来协调摄像头的旋转。最好用一个ListBox显示QR码的解码。
1 <Grid x:Name="LayoutRoot" Background="Transparent"> 2 <Rectangle x:Name="_previewRect" Margin="0" Height="800"Width="600" HorizontalAlignment="Center" VerticalAlignment="Center"> 3 <Rectangle.Fill> 4 <VideoBrush x:Name="_previewVideo"> 5 <VideoBrush.RelativeTransform> 6 <CompositeTransform x:Name="_previewTransform" CenterX=".5"CenterY=".5" /> 7 </VideoBrush.RelativeTransform> 8 </VideoBrush> 9 </Rectangle.Fill> 10 </Rectangle> 11 <ListBox Margin="10" x:Name="_matchesList" FontSize="30" FontWeight="ExtraBold" /> 12 </Grid>
在代码隐藏文件中,我们需要添加一些初始化代码。 在构造函数中,我们实例化一个ObservableCollection它将绑定到ListBox的ItemsSource上.每个成功的QR扫描结果都会加到集合中,然后更新ListBox的items。我们还创建了一个DispatcherTimer每250毫秒执行一次。在每次执行tick时,我们对QR码图像进行解析。在OnNavgiatedTo方法我们实例PhotoCamera类,调用相机初始化事件。在创建XAML是把photocamera设置为videoBruch的source。这样就能把camera看到的景象画到矩形上。然后把ShutterKeyHalfPressed和foucue挂起来.
1 private readonly DispatcherTimer _timer; 2 private readonly ObservableCollection<string> _matches; 3 private PhotoCameraLuminanceSource _luminance; 4 private QRCodeReader _reader; 5 private PhotoCamera _photoCamera; 6 public MainPage() 7 { 8 InitializeComponent(); 9 _matches = new ObservableCollection<string>(); 10 _matchesList.ItemsSource = _matches; 11 _timer = new DispatcherTimer(); 12 _timer.Interval = TimeSpan.FromMilliseconds(250); 13 _timer.Tick += (o, arg) => ScanPreviewBuffer(); 14 } 15 16 protected override void OnNavigatedTo(NavigationEventArgs e) 17 { 18 _photoCamera = new PhotoCamera(); 19 _photoCamera.Initialized += OnPhotoCameraInitialized; 20 _previewVideo.SetSource(_photoCamera); 21 CameraButtons.ShutterKeyHalfPressed += (o, arg) => _photoCamera.Focus(); 22 base.OnNavigatedTo(e); 23 }
当photocamera初始化时,我们运行一些设置代码。这些代码是设置相机预览分辨率的宽和高,因次在相机初始化之前这些代码是不能运行的。下面我们创建一个PhotoCameraLumianceSource的实例。这是一个自定义的类,它扩展了ZXing库。这个类负责从图像中提取亮度信息。ZXing提供了一个基类的扩展,你通过代码得到特定格式的图片信息。接着我们创建一个QRCodeReader的实例。这个类用来解析QR码扫描的图片。ZXing库可以读取很多种码,因此如果你想读取Code128,只需创建一个Code128Reader的实例。最好我们设置预览VideoBrush的旋转并开启时钟。
1 private void OnPhotoCameraInitialized(object sender, CameraOperationCompletedEventArgs e) 2 { 3 int width = Convert.ToInt32(_photoCamera.PreviewResolution.Width); 4 int height = Convert.ToInt32(_photoCamera.PreviewResolution.Height); 5 _luminance = new PhotoCameraLuminanceSource(width, height); 6 _reader = new QRCodeReader(); 7 Dispatcher.BeginInvoke(() => 8 { 9 _previewTransform.Rotation = _photoCamera.Orientation; 10 _timer.Start(); 11 }); 12 }
每次执行tick事件都会调用ScanPreviewBuffer。这个方法负责获得照相机的图片信息,并对其进行解码。pjotocamera提供了很多方法获取图片。你可以通过捕获全屏的图片得到,也可以通过预览缓存得到。捕捉全分辨率的图像对计算机视觉应用是很慢的,但幸好我们有方法来访问预览缓冲区。你可以得到要么ARGB格式或YCbCr格式的预览缓冲区。相机传感器的内部使用的YCbCr,所以ARGB格式需要转换。如果你只关心亮度(在YCbCr Y)的有一个方便的方法,让你从YCbCr格式中得到Y分量。通过ZXing库实现是很容易的。Rene Schutle有一篇关于Argb vs YCbCr的详细博客
GetPreviewBufferY方法需要一个字节数组作为参数,这个自己数组可以用预览缓冲器的亮度数据来填充。我们可以从亮度源类传递到PerviewBufferY属性中(稍后提到)。 一旦我们捕捉到亮度数据,我们创建了一个HybridBinarizer和BinaryBitmap类。他们是的ZXing库的一部分。 我没有足够的精力与时间完全理解ZXing的架构, 但通过亮度数据的传递,然后传递给的QRCodeReader的解码方法的步骤我们是知道的。 如果解码成功解码的文本将被添加到ObservableCollection,这反过来将更新的ListBox。如果它不能够解码的图像, 解码方法将QRCodeReader抛出一个异常,所以我们需要的代码包装在一个try-catch块。
1 private void ScanPreviewBuffer() 2 { 3 try 4 { 5 _photoCamera.GetPreviewBufferY(_luminance.PreviewBufferY); 6 var binarizer = new HybridBinarizer(_luminance); 7 var binBitmap = new BinaryBitmap(binarizer); 8 var result = _reader.decode(binBitmap); 9 Dispatcher.BeginInvoke(() => DisplayResult(result.Text)); 10 } 11 catch 12 { 13 14 } 15 } 16 17 private void DisplayResult(string text) 18 { 19 if (!_matches.Contains(text)) 20 _matches.Add(text); 21 }
PhotoCameraLumianceSoure引用了ZXing库里的基类。这个类是负责您正在使用的图像格式的亮度数据方便的PhotoCamera类为我们提供了直接的亮度数据,所以实现类并不需要太多的代码。有一个属性和方法是我们要实现的。Matrix简单的返回一个完整的图片亮度数据。getRow方法返回一个给定的亮度数据。不管属性还是方法,都是每次时钟器调用GetPreviewBufferY方法返回的PreviewBufferY数组。
1 public class PhotoCameraLuminanceSource : LuminanceSource 2 { 3 public byte[] PreviewBufferY { get; private set; } 4 public PhotoCameraLuminanceSource(int width, int height) : base(width, height) 5 { 6 PreviewBufferY = new byte[width * height]; 7 } 8 9 public override sbyte[] Matrix 10 { 11 get { return (sbyte[])(Array)PreviewBufferY; } 12 } 13 14 public override sbyte[] getRow(int y, sbyte[] row) 15 { 16 if (row == null || row.Length < Width) 17 { 18 row = new sbyte[Width]; 19 } 20 for (int i = 0; i < Height; i++) 21 row[i] = (sbyte)PreviewBufferY[i * Width + y]; 22 return row; 23 } 24 }
那么,如何在实际工作?下面是一个YouTube视频,展示了QR扫描仪。正如你可以看到它是相当快速,准确的。我还没有在野外测试,和的ZXing库提供了许多定制扫描器行为的微调,但至少在这个演示如何可以结合ZXing和芒果PhotoCamera API实现在150行代码内扫描QR码。
终于翻译完了,对我这个英语不好的人,还真是不小的挑战。觉得翻译可以的给点掌声。
转载请注明出处。
原文:http://jonas.follesoe.no/2011/07/22/qr-code-scanning-on-windows-phone-75-using-zxlib/