關於iOS原生條形碼掃描,你需要注意的兩三事


轉自 txx's blog
 
前言
這篇文章是我們在新發布的 禮物說的iOS端開發過程中遇到的一些關於條形碼的問題總結而來。
 
本文記錄的問題是:當AVFoundation使用多譯碼器掃描的時候。二維碼是秒殺,但是條形碼卻經常掃不上。如果去掉二維碼的話,條形碼掃描又秒殺的問題。
 
為什么我們沒有選用ZXing而是用AVfoundation呢,是因為我說服了老板,iOS7開發,而不再去兼容iOS5/6。所以我們終於可以拋棄效率低下的ZXing,而選擇AVFoundation。為什么說ZXing效率低下,我們這里可以說上幾句。

ZXing
ZXing 是 Google Code上的一個開源的條形碼掃描庫,是用java設計的,連Google Glass 都在使用的。但有人為了追求更高效率以及可移植性,出現了c++ port. Github上的Objectivc-C port,其實就是用OC代碼封裝了一下而已,而且已經停止維護。
 
ZXing掃描,是拿到攝像頭的每一幀,然后對其根據如下公式做灰度化
  1. f(i,j)=0.30R(i,j)+0.59G(i,j)+0.11B(i,j))  
之后做全局直方圖二值化的方法,最后按照  ISO/IEC 18004 規范進行解析。
 
這樣效率非常低,在instrument下面可以看到CPU占用遠遠高於 AVFoundation。而且全局直方圖二值化導致精准度並不高。這個庫還會帶來一大堆C++的東西,在純iOS7的工程下,不推薦使用。
 
AVFoundation 掃碼的簡單使用
這里說一下,我們禮物說是和passbook一樣,同時可以掃描二維碼和條形碼,真是因為這個特性,導致了我寫這篇總結。 先粘一下掃碼實現部份,如下。
  1. - (BOOL)startReading { 
  2.     _isReading = YES; 
  3.     NSError *error; 
  4.     AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 
  5.  
  6.     AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; 
  7.  
  8.     if (!input) { 
  9.         NSLog(@"%@", [error localizedDescription]); 
  10.         return NO; 
  11.     } 
  12.  
  13.     _captureSession = [[AVCaptureSession alloc] init]; 
  14.     // Set the input device on the capture session. 
  15.     [_captureSession addInput:input]; 
  16.  
  17.     AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init]; 
  18.     [_captureSession addOutput:captureMetadataOutput]; 
  19.  
  20.     // Create a new serial dispatch queue. 
  21.     dispatch_queue_t dispatchQueue; 
  22.     dispatchQueue = dispatch_queue_create("myQueue", NULL); 
  23.     [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue]; 
  24.  
  25.     if (self.qrcodeFlag) 
  26.         [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]]; 
  27.     else 
  28.         [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObjects:AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeQRCode, nil]]; 
  29.  
  30.     _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession]; 
  31.     [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; 
  32.     [_videoPreviewLayer setFrame:self.view.layer.bounds]; 
  33.     [self.view.layer addSublayer:_videoPreviewLayer]; 
  34.  
  35.     [_captureSession startRunning]; 
  36.  
  37.     return YES; 
  38.  
  39.  
  40. -(void)stopReading{ 
  41.     [_captureSession stopRunning]; 
  42.     _captureSession = nil; 
  43.     [_videoPreviewLayer removeFromSuperlayer]; 
  44.  
  45. -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects 
  46.       fromConnection:(AVCaptureConnection *)connection 
  47.     if (!_isReading) return
  48.  
  49.     if (metadataObjects != nil && [metadataObjects count] > 0) { 
  50.         AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0]; 
  51.  
  52.         Do Something.... 
  53.     } 
 這個代碼也不需要加什么注釋,挺簡單易懂的。
 
闡述問題
我們上面說過了:當AVFoundation使用多譯碼器掃描的時候。二維碼是秒殺,但是條形碼卻經常掃不上。如果去掉二維碼的話,條形碼掃描又秒殺的問題。
 
但有趣的事情是,如果我寫了個demo,用上述代碼的話。卻又可以秒殺掃描。這個問題困擾了我一下午,仔細對比了項目中的每一行代碼和我demo中的全部。除了demo沒有畫一個提示框在屏幕上以外,其他地方全都一模一樣。
 
那么為什么導致項目中掃描效率如此之慢呢?
 
猜想1: UI以及后台線程占用大量CPU時間
結果在 instrument下,不攻自破,cpu占用,內存占用非常非常低。
 
猜想2:系統架構問題
因為添加了QRCode才導致掃描變慢的,那么就應該是和算法效率有關。多引入了一個每一幀都要工作的譯碼器,導致條形碼掃描效率下降。我的Demo是arm64 v7s v7 系統全支持,而項目是ArmV7。
這個想法挺異想天開的。覺得可能是Arm64的指令集效率比armv7快得多導致的。我還去問巧哥,armv7和arm64在密集運算的時候效率差多少,會不會比較明顯。
 
但重新配置了一下,還是錯誤的。
 
插曲
我發現把屏幕橫過來掃描效率比豎過來高多了。於是懷疑是不是 Capture 的方向問題。
 
猜想3: 攝像頭方向問題導致解碼效率低
這個猜想,我沒有去證實,因為太麻煩了。要給Session 添加一個新的output 來輸出每一幀,而且還是個CMBuffer,還要手動轉碼。不過后面證實這個也是錯的。
 
猜想4:攝像頭參數問題
當初看AVCam 寫拍照模塊的時候,記得攝像頭有很多參數,ZXing 也有一個文件位叫做精確解碼,犧牲效率換精確度。於是就在想會不會蘋果家的也要設置參數。
 
於是就壞懷這個問題去看文檔去了,結果歪打正着的發現了正確原因。 這是記錄在蘋果的FAQ中的,並沒在AVFoundation 的 Reference 中。具體編號為: Technical Note TN2325
 
正確原因
就是描述問題里面說到的,demo和工程里面的唯一區別,多了個surfaceLayer。如下圖:
為了正確解釋這個有趣的問題,我們要解釋一下條形碼掃描原理。
 
上面有提過二維碼是通過全局直方圖二值化后,按照ISO標准解碼,實際上是,按照1:1:3:1:1去尋找那三個尋像圖形,就是標志性的大方塊。然后圈出二維碼大小再去解碼的。也就是說,再沒設定邊界的情況下全屏都可以。

而條形碼完全不同,他是在Detect Center那個點,畫一個無限延伸的米字型,然后去判斷每一條在線能否解析出條形碼所需要的0101010序列。而iOS默認的Center是 Layer 的 Center。
 
我們再回過頭來看工程中的 SurfaceLayer,其實他提示給用戶的那個框,已經遠離了Center。所以我們豎着掃描的時候,那條水平的掃描線是沒有貫穿條形碼的,所以掃不上他。
 
於是乎要根據設備,iPhone4 iPhone5 通過AVCaptureDeviceFormat和AVCaptureSessionPreset 重新設置一下AVCaptureMetadataOutput rectOfInterest,結果問題就解決了。
 
為什么去掉二維碼就沒事了呢?
 
還在那篇FAQ中,有那么一個表格。
可見,當我們沒有二維碼的時候,他會有個additional存在。用更加優秀且稍微耗時的算法去優化掃描精准度。
 
總結
1.當我們遇到問題的時候,不光要記得看 蘋果的 guide 和 reference,還要記得看以下 sample code,tech note, FAQ。
 
2.說不好有意外收獲 為什么條形碼掃描儀上往往會有一條紅線,這並不是為了擬物化,而是告訴用戶一定要用這條線對准條形碼,否則會有掃不上的可能性。
 
3.正如福爾摩斯所說:拋開所有不可能的,剩下的,不管多么令人匪夷所思,那都是事實。兩套代碼僅有UI不一樣,效果不同,其實就是UI引導用戶錯誤的使用了掃描儀。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM