iOS8采用系统方法采集和生成二维码

项目中要添加扫码功能。虽然网上已经有很多Demo,但还是想自己好好看看系统文档,亲自踩下坑。将学习过程记录下。
Demo已经放到GitHub上了。主要有以下功能特点

  1. 支持iOS8.0以上系统;
  2. 采用系统API扫描识别二维码;
  3. 采用系统API识别相册中的二维码;若识别失败,调用ZBar方法再识别一次;
  4. 采用系统API生成二维码,二维码中间可以添加一个小图片,可生成彩色二维码;
  5. 扫码界面布局仿照微信。
  6. 可打开手电筒、切换前后置相机

效果图

iOS8采用系统方法采集和生成二维码_第1张图片
效果图

概述

采用AVFoundataion框架来进行视频采集。至少需要以下几步

  • 一个AVCaptureDevice的实例,用来描述输入设备,比如相机或者麦克风
  • 一个AVCaptureInput子类的实例,用来从输入设备配置端口。此处使用的是子类AVCaptureDeviceInput
  • 一个AVCaptureOutput子类的实例,用来管理输出数据。此处使用的是子类AVCaptureMetadataOutput
  • 一个AVCaptureSession的实例,用来协调输入输出流

如果要向用户展示正在录制的内容,可以使用AVCaptureVideoPreviewLayer(CALayer的子类)的实例

Capture Session

AVCaptureSession这个类是管理采集数据的中枢。我们可以使用这个类去管理从输入到输出的数据流。添加想要的输入输出数据到这个session,然后调用startRunning开始采集数据,调用stopRunning停止数据流。

AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];

startRunning会同步执行并阻塞当前线程,直到AVCaptureSession启动或者启动失败。如果启动失败,我们会接收到一个通知AVCaptureSessionRuntimeErrorNotification
注意:由于startRunning执行需要花费一段时间,并且会阻塞当前线程,因此我们应该在一个串行子线程中去执行这个方法,这样可以避免主线程UI被锁。
参考例子AVCam-iOS: Using AVFoundation to Capture Images and Movies

配置Session

设置图片的质量和分辨率。系统提供以下几种配置。当需要识别的图片比较小时,就选高分辨率,反之则选择低分辨率。一般来说选择AVCaptureSessionPreset1280x720或者AVCaptureSessionPreset640x480就可以了。

iOS8采用系统方法采集和生成二维码_第2张图片
image1

在设置之前,先要判断系统是否支持这个分辨率

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}

如果需要根据实际情况选择不同的分辨率,可以使用 beginConfiguration , commitConfiguration这两个方法

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];

监听CaptureSession状态

session开始或者停止运行,发生中断等,都会发出一个通知。
系统提供了如下几种通知,可以监听并且做一些异常判断。

AVCaptureSessionRuntimeErrorNotification
AVCaptureSessionDidStartRunningNotification
AVCaptureSessionDidStopRunningNotification
AVCaptureSessionWasInterruptedNotification
AVCaptureSessionInterruptionEndedNotification

添加输入设备

NSError *error;
AVCaptureDeviceInput *captureDeviceInput =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!captureDeviceInput) {
    // Handle the error appropriately.
}

AVCaptureSession *captureSession = <#Get a capture session#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // Handle the failure.
}

获取输出数据

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMetadataOutput *outPut =  [[AVCaptureMetadataOutput alloc] init];
if ([captureSession canAddOutput:outPut]) {
    [captureSession addOutput:outPut];
    //设置输出执行的委托以及委托运行的线程
    [outPut setMetadataObjectsDelegate:self queue:_metadataObjectsQueue];
//设置支持的类型,此处设置为支持所有的类型
    outPut.metadataObjectTypes = [self.videoDeviceOutput availableMetadataObjectTypes];
}
else {
    // Handle the failure.
}

输出代理方法

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    for (AVMetadataObject *object in metadataObjects) {
          AVMetadataMachineReadableCodeObject *codeObj = (AVMetadataMachineReadableCodeObject *)object;
          NSString *QRMessage = codeObj.stringValue;
        }
}

如果能够正确识别二维码,那么QRMessage就是识别出的数据
可参考例子AVCamBarcode: Using AVFoundation to Detect Barcodes and Faces
通过api的说明可知,当采集到数据后,可在此方法中获得输出数据。此委托方法会运行在上面设置代理时指定的线程中。由于这个方法会很频繁的调用,因此我们必须注意性能问题。

至此,调用相机扫描二维码的功能就完成了。下面看一下如何识别相册中的二维码。

识别图片中的二维码

使用了CIDetector类来识别图片中的二维码。除了二维码,它还可以识别人脸等其他类型。系统提供了几种类型:

