二維碼掃描的實現,簡單的來說可以分三步走:“成像”、“截圖”與“識別”。 UWP開發中,最常用的媒體工具非MediaCapture莫屬了,下麵就來簡單介紹一下如何利用MediaCapture來實現掃描和截圖並且利用Zxing識別二維碼,以及會遇到的問題和需要註意的地方。 1. 初始化與成像 1 pr ...
二維碼掃描的實現,簡單的來說可以分三步走:“成像”、“截圖”與“識別”。
UWP開發中,最常用的媒體工具非MediaCapture莫屬了,下麵就來簡單介紹一下如何利用MediaCapture來實現掃描和截圖並且利用Zxing識別二維碼,以及會遇到的問題和需要註意的地方。
1. 初始化與成像
1 private async void InitMediaCaptureAsync() 2 { 3 //尋找後置攝像頭 4 var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); 5 var cameraDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back); 6 7 if (cameraDevice == null) 8 { 9 Debug.WriteLine("No camera device found!"); 10 11 return; 12 } 13 14 var settings = new MediaCaptureInitializationSettings 15 { 16 StreamingCaptureMode = StreamingCaptureMode.Video, 17 //必須,否則截圖的時候會很卡很慢 18 PhotoCaptureSource = PhotoCaptureSource.VideoPreview, 19 VideoDeviceId = cameraDevice.Id 20 }; 21 22 _mediaCapture = new MediaCapture(); 23 24 try 25 { 26 await _mediaCapture.InitializeAsync(settings); 27 _initialized = true;//初始化成功 28 } 29 catch (UnauthorizedAccessException) 30 { 31 Debug.WriteLine("The app was denied access to the camera"); 32 } 33 catch (Exception ex) 34 { 35 Debug.WriteLine("Exception when initializing MediaCapture with {0}: {1}", cameraDevice.Id, ex.ToString()); 36 } 37 38 if (_initialized) 39 { 40 var focusControl = _mediaCapture.VideoDeviceController.FocusControl; 41 42 if (focusControl.Supported) 43 { 44 var focusSettings = new FocusSettings() 45 { 46 Mode = focusControl.SupportedFocusModes.FirstOrDefault(f => f == FocusMode.Continuous), 47 DisableDriverFallback = true, 48 AutoFocusRange = focusControl.SupportedFocusRanges.FirstOrDefault(f => f == AutoFocusRange.FullRange), 49 Distance = focusControl.SupportedFocusDistances.FirstOrDefault(f => f == ManualFocusDistance.Nearest) 50 }; 51 52 //設置聚焦,最好使用FocusMode.Continuous,否則影響截圖會很模糊,不利於識別 53 focusControl.Configure(focusSettings); 54 } 55 56 captureElement.Source = _mediaCapture; 57 captureElement.FlowDirection = FlowDirection.LeftToRight; 58 59 try 60 { 61 await _mediaCapture.StartPreviewAsync(); 62 _previewing = true; 63 } 64 catch (Exception ex) 65 { 66 Debug.WriteLine("Exception when starting the preview: {0}", ex.ToString()); 67 } 68 69 if (_previewing) 70 { 71 try 72 { 73 if (_mediaCapture.VideoDeviceController.FlashControl.Supported) 74 { 75 //關閉閃光燈 76 _mediaCapture.VideoDeviceController.FlashControl.Enabled = false; 77 } 78 } 79 catch 80 { 81 } 82 83 if (focusControl.Supported) 84 { 85 //開始聚焦 86 await focusControl.FocusAsync(); 87 } 88 } 89 } 90 }View Code
2. 截圖與識別
1 private void InitTimer() 2 { 3 _timer = new DispatcherTimer(); 4 //每50毫秒截一次圖 5 _timer.Interval = TimeSpan.FromMilliseconds(50); 6 _timer.Tick += _timer_Tick; 7 _timer.Start(); 8 }View Code
1 private async void _timer_Tick(object sender, object e) 2 { 3 using (var stream = new InMemoryRandomAccessStream()) 4 { 5 var previewProperties = _mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties; 6 //將截圖寫入記憶體流中 7 await _mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), stream); 8 9 //利用Zxing識別,成功:停止timer;失敗:繼續 10 var reader = new BarcodeReader(); 11 var bitmapWriteable = new WriteableBitmap((int)previewProperties.Width, (int)previewProperties.Height); 12 bitmapWriteable.SetSource(stream); 13 var result = reader.Decode(bitmapWriteable); 14 15 if (!string.IsNullOrEmpty(result.Text)) 16 { 17 _timer.Stop(); 18 } 19 } 20 }View Code
這裡順便說一下如何安裝Zxing,打開nuget管理器 命令視窗輸入 Install-Package ZXing.Net ,回車; 關於Zxing如何使用,到網上搜索一下有很多教程,這裡不再贅述
3. 問題與優化
A) 截圖有響聲
使用CapturePhotoToStreamAsync來截取圖片有的時候會有“咔擦咔擦”聲,很影響用戶體驗,最理想的做法是找到一種能從視頻流中直接截取圖片的方法,在這裡不得不說一句MediaCapture真的真的很強大,MediaCapture給我們提供了直接從視頻流中取出其中一幀的方法GetPreviewFrameAsync,於是我把代碼進行瞭如下修改,即流暢又沒有煩人的“咔擦咔擦”聲
1 private async void _timer_Tick(object sender, object e) 2 { 3 var previewProperties = _mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties; 4 5 using (var videoFrame = new VideoFrame(BitmapPixelFormat.Rgba8, (int)previewProperties.Width, (int)previewProperties.Height)) 6 { 7 using (var currentFrame = await _mediaCapture.GetPreviewFrameAsync(videoFrame)) 8 { 9 using (var previewFrame = currentFrame.SoftwareBitmap) 10 { 11 var buffer = new Windows.Storage.Streams.Buffer((uint)(4 * previewFrame.PixelWidth * previewFrame.PixelHeight)); 12 previewFrame.CopyToBuffer(buffer); 13 14 using (var stream = buffer.AsStream().AsRandomAccessStream()) 15 { 16 //利用Zxing識別,成功:停止timer;失敗:繼續 17 var reader = new BarcodeReader(); 18 var bitmapWriteable = new WriteableBitmap((int)previewProperties.Width, (int)previewProperties.Height); 19 bitmapWriteable.SetSource(stream); 20 var result = reader.Decode(bitmapWriteable); 21 22 if (!string.IsNullOrEmpty(result.Text)) 23 { 24 _timer.Stop(); 25 } 26 } 27 } 28 } 29 } 30 }View Code
順便提一下記得要使用如下兩個命名空間
using System.IO; using System.Runtime.InteropServices.WindowsRuntime;
否則無法實現buffer.AsStream().AsRandomAccessStream()
B) 連續聚焦
並不是所有機型都支持連續聚焦的(FocusMode.Continuous),這個時候只能自己實現間斷性持續聚焦了
C) 截圖之後圖片處理
有的時候為了實現一些功能(比如說掃描框)或者提高識別率,我們需要對截取出來的圖片進行一些二次處理,或剪裁或縮放或旋轉,我們可以使用BitmapDecoder和BitmapEncoder來實現
using (var stream = buffer.AsStream().AsRandomAccessStream()) { var decoder = await BitmapDecoder.CreateAsync(BitmapDecoder.JpegDecoderId, stream); var destStream = new InMemoryRandomAccessStream(); var encoder = await BitmapEncoder.CreateForTranscodingAsync(destStream, decoder); //剪裁 encoder.BitmapTransform.Bounds = new BitmapBounds() { X = 0, Y = 0, Width = 100, Height = 100 }; //縮放 encoder.BitmapTransform.ScaledWidth = 100; encoder.BitmapTransform.ScaledHeight = 100; //旋轉 encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise90Degrees; await encoder.FlushAsync(); await destStream.FlushAsync();
}
D) 掛起和喚醒
另外值得註意的是,程式在Suspending和Resuming還有Activated時出現的一系列狀態轉換,這時候很容易引起bug,需要處理好避免crash。
4. 最後
識別出來的字元串處理一般也就超鏈接和普通文本兩種,當然也可以增加條碼掃描功能,識別出的是編碼,不管怎樣,大家可以根據項目具體需求做相應的處理。