在 iOS7 以前,在iOS中實現二維碼和條形碼掃描,我們所知的有,兩大開源組件ZBar與ZXing. 這兩大組件我們都有用過,這裡總結下各自的缺點:
ZBar
ZBar在掃描的靈敏度上,和內存的使用上相對於ZXing上都是較優的,但是對於 “圓角二維碼” 的掃描確很困難。如:
ZXing
ZXing 是 Google Code上的一個開源的條形碼掃描庫,是用java設計的,連Google Glass 都在使用的。但有人為了追求更高效率以及可移植性,出現了c++ port. Github上的Objectivc-C port,其實就是用OC代碼封裝了一下而已,而且已經停止維護。這樣效率非常低,在instrument下面可以看到CPU和內存瘋漲,在內存小的機器上很容易崩潰。
AVFoundation
AVFoundation無論在掃描靈敏度和性能上來說都是最優的,所以毫無疑問我們應該切換到AVFoundation,需要兼容iOS 6或之前的版本可以用zbar或zxing代替。
下面介紹本文的重點,無論你是用以上哪一種或其他的解決方案,都需要知道下面兩點。
1. 圖片很小的二維碼
以前測試提了一個bug,說有二維碼掃不了,拿到二維碼一看,是個很小的二維碼,邊長不到1cm,於是就修改了 sessionPreset 為 1080p 的,當時用的是ZXing, 當把圖片質量改清楚時,也造成了性能的下降,基本打開掃描界面就會報memoryWarning,但是也確實解決了小二維碼掃描的問題。
AVCaptureSession 可以設置 sessionPreset 屬性,這個決定了視頻輸入每一幀圖像質量的大小。
AVCaptureSessionPreset320x240
AVCaptureSessionPreset352x288
AVCaptureSessionPreset640x480
AVCaptureSessionPreset960x540
AVCaptureSessionPreset1280x720
AVCaptureSessionPreset1920x1080
以上列舉了部分的屬性值,分別代表輸入圖片質量大小,一般來說AVCaptureSessionPreset640x480就夠使用,但是如果要保證較小的二維碼圖片能快速掃描,最好設置高些,如AVCaptureSessionPreset1920x1080(就是我們常說的1080p).
2. scanCrop
另一個提升掃描速度和性能的就是設置解析的范圍,在zbar和zxing中就是scanCrop, AVFoundation中設置 AVCaptureMetadataOutput 的 rectOfInterest 屬性來配置解析范圍。
最開始我按照文檔說的按照比例值來設置這個屬性,如下:
CGSize size = self.view.bounds.size; CGRect cropRect = CGRectMake(40, 100, 240, 240); captureOutput.rectOfInterest = CGRectMake(cropRect.origin.x/size.width, cropRect.origin.y/size.height, cropRect.size.width/size.width, cropRect.size.height/size.height);
但是發現, Ops, 好像不對啊,掃不到了,明顯不正確呢,於是猜想: AVCapture輸出的圖片大小都是橫著的,而iPhone的屏幕是豎著的,那麼我把它旋轉90°呢:
CGSize size = self.view.bounds.size; CGRect cropRect = CGRectMake(40, 100, 240, 240); captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/size.height, cropRect.origin.x/size.width, cropRect.size.height/size.height, cropRect.size.width/size.width);
OK,貌似對了,在iPhone5上一切工作良好,但是在4s上,或者換了sessionPreset的大小之後,這個框貌似就不那麼准確了, 可能發現超出框上下一些也是可以掃描出來的。 再次猜想: 圖片的長寬比和手機屏幕不是一樣的,這個rectOfInterest是相對於圖片大小的比例。比如iPhone4s屏幕大小是 640x960, 而圖片輸出大小是 1920x1080. 實際的情況可能就是下圖中的效果:
上圖中下面的代表iPhone4s屏幕,大小640x960, 上面代表AVCaptureVideoPreviewLayer中預覽到的圖片位置,在圖片輸入為1920x1080大小時,實際大小上下會被截取一點的,因為我們AVCaptureVideoPreviewLayer設置的videoGravity是AVLayerVideoGravityResizeAspectFill, 類似於UIView的UIViewContentModeScaleAspectFill效果。
於是我對大小做了一下修正:
CGSize size = self.view.bounds.size; CGRect cropRect = CGRectMake(40, 100, 240, 240); CGFloat p1 = size.height/size.width; CGFloat p2 = 1920./1080.; //使用了1080p的圖像輸出 if (p1 < p2) { CGFloat fixHeight = bounds.size.width * 1920. / 1080.; CGFloat fixPadding = (fixHeight - size.height)/2; captureOutput.rectOfInterest = CGRectMake((cropRect.origin.y + fixPadding)/fixHeight, cropRect.origin.x/size.width, cropRect.size.height/fixHeight, cropRect.size.width/size.width); } else { CGFloat fixWidth = bounds.size.height * 1080. / 1920.; CGFloat fixPadding = (fixWidth - size.width)/2; captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/size.height, (cropRect.origin.x + fixPadding)/fixWidth, cropRect.size.height/size.height, cropRect.size.width/fixWidth); }
經過上面的驗證,證實了猜想rectOfInterest是基於圖像的大小裁剪的。
3. 小結
scanCrop對於掃描來說是比較重要的,試想圖片截小點來解析是不是理論上就會更快了呢。網絡上貌似很難搜到關於scanCrop的詳解,希望對看到的人有幫助。
(via:劉坤的技術博客)