天天看点

ios 二维码

转载:http://blog.cnbluebox.com/blog/2014/08/26/ioser-wei-ma-sao-miao/

IOS二维码扫描,你需要注意的两件事

在 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,需要兼容IOS6或之前的版本可以用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);

但是发现, Oops, 好像不对啊,扫不到了,明显不正确呢,于是猜想: 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的详解,希望对看到的人有帮助。

---------------------------------------------------------------------------------------------------------------------

继续补充:

http://blog.txx.im/blog/2014/05/29/qrcode-detect-with-avfoundation/

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

前言

這篇文章是我們在新發佈的禮物說的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一樣,同時可以掃描二維碼和條形碼,真是因為這個特性,導致了我寫這篇總結。 先粘一下掃碼實現部份,如下。

- (BOOL)startReading {

    _isReading = YES;

    NSError *error;

    AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];

    if (!input) {

        NSLog(@"%@", [error localizedDescription]);

        return NO;

    }

    _captureSession = [[AVCaptureSession alloc] init];

    // Set the input device on the capture session.

    [_captureSession addInput:input];

    AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];

    [_captureSession addOutput:captureMetadataOutput];

    // Create a new serial dispatch queue.

    dispatch_queue_t dispatchQueue;

    dispatchQueue = dispatch_queue_create("myQueue", NULL);

    [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];

    if (self.qrcodeFlag)

        [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];

    else

        [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObjects:AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeQRCode, nil]];

    _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];

    [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

    [_videoPreviewLayer setFrame:self.view.layer.bounds];

    [self.view.layer addSublayer:_videoPreviewLayer];

    [_captureSession startRunning];

    return YES;

}

-(void)stopReading{

    [_captureSession stopRunning];

    _captureSession = nil;

    [_videoPreviewLayer removeFromSuperlayer];

}

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects

      fromConnection:(AVCaptureConnection *)connection

{

    if (!_isReading) return;

    if (metadataObjects != nil && [metadataObjects count] > 0) {

        AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];

        Do Something....

    }

}

這個代碼也不需要加什麼註釋,挺簡單易懂的。

闡述問題

我們上面說過了:當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中,有那麼一個表格。

Supported Device    Scan lines (only 1D codes enabled)    Scan lines (when 2D codes are also enabled)

iPhone 4    Center    Center

iPhone 4S and later    Center + additional    Center

iPad (all)    Center + additional    Center

iPod Touch    Center + additional    Center

可見,當我們沒有二維碼的時候,他會有個additional存在。用更加優秀且稍微耗時的算法去優化掃描精准度。

總結

當我們遇到問題的時候,不光要記得看 蘋果的 guide 和 reference,還要記得看以下 sample code,tech note, FAQ。說不好有意外收穫

為什麼條形碼掃描器上往往會有一條紅線,這並不是為了擬物化,而是告訴用戶一定要用這條線對準條形碼,否則會有掃不上的可能性。

正如福爾摩斯所說:抛开所有不可能的,剩下的,不管多么令人匪夷所思,那都是事实。兩套代碼僅有UI不一樣,效果不同,其實就是UI引導用戶錯誤的使用了掃描器。

iOS