CIDetectorTypeFace
CIDetectorTypeRectangle
CIDetectorTypeQRCode

识别的代码如下

- (NSString *)decodeQRImage:(UIImage*)image
{
    CIContext *context = [CIContext contextWithOptions:nil];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
    CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
    NSArray *features = [detector featuresInImage:ciImage];
    
    //如果使用系统发发不能识别图中二维码,再采用ZBar尝试一次
    if (features.count > 0) {
        CIQRCodeFeature *feature = [features firstObject];
        return feature.messageString;
    }else {
        //采用ZBar识别
        CGImageRef cgImageRef = image.CGImage;
        
        ZBarQRDecoder *QRDecoder = [[ZBarQRDecoder alloc] init];
        NSArray *symbols = [QRDecoder scanImage:cgImageRef];

        for (ZBarSymbol *symbol in symbols) {
            return symbol.data;
        }
    }
    return nil;
}

注意 NSArray *features = [detector featuresInImage:ciImage];这个方法不稳定,在某些设备上会存在识别失败的问题。看网上有人说在iPhone5、iPhone4等老设备上会存在识别不出来的问题。我自己亲测的是,使用5c拍摄的二维码全都识别不了。这个应该是苹果API的问题。为了解决这个问题,引用了ZBar,当使用系统API识别失败时,用ZBar再尝试一次。

ZBarSDK

ZBarSDK
在github上看到ZBar最近的更新也是4年前了。虽然已经这么久不维护了,但是既然当年这份代码能运行的很好,现在二维码的标准也没有什么更新,那这份代码依然能够很好的运行。完全可以满足我目前的需求。
从GitHub上下载的ZBar工程有很多问题,运行起来也会有很多错误。因为我的项目中只需要使用识别图片的功能,因此我将这个库整理了一下,删掉了不必要的操作。具体也是看我的Demo。
需要注意的两点是

  1. 需要在工程中添加libiconv.2.4.0.tbd链接库
  2. ZBarSymbol.m, ZBarImageScanner.m, ZBarImage.m三个文件要禁止使用ARC,Build Phases中添加-fno-objc-arc
  3. 在工程中导入头文件 ZBarSDK.h,调用ZBarQRDecoder类的scanImage方法来扫描图片。

生成二维码

使用CIFilter类来创建自定义的二维码滤镜,生成二维码图片。
关于自定义滤镜的使用,可以参考官方文档
使用CIQRCodeGenerator创建一个二维码滤镜

- (void)generateQRCode
{
    // Create a new filter with the given name.
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    if (!filter) return;
    
    // -setDefaults instructs the filter to configure its parameters
    // with their specified default values.
    [filter setDefaults];
    
    //二维码数据
    NSString *QRMessage = @"http://www.jianshu.com/users/3688d37243de/latest_articles";
    //    官方建议使用 NSISOLatin1StringEncoding 来编码,但经测试这种编码对中文或表情无法生成,改用 NSUTF8StringEncoding 就可以了。
    NSData *inputData = [QRMessage dataUsingEncoding:NSUTF8StringEncoding];
    
    
    // 设置过滤器的输入值, KVC赋值
    [filter setValue:inputData forKey:@"inputMessage"];
    CIImage *outputImage = [filter outputImage];
    
    // 输出的图片比较小,需要放大。此处放大20倍
    outputImage = [outputImage imageByApplyingTransform:CGAffineTransformMakeScale(20, 20)];
    
    UIImage *QRImage = [UIImage imageWithCIImage:outputImage];
    _QRCodeImageView.image = QRImage;
}

为二维码添加中间的小图片

此处直接在二维码的中心画一个UIImageView

-(void)drawSmallIcon
{
    UIImageView *iconView = [[UIImageView alloc]init];
    iconView.layer.cornerRadius = 4.0f;
    iconView.layer.borderColor = [UIColor whiteColor].CGColor;
    iconView.layer.borderWidth = 2.0f;
    [_QRCodeImageView addSubview:iconView];
    
    UIImage *avator = [UIImage imageNamed:@"avator.jpg"];
    avator = [avator resizeToSize:CGSizeMake(100, 100)];
    iconView.image = avator;
    //设置小图片为二维码的20%
    [iconView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(0);
        make.width.equalTo(_QRCodeImageView.mas_width).multipliedBy(0.2f);
        make.height.equalTo(iconView.mas_width);
    }];
}

除了文中指出的文档和demo外,还参考了以下文章

  • iOS原生二维码的生成与扫描
  • Core Image Filter Reference
  • 使用 OpenCV 识别 QRCode

你可能感兴趣的:(iOS8采用系统方法采集和生成二维码